mathsgod/light-server

A lightweight PHP 8.1+ file-based routing server inspired by Nuxt.js pages, built on PSR-7/PSR-15 with League Route and Laminas.

Maintainers

👁 mathsgod

Package info

github.com/mathsgod/light-server

pkg:composer/mathsgod/light-server

Statistics

Installs: 165

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2.1 2026-06-01 03:00 UTC

Requires (Dev)

Suggests

None

Provides

None

Conflicts

None

Replaces

None

MIT f5d7b1f499c4ced717f7fab39ed464360c4ad98c

  • Raymond Chong <mathsgod.woop@yahoo.com>

This package is auto-updated.

Last update: 2026-06-09 02:56:36 UTC


README

👁 Tests

A lightweight PHP 8.1+ web framework with file-based routing, PSR-7 support, and automatic dependency injection.

Requirements

  • PHP 8.1+

Features

  • 📄 File-system based routing — drop a file in pages/, get a route automatically
  • 🔀 Dynamic routespages/blog/{id}/index.php/blog/{id}
  • 🛠️ PSR-7 / PSR-15 standard — standard HTTP message and middleware interfaces
  • 💉 Automatic dependency injection — method parameters resolved from a PSR-11 container
  • 🎯 Attribute-based middleware — attach PSR-15 middleware directly to handler methods via PHP attributes
  • 💪 Global middleware — pipe middleware at the server level
  • 🔒 Built-in security headers — optional SecurityHeadersMiddleware

Installation

composer require mathsgod/light-server

Quick Start

1. Create an entry point

<?php
// public/index.php

require 'vendor/autoload.php';

(new Light\Server())->run();

2. Create a page handler

<?php
// pages/index.php

use Laminas\Diactoros\Response\TextResponse;

return new class {
 public function get(): TextResponse
 {
 return new TextResponse("Hello, World!");
 }

 public function post(): TextResponse
 {
 return new TextResponse("POST request received");
 }
};

Routing

Routes are generated automatically from the pages/ directory structure:

File Route
pages/index.php /
pages/about.php /about
pages/blog/index.php /blog/
pages/blog/{id}/index.php /blog/{id}

If the pages/ directory does not exist, the server starts normally with no routes.

HTTP Methods

Define public methods matching the HTTP verb (case-insensitive):

return new class {
 public function get(): ResponseInterface { }
 public function post(): ResponseInterface { }
 public function put(): ResponseInterface { }
 public function delete(): ResponseInterface { }
 public function patch(): ResponseInterface { }
};

Dynamic Routes

Route parameters are available via $request->getAttribute():

<?php
// pages/blog/{id}/index.php

use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ServerRequestInterface;

return new class {
 public function get(ServerRequestInterface $request): JsonResponse
 {
 $id = $request->getAttribute('id');
 return new JsonResponse(['id' => $id]);
 }
};

Route Constraints

Since routing is powered by League\Route, you can constrain route parameters directly in the folder/file name using {param:type} syntax:

Constraint Pattern Example match
{id:number} [0-9]+ 123
{name:word} [a-zA-Z]+ raymond
{slug:slug} [a-z0-9-]+ my-post
{token:alphanum_dash} [a-zA-Z0-9-_]+ abc-123_x
{id:uuid} UUID format 550e8400-e29b-41d4-a716-446655440000
{path:any} .+ foo/bar/baz

Example: Only match when id is numeric and name is alphabetic:

pages/
└── user/
 └── {id:number}/
 └── {name:word}/
 └── index.php → /user/{id:number}/{name:word}/
/user/1/raymond/ ✅ matches (id=1, name=raymond)
/user/test/raymond/ ❌ no match (id is not numeric)
<?php
// pages/user/{id:number}/{name:word}/index.php

use Laminas\Diactoros\Response\TextResponse;
use Psr\Http\Message\ServerRequestInterface;

return new class {
 public function get(ServerRequestInterface $request): TextResponse
 {
 $id = $request->getAttribute('id');
 $name = $request->getAttribute('name');
 return new TextResponse("User ID: $id, Name: $name");
 }
};

Dependency Injection

Method parameters are resolved automatically by type hint:

  • ServerRequestInterface — injects the current HTTP request
  • Any other type hint — resolved from the PSR-11 container (if provided)
  • Unresolvable parameters — receive null
<?php
// pages/users.php

use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ServerRequestInterface;

return new class {
 public function get(ServerRequestInterface $request, UserRepository $repo): JsonResponse
 {
 return new JsonResponse($repo->findAll());
 }
};

Pass a PSR-11 container when creating the server:

$container = /* your PSR-11 container */;
(new Light\Server($container))->run();

Middleware

Global Middleware

Use pipe() to apply middleware to all routes:

$server = new Light\Server();
$server->pipe(new Light\Server\SecurityHeadersMiddleware());
$server->run();

Attribute-based Middleware (per method)

Attach PSR-15 middleware to a specific handler method using PHP attributes:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

#[\Attribute]
class AuthMiddleware implements MiddlewareInterface
{
 public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
 {
 // auth logic ...
 return $handler->handle($request);
 }
}

return new class {
 #[AuthMiddleware]
 public function get(): ResponseInterface { /* ... */ }
};

Built-in Middleware

CorsMiddleware

Handles CORS preflight (OPTIONS) requests and adds CORS headers to all responses.

  • OPTIONS requests are intercepted before routing and return 204 No Content with CORS headers
  • All other requests pass through normally with CORS headers appended
$server = new Light\Server();
$server->pipe(new Light\Server\CorsMiddleware(
 allowedOrigins: ['https://example.com'],
 allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
 allowedHeaders: ['Content-Type', 'Authorization'],
 allowCredentials: true,
 maxAge: 3600,
));
$server->run();

All constructor parameters are optional:

Parameter Default Description
allowedOrigins ['*'] Allowed origins. Use ['*'] for wildcard
allowedMethods ['GET','POST','PUT','PATCH','DELETE','OPTIONS'] Allowed HTTP methods
allowedHeaders ['Content-Type','Authorization'] Allowed request headers
allowCredentials false Set true to send Access-Control-Allow-Credentials: true
maxAge 86400 Preflight cache duration in seconds

Note: When using allowCredentials: true, allowedOrigins must list specific origins — wildcard * does not work with credentials.

SecurityHeadersMiddleware

Adds common security response headers:

Header Value
X-Content-Type-Options nosniff
X-Frame-Options DENY
X-XSS-Protection 1; mode=block
Referrer-Policy strict-origin-when-cross-origin
Content-Security-Policy default-src 'self'
$server = new Light\Server();
$server->pipe(new Light\Server\SecurityHeadersMiddleware());
$server->run();

Testing

composer install
./vendor/bin/phpunit

The test suite runs against PHP 8.1, 8.2, 8.3, 8.4, and 8.5 via the GitHub Actions matrix in .github/workflows/tests.yml.

License

MIT — see the LICENSE file for details.