PHPackages                             phputil/router - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. phputil/router

ActiveLibrary[Framework](/categories/framework)

phputil/router
==============

ExpressJS-like router for PHP

v0.3.6(1y ago)5962↓17.6%2MITPHPPHP &gt;=7.4CI passing

Since Dec 22Pushed 1y ago1 watchersCompare

[ Source](https://github.com/thiagodp/router)[ Packagist](https://packagist.org/packages/phputil/router)[ RSS](/packages/phputil-router/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (5)Versions (25)Used By (2)

[![Packagist Version](https://camo.githubusercontent.com/6371e0f5a8b03e5fa583ec59a049d388221d59fa450c6f53f6838bca9f4ea82d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7068707574696c2f726f757465723f7374796c653d666f722d7468652d626164676526636f6c6f723d677265656e)](https://github.com/thiagodp/router/commits/main/)[![GitHub License](https://camo.githubusercontent.com/777d015e4e88a4671d2fd9f1ae6732f1c5ca63340288d1370ea89b146e71a9c3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f74686961676f64702f726f757465723f7374796c653d666f722d7468652d626164676526636f6c6f723d677265656e)](https://github.com/thiagodp/router/blob/main/LICENSE)[![Packagist Downloads](https://camo.githubusercontent.com/f907e12e83e1895b9101020afa0536d03b6ebfe90beaae7abbe36885900564fd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7068707574696c2f726f757465723f7374796c653d666f722d7468652d626164676526636f6c6f723d677265656e)](https://packagist.org/packages/phputil/router)[![Build](https://github.com/thiagodp/router/actions/workflows/ci.yml/badge.svg?style=for-the-badge&color=green)](https://github.com/thiagodp/cors/actions)

phputil/router
==============

[](#phputilrouter)

> ExpressJS-like router for PHP

- No third-party dependencies
- Unit-tested
- Mockable - it's easy to create automated tests for your API

👉 Do **NOT** use it in production yet - just for toy projects.

Installation
------------

[](#installation)

> Requires PHP 7.4+

```
composer require phputil/router
```

*Is it useful for you? Consider giving it a Star ⭐*

### Installation notes

[](#installation-notes)

- Unlike ExpressJS, `phputil/router` needs an HTTP server to run (if the request is not [mocked](#mocking-an-http-request)). You can use the HTTP server of your choice, such as `php -S localhost:80`, [Apache](https://httpd.apache.org/), [Nginx](https://nginx.org/) or [http-server](https://www.npmjs.com/package/http-server). **See [Server Configuration](server.md) for more information.**
- If you are using **Apache** or **Nginx**, you may need to inform the `rootURL` parameter when calling `listen()`. Example: ```
    // Sets the 'rootURL' to where the index.php is located.
    $app->listen( [ 'rootURL' => dirname( $_SERVER['PHP_SELF'] ) ] );
    ```

Middlewares
-----------

[](#middlewares)

You may also want to install the following middlewares:

- [phputil/cors](https://github.com/thiagodp/cors) - [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) Middleware
- [phputil/csrf](https://github.com/thiagodp/phputil-csrf) - Anti [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) Middleware

> ℹ Did you create a useful middleware? Open an Issue for including it here.

Examples
--------

[](#examples)

### Hello World

[](#hello-world)

```
require_once 'vendor/autoload.php';
use \phputil\router\Router;

$app = new Router();
$app->get( '/', function( $req, $res ) {
    $res->send( 'Hello World!' );
} );
$app->listen();
```

### Using parameters

[](#using-parameters)

```
require_once 'vendor/autoload.php';
use \phputil\router\Router;

$app = new Router();
$app->get( '/', function( $req, $res ) {
        $res->send( 'Hi, Anonymous' );
    } )
    ->get( '/:name', function( $req, $res ) {
        $res->send( 'Hi, ' . $req->param( 'name' ) );
    } )
    ->get( '/json/:name', function( $req, $res ) {
        $res->json( [ 'hi' => $req->param( 'name' ) ] );
    } );
$app->listen();
```

### Middleware per route

[](#middleware-per-route)

```
require_once 'vendor/autoload.php';
use \phputil\router\Router;

$middlewareIsAdmin = function( $req, $res, &$stop ) {
    session_start();
    $isAdmin = isset( $_SESSION[ 'admin' ] ) && $_SESSION[ 'admin' ];
    if ( $isAdmin ) {
        return; // Access allowed
    }
    $stop = true;
    $res->status( 403 )->send( 'Admin only' ); // Forbidden
};

$app = new Router();
$app->get( '/admin', $middlewareIsAdmin, function( $req, $res ) {
    $res->send( 'Hello, admin' );
} );
$app->listen();
```

[See all the examples](https://github.com/thiagodp/router/tree/main/examples/)

> ℹ Interested in helping us? Submit a Pull Request with a new example or open an Issue with your code.

Features
--------

[](#features)

- \[✔\] Support to standard HTTP methods (`GET`, `POST`, `PUT`, `DELETE`, `HEAD`, `OPTIONS`) and `PATCH`.
- \[✔\] Route parameters
    - *e.g.* `$app->get('/customers/:id', function( $req, $res ) { $res->send( $req->param('id') ); } );`
- \[✔\] URL groups
    - *e.g.* `$app->route('/customers/:id')->get('/emails', $cbGetEmails );`
- \[✔\] Global middlewares
    - *e.g.* `$app->use( function( $req, $res, &$stop ) { /*...*/ } );`
- \[✔\] Middlewares per URL group
    - *e.g.* `$app->route( '/admin' )->use( $middlewareIsAdmin )->get( '/', function( $req, $res ) { /*...*/ } );`
- \[✔\] Middlewares per route
    - *e.g.* `$app->get( '/', $middleware1, $middleware2, function( $req, $res ) { /*...*/ } );`
- \[✔\] Request cookies
    - *e.g.* `$app->get('/', function( $req, $res ) { $res->send( $req->cookie('sid') ); } );`
- \[✔\] *Extra*: Can mock HTTP requests for testing, without the need to running an HTTP server.
- \[🕑\] *(soon)* Deal with `multipart/form-data` on `PUT` and `PATCH`

API
---

[](#api)

This library does not aim to cover the entire [ExpressJS API](https://expressjs.com/en/api.html). However, feel free to contribute to this project and add more features.

Types:

- [Middleware](#middleware)
- [Router](#router)
- [RouterOptions](#routeroptions)
- [HttpRequest](#httprequest)
- [ExtraData](#extradata)
- [HttpResponse](#httpresponse)

### Middleware

[](#middleware)

In `phputil/router`, a middleware is a function that:

1. Perform some action (e.g., set response headers, verify permissions) *before* a route is evaluated.
2. Can stop the router, optionally setting a response.

Syntax:

```
function ( HttpRequest $req, HttpResponse $res, bool &$stop = false )
```

where:

- `$req` allows to *get* all the *request* headers and data.
- `$res` allows to *set* all the *response* headers and data.
- `$stop` allows to stop the router, when set to `true`.

### Router

[](#router)

> Class that represents a router.

#### get

[](#get)

Method that deals with a `GET` HTTP request.

```
function get( string $route, callable ...$callbacks )
```

where:

- `$route` is a route (path).
- `$callbacks` can receive none, one or more [middleware](#middleware) functions and one route handler - which must be the last function.

A route handler has the following syntax:

```
function ( HttpRequest $req, HttpResponse $res )
```

where:

- `$req` allows to *get* all the *request* headers and data.
- `$res` allows to *set* all the *response* headers and data.

Examples:

```
use \phputil\router\HttpRequest;
use \phputil\router\HttpResponse;

$app->get( '/hello', function( HttpRequest $req, HttpResponse $res ) {
        $res->send( 'Hello!' );
    } )
    ->get( '/world',
        // Middleware
        function( HttpRequest $req, HttpResponse $res, bool &$stop ) {
            if ( $req->header( 'Origin' ) === 'http://localhost' ) {
                $res->status( 200 )->send( 'World!' );
                $stop = true;
            }
        },
        // Route handler
        function( HttpRequest $req, HttpResponse $res ) {
            $res->status( 400 )->send( 'Error: not in http://localhost :(' );
        }
    );
```

#### post

[](#post)

Method that deals with a `POST` HTTP request. Same syntax as [get](#get)'s.

#### put

[](#put)

Method that deals with a `PUT` HTTP request. Same syntax as [get](#get)'s.

#### delete

[](#delete)

Method that deals with a `DELETE` HTTP request. Same syntax as [get](#get)'s.

#### head

[](#head)

Method that deals with a `HEAD` HTTP request. Same syntax as [get](#get)'s.

#### option

[](#option)

Method that deals with a `OPTION` HTTP request. Same syntax as [get](#get)'s.

#### patch

[](#patch)

Method that deals with a `PATCH` HTTP request. Same syntax as [get](#get)'s.

#### all

[](#all)

Method that deals with any HTTP request. Same syntax as [get](#get)'s.

#### group

[](#group)

Alias to the method [route](#route).

#### route

[](#route)

Method that adds a route group, where you can register one or more HTTP method handlers.

Example:

```
$app->
    route( '/employees' )
        ->get( '/emails', function( $req, $res ) { /* GET /employees/emails  */ } )
        ->get( '/phone-numbers', function( $req, $res ) { /* GET /employees/phone-numbers */ } )
        ->post( '/children', function( $req, $res ) { /* POST /employees/children */ } )
        ->end() // 👈 Finishes the group and back to "/"
    ->get( '/customers', function( $req, $res ) { /* GET /customers */ } )
    ;
```

⚠️ Don't forget to finish a route/group with the method `end()`.

#### end

[](#end)

Method that finishes a route group and returns to the group parent.

Example:

```
$app->
    route( '/products' )
        ->get( '/colors', function( $req, $res ) { /* GET /products/colors  */ } )
        ->route( '/suppliers' )
            ->get( '/emails', function( $req, $res ) { /* GET /products/suppliers/emails */ } )
            ->end() // Finishes "/suppliers" and back to "/products"
        ->get( '/sizes', function( $req, $res ) { /* GET /products/sizes  */ } )
        ->end() // 👈 Finishes "/products" and back to "/"
    ->get( '/sales', function( $req, $res ) { /* GET /sales  */ } )
    ;
```

#### use

[](#use)

Method that adds a [middleware](#middleware) to be evaluated before the routes declared after it.

Example:

```
$app
    ->use( $myMiddlewareFunction )
    ->get( '/hello', $sayHelloFunction ); // Executes after the middleware
```

#### listen

[](#listen)

Method that executes the router.

```
function listen( array|RouterOptions $options = [] ): void
```

Options are:

- `rootURL` is a string that sets the root URL. Example: `dirname( $_SERVER['PHP_SELF'] )`. By default it is `''`.
- `req` is an object that implements the interface `HttpRequest`, which retrieves all the headers and data from a HTTP request. *Changing it is only useful if you want to unit test your API* - see [Mocking an HTTP request](#mocking-an-http-request). By default, it will receive an object from the class `RealHttpRequest`.
- `res` is an object that implements the interface `HttpResponse`. *You probably won't need to change its value*. By default, it will receive an object from the class `RealHttpResponse`.

Example:

```
// Sets the 'rootURL' to where the index.php is located.
$app->listen( [ 'rootURL' => dirname( $_SERVER['PHP_SELF'] ) ] );
```

You can also use an instance of `RouterOptions` for setting the options:

```
use phputil\router\RouterOptions;
// Sets the 'rootURL' to where the index.php is located.
$app->listen( ( new RouterOptions() )->withRootURL( dirname( $_SERVER['PHP_SELF'] ) ) );
```

### RouterOptions

[](#routeroptions)

> Options for the [Router](#router)'s [listen()](#listen) method.

#### withRootURL

[](#withrooturl)

```
withRootURL( string $url ): RouterOptions
```

#### withReq

[](#withreq)

```
withReq( HttpRequest $req ): RouterOptions
```

#### withRes

[](#withres)

```
withRes( HttpResponse $res ): RouterOptions
```

### HttpRequest

[](#httprequest)

> Interface that represents an HTTP request.

API:

```
interface HttpRequest {

    /** Returns the current URL or `null` on failure. */
    function url(): ?string;

    /** Returns the current URL without any queries. E.g. `/foo?bar=10` -> `/foo` */
    function urlWithoutQueries(): ?string;

    /** Returns the URL queries. E.g. `/foo?bar=10&zoo=A` -> `['bar'=>'10', 'zoo'=>'A']` */
    function queries(): array;

    /** Returns all HTTP request headers */
    function headers(): array;

    /** Returns the header with the given case-insensitive name, or `null` if not found. */
    function header( $name ): ?string;

    /** Returns the raw body or `null` on failure. */
    function rawBody(): ?string;

    /**
     * Returns the converted content, depending on the `Content-Type` header:
     *   - For `x-www-form-urlencoded`, it returns an `array`;
     *   - For `application/json`, it returns an `object` or an `array` (depending on the content).
     *   - Otherwise it returns a `string`, or `null` on failure.
     */
    function body();

    /** Returns the HTTP request method or `null` on failure. */
    function method(): ?string;

    /** Returns all cookies as an array (map). */
    function cookies(): array;

    /**
     * Returns the cookie value with the given case-insensitive key or `null` if not found.
     *
     * @param string $key Cookie key.
     * @return string|null
     */
    function cookie( $key ): ?string;

    /**
     * Returns a URL query or route parameter with the given name (key),
     * or `null` when the given name is not found.
     *
     * @param string $name Parameter name.
     * @return string
     */
    function param( $name ): ?string;

    /**
     * Returns all the URL queries and route parameters as an array (map).
     * @return array
     */
    function params(): array;

    /**
     * Returns extra, user-configurable data.
     * @return ExtraData
     */
    function extra(): ExtraData;

}
```

### ExtraData

[](#extradata)

> Extra, user-defined data.

Syntax:

```
class ExtraData {

    /**
     * Sets a value to the given key. Chainable method.
     *
     * @param string|int $key
     * @param mixed $value
     * @return ExtraData
     */
    function set( $key, $value ): ExtraData;

    /**
     * Returns the value for the given key, or null otherwise.
     * @param string|int $key
     * @return mixed
     */
    function get( $key );

    /**
     * Returns the keys and values as an array.
     */
    function toArray(): array;

}
```

### HttpResponse

[](#httpresponse)

> Interface that represents an HTTP response.

Most of its methods are chainable, that is, you can call them in a sequence. Example:

```
$response->status( 201 )->send( 'Saved successfully.' );
```

API:

```
interface HttpResponse {

    /**
     * Sets the HTTP status code.
     *
     * @param int $code HTTP status code.
     * @return HttpResponse
     */
    function status( int $code ): HttpResponse;

    /**
     * Indicates if the current HTTP status code is equal to the given one.
     *
     * @param int $code HTTP status code.
     * @return bool
     */
    function isStatus( int $code ): bool;

    /**
     * Sets an HTTP header.
     *
     * @param string $header HTTP header.
     * @param string|int|float|bool|array $value Header value.
     * @return HttpResponse
     */
    function header( string $header, $value ): HttpResponse;

    /**
     * Indicates if the response has the given HTTP header.
     *
     * @param string $header HTTP header.
     * @return boolean
     */
    function hasHeader( string $header ): bool;

    /**
     * Returns the response header, if it exists. Returns `null` otherwise.
     *
     * @param string $header HTTP header.
     * @return string|null
     */
    function getHeader( string $header ): ?string;

    /**
     * Returns all the response headers. If a header key is given, it returns all the headers with the given key.
     * The headers are returned as an array of [ key, value ] pairs.
     *
     * Example: `[['Set-Cookie', 'foo=1;'], ['Set-Cookie', 'bar=hello;'], ['Content-Type', 'application/json']]`
     *
     * Note that the inner arrays do not have keys.
     *
     * @param string $header HTTP header. Optional, it default to `''`.
     * @return array
     */
    function getHeaders( string $header = '' ): array;

    /**
     * Removes the first header with the given key. Optionally removes all the headers with the given key.
     *
     * @param string $header Header to remove.
     * @param bool $removeAll Option (default `false`) to remove all the headers with the given key.
     * @return int The number of removed headers.
     */
    function removeHeader( string $header, bool $removeAll = false ): int;

    /**
     * Sets a redirect response.
     *
     * @param int $statusCode HTTP status code.
     * @param string|null $path Path.
     * @return HttpResponse
     */
    function redirect( int $statusCode, $path = null ): HttpResponse;

    /**
     * Sets a cookie.
     *
     * @param string $name Name (key)
     * @param string $value Value.
     * @param array $options Optional map with the following options:
     *  - `domain`: string
     *  - `path`: string
     *  - `httpOnly`: true|1
     *  - `secure`: true|1
     *  - `maxAge`: int
     *  - `expires`: string
     *  - `sameSite`: true|1
     * @return HttpResponse
     *
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies for options' meanings.
     */
    function cookie( string $name, string $value, array $options = [] ): HttpResponse;

    /**
     * Clears a cookie with the given name (key).
     *
     * @param string $name Name (key)
     * @param array $options Optional map with the same options as #cookie()'s.
     * @return HttpResponse
     */
    function clearCookie( string $name, array $options = [] ): HttpResponse;

    /**
     * Sets the `Content-Type` header with the given MIME type.
     *
     * @param string $mime MIME type.
     * @return HttpResponse
     */
    function type( string $mime ): HttpResponse;

    /**
     * Sends the given HTTP response body.
     *
     * @param mixed $body Response body.
     * @return HttpResponse
     */
    function send( $body ): HttpResponse;

    /**
     * Sends a file based on its path.
     *
     * @param string $path File path
     * @param array $options Optional map with the options:
     *  - `mime`: string - MIME type, such as `application/pdf`.
     * @return HttpResponse
     */
    function sendFile( string $path, array $options = [] ): HttpResponse;

    /**
     * Send the given content as JSON, also setting the needed headers.
     *
     * @param mixed $body Content to send as JSON.
     * @return HttpResponse
     */
    function json( $body ): HttpResponse;

    /**
     * Ends the HTTP response.
     *
     * @param bool $clear If it is desired to clear the headers and the body after sending them. It defaults to `true`.
     */
    function end( bool $clear = true ): HttpResponse;
}
```

### Mocking an HTTP request

[](#mocking-an-http-request)

👉 Useful for API testing

```
require_once 'vendor/autoload.php';
use \phputil\router\FakeHttpRequest;
use \phputil\router\Router;
$app = new Router();

// Set a expectation
$app->get( '/foo', function( $req, $res ) { $res->send( 'Called!' ); } );

// Mock the request
$fakeReq = new FakeHttpRequest();
$fakeReq->withURL( '/foo' )->withMethod( 'GET' );

// Use the mock request
$app->listen( [ 'req' => $fakeReq ] ); // It will use the fake request to call "/foo"
```

License
-------

[](#license)

[MIT](LICENSE) © [Thiago Delgado Pinto](https://github.com/thiagodp)

###  Health Score

33

—

LowBetter than 72% of packages

Maintenance46

Moderate activity, may be stable

Popularity21

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~38 days

Recently: every ~45 days

Total

24

Last Release

421d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/154324d722a6ee9c252a0338329781084a97af2d0ea9faaf39176df5a689a2ec?d=identicon)[thiagodp](/maintainers/thiagodp)

---

Top Contributors

[![thiagodp](https://avatars.githubusercontent.com/u/2997844?v=4)](https://github.com/thiagodp "thiagodp (62 commits)")

---

Tags

expressjshttpphprouter

###  Code Quality

Static AnalysisPHPStan, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/phputil-router/health.svg)

```
[![Health](https://phpackages.com/badges/phputil-router/health.svg)](https://phpackages.com/packages/phputil-router)
```

###  Alternatives

[laravel/dusk

Laravel Dusk provides simple end-to-end testing and browser automation.

1.9k39.6M299](/packages/laravel-dusk)[nineinchnick/edatatables

Grid widget for the Yii Framework, wrapper for the DataTables jQuery plugin

173.2k](/packages/nineinchnick-edatatables)[link-cloud/fast-hyperf

LinkCloud Fast Hyperf

241.2k1](/packages/link-cloud-fast-hyperf)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
