{"id":33956752,"url":"https://github.com/edgaralexanderfr/php-espresso","last_synced_at":"2026-04-05T18:32:33.325Z","repository":{"id":142263497,"uuid":"603265079","full_name":"edgaralexanderfr/php-espresso","owner":"edgaralexanderfr","description":"Runtime web server for PHP.","archived":false,"fork":false,"pushed_at":"2024-06-24T03:17:19.000Z","size":584,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-14T09:28:08.492Z","etag":null,"topics":["async","php","php8","web","webserver","webservers"],"latest_commit_sha":null,"homepage":"https://packagist.org/packages/edgaralexanderfr/php-espresso","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edgaralexanderfr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-18T01:28:29.000Z","updated_at":"2024-05-15T00:07:24.000Z","dependencies_parsed_at":"2024-06-18T04:07:47.972Z","dependency_job_id":null,"html_url":"https://github.com/edgaralexanderfr/php-espresso","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/edgaralexanderfr/php-espresso","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgaralexanderfr%2Fphp-espresso","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgaralexanderfr%2Fphp-espresso/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgaralexanderfr%2Fphp-espresso/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgaralexanderfr%2Fphp-espresso/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edgaralexanderfr","download_url":"https://codeload.github.com/edgaralexanderfr/php-espresso/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgaralexanderfr%2Fphp-espresso/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31446524,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T15:22:31.103Z","status":"ssl_error","status_checked_at":"2026-04-05T15:22:00.205Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","php","php8","web","webserver","webservers"],"created_at":"2025-12-12T20:46:01.752Z","updated_at":"2026-04-05T18:32:33.319Z","avatar_url":"https://github.com/edgaralexanderfr.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PHP Espresso Framework\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/edgaralexanderfr/php-espresso/master/public/img/example.gif\" alt=\"PHP Espresso Example GIF\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/edgaralexanderfr/php-espresso/releases/latest\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/version-v1.0.0-informational.svg\" alt=\"View last release\" title=\"View last release\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.php.net/releases/8.0/es.php\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/php-\u003e=8.0.0-informational.svg\" alt=\"PHP 8.0.0\" title=\"Requires PHP 8.0.0 or major\" /\u003e\n    \u003c/a\u003e\n    \u003cimg src=\"https://img.shields.io/badge/experimental-critical.svg\" alt=\"Experimental\" title=\"Not intended for production use\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/sockets-yellowgreen.svg\" alt=\"Sockets\" title=\"PHP sockets module\" /\u003e\n    \u003ca href=\"https://packagist.org/packages/edgaralexanderfr/php-espresso\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/composer-yellowgreen.svg\" alt=\"Composer\" title=\"composer require edgaralexanderfr/php-espresso\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n**PHP Espresso** is a small PHP Framework I created to develop runtime web servers for PHP running CLI programs and scripts. Very similar to frameworks like **Express** for _**NodeJS**_, **Gorilla Mux** for _**Golang**_, etc.\n\n**IMPORTANT NOTE:** This is just a _proof of concept_ to test the reliability of a runtime web server for PHP, its use and implementation is discouraged for **production-level** projects as it's an experimental framework for learning purposes.\n\nPHP was designed to be a **Single-Threaded** **Non-Asynchronous** programming language, hence, the implementation of these type of web servers is very difficult as there will be always blocking processes for each request, hence, this server/framework is non-scalable.\n\n##### Table of contents 📖\n\n1. [Requirements](#requirements)\n2. [Installation](#installation)\n3. [Usage](#usage)\n\n- [3.1 Creating a basic web server](#server)\n- [3.2 Serving a basic static HTML Page](#html)\n- [3.3 Create a POST request](#post)\n- [3.4 Complete Rest API CRUD example](#crud)\n- [3.5 Defining middlewares](#middlewares)\n- [3.6 Asynchronous programming](#async)\n\n\u003ca name=\"requirements\"\u003e\u003c/a\u003e\n\n## Requirements\n\n1. **PHP 8.0.0 or major**\n2. **Have PHP sockets module installed and enabled**\n3. **Composer**\n4. **Have a initted Composer project**\n\n\u003ca name=\"installation\"\u003e\u003c/a\u003e\n\n## Installation\n\nInstall **PHP Espresso** via Composer:\n\n```bash\ncomposer require edgaralexanderfr/php-espresso\n```\n\n\u003ca name=\"usage\"\u003e\u003c/a\u003e\n\n## Usage\n\n\u003ca name=\"server\"\u003e\u003c/a\u003e\n\n### Creating a basic web server\n\nCreate a _**server.php**_ file inside your project with the following program:\n\n```php\n\u003c?php\n\nrequire_once 'vendor/autoload.php';\n\nuse Espresso\\Http\\Request;\nuse Espresso\\Http\\Response;\nuse Espresso\\Http\\Router;\nuse Espresso\\Http\\Server;\n\nconst PORT = 80;\n\n$server = new Server();\n$router = new Router();\n\n$router-\u003eget('/', function (Request $request, Response $response) {\n    return $response-\u003esend([\n        'message' =\u003e 'Hello world!',\n        'code' =\u003e 200,\n    ]);\n});\n\n$server-\u003euse($router);\n\n$server-\u003elisten(PORT, function () use ($server) {\n    $server-\u003elog('Listening at port ' . PORT . '...');\n});\n```\n\nRun the server:\n\n```bash\nphp server.php # Use sudo if necessary for port 80\n```\n\nVisit http://localhost or execute:\n\n```bash\ncurl http://localhost\n```\n\nAnd voila! 🎉\n\n\u003ca name=\"html\"\u003e\u003c/a\u003e\n\n### Serving a basic static HTML Page\n\n```php\n\u003c?php\n\nrequire_once 'vendor/autoload.php';\n\nuse Espresso\\Http\\Request;\nuse Espresso\\Http\\Response;\nuse Espresso\\Http\\Router;\nuse Espresso\\Http\\Server;\n\n$server = new Server();\n$router = new Router();\n\n$router-\u003eget('/php-espresso-page', function (Request $request, Response $response) {\n    return $response-\u003esetPayload(\n        \u003c\u003c\u003cHTML\n            \u003c!DOCTYPE html\u003e\n            \u003chtml lang=\"en\"\u003e\n                \u003chead\u003e\n                    \u003ctitle\u003eMy Web Page with PHP Espresso!\u003c/title\u003e\n                \u003c/head\u003e\n                \u003cbody\u003e\n                    \u003ch1\u003eMy Web Page with PHP Espresso!\u003c/h1\u003e\n\n                    \u003cp\u003eThis page was served using PHP Espresso.\u003c/p\u003e\n                \u003c/body\u003e\n            \u003c/html\u003e\n        HTML\n    );\n});\n\n$server-\u003euse($router);\n\n$server-\u003elisten(80, function () use ($server) {\n    $server-\u003elog('Listening at port 80...');\n});\n```\n\nVisit http://localhost/php-espresso-page in your browser.\n\n\u003ca name=\"post\"\u003e\u003c/a\u003e\n\n### Create a POST request:\n\n```php\n\u003c?php\n\nrequire_once 'vendor/autoload.php';\n\nuse Espresso\\Http\\Request;\nuse Espresso\\Http\\Response;\nuse Espresso\\Http\\Router;\nuse Espresso\\Http\\Server;\n\n$server = new Server();\n$router = new Router();\n\n$router-\u003epost('/users', function (Request $request, Response $response) {\n    $body_json = $request-\u003egetJSON();\n\n    return $response-\u003esend([\n        'message' =\u003e 'User created successfully',\n        'code' =\u003e 201,\n        'user' =\u003e $body_json,\n    ], 201);\n});\n\n$server-\u003euse($router);\n\n$server-\u003elisten(80, function () use ($server) {\n    $server-\u003elog('Listening at port 80...');\n});\n```\n\nExecute a POST request:\n\n```bash\ncurl -X POST http://localhost/users -d '{\"name\":\"Alexander The Great\"}'\n```\n\n\u003ca name=\"crud\"\u003e\u003c/a\u003e\n\n### Complete Rest API CRUD example:\n\n```php\n\u003c?php\n\nrequire_once 'vendor/autoload.php';\n\nuse Espresso\\Http\\Request;\nuse Espresso\\Http\\Response;\nuse Espresso\\Http\\Router;\nuse Espresso\\Http\\Server;\n\n/** @var stdClass[] */\n$users = [];\n/** @var int */\n$users_id = 1;\n\n$server = new Server();\n$router = new Router();\n\n$router-\u003eget('/users', function (Request $request, Response $response) use (\u0026$users) {\n    return $response-\u003esend($users);\n});\n\n$router-\u003eget('/users/:id', function (Request $request, Response $response) use (\u0026$users) {\n    $id = $request-\u003egetId();\n\n    foreach ($users as $user) {\n        if (isset($user-\u003e{'id'}) \u0026\u0026 $user-\u003eid == $id) {\n            return $response-\u003esend($user);\n        }\n    }\n\n    return $response-\u003esend([\n        'message' =\u003e 'User not found',\n        'code' =\u003e 404,\n    ], 404);\n});\n\n$router-\u003epost('/users', function (Request $request, Response $response) use (\u0026$users, \u0026$users_id) {\n    $body = $request-\u003egetJSON();\n\n    $email = $body-\u003eemail ?? null;\n    $name = $body-\u003ename ?? null;\n\n    if (!$email || !$name) {\n        return $response-\u003esend([\n            'message' =\u003e 'Email and Name are required',\n            'code' =\u003e 400,\n        ], 400);\n    }\n\n    $user = (object) [\n        'id' =\u003e $users_id++,\n        'email' =\u003e $email,\n        'name' =\u003e $name,\n    ];\n\n    $users[] = $user;\n\n    return $response-\u003esend([\n        'message' =\u003e 'User created successfully',\n        'code' =\u003e 201,\n        'user' =\u003e $user,\n    ], 201);\n});\n\n$router-\u003epatch('/users/:id', function (Request $request, Response $response) use (\u0026$users) {\n    $id = $request-\u003egetId();\n    $body = $request-\u003egetJSON();\n\n    foreach ($users as \u0026$user) {\n        if (isset($user-\u003e{'id'}) \u0026\u0026 $user-\u003eid == $id) {\n            $user-\u003eemail = $body-\u003eemail ?? $user-\u003eemail;\n            $user-\u003ename = $body-\u003ename ?? $user-\u003ename;\n\n            return $response-\u003esend([\n                'message' =\u003e 'User updated successfully',\n                'code' =\u003e 200,\n                'user' =\u003e $user,\n            ]);\n        }\n    }\n\n    return $response-\u003esend([\n        'message' =\u003e 'User not found',\n        'code' =\u003e 404,\n    ], 404);\n});\n\n$router-\u003edelete('/users/:id', function (Request $request, Response $response) use (\u0026$users) {\n    $id = $request-\u003egetId();\n\n    foreach ($users as $i =\u003e \u0026$user) {\n        if (isset($user-\u003e{'id'}) \u0026\u0026 $user-\u003eid == $id) {\n            array_splice($users, $i, 1);\n\n            return $response-\u003esend([\n                'message' =\u003e 'User deleted successfully',\n                'code' =\u003e 200,\n            ]);\n        }\n    }\n\n    return $response-\u003esend([\n        'message' =\u003e 'User not found',\n        'code' =\u003e 404,\n    ], 404);\n});\n\n$server-\u003euse($router);\n\n$server-\u003elisten(80, function () use ($server) {\n    $server-\u003elog('Listening at port 80...');\n});\n```\n\nCreate a couple of users:\n\n```bash\ncurl -X POST http://localhost/users -d '{\"email\":\"john.doe@example.com\",\"name\":\"John Doe\"}'\ncurl -X POST http://localhost/users -d '{\"email\":\"jane.doe@example.com\",\"name\":\"Jane Doe\"}'\n```\n\nRetrieve all created users:\n\n```bash\ncurl http://localhost/users\n```\n\nRetrieve user with `id` 2:\n\n```bash\ncurl http://localhost/users/2\n```\n\nUpdate user with `id` 1:\n\n```bash\ncurl -X PATCH http://localhost/users/1 -d '{\"name\":\"John James Doe\"}'\n```\n\nDelete user with `id` 2:\n\n```bash\ncurl -X DELETE http://localhost/users/2\n```\n\n\u003ca name=\"middlewares\"\u003e\u003c/a\u003e\n\n### Defining middlewares\n\n**PHP Espresso** supports global and route middlewares. You can assign as much middlewares to a single route as you want.\n\nTo do so, you can create a new _middlewares.php_ file and add the following code:\n\n```php\n\u003c?php\n\nrequire_once 'vendor/autoload.php';\n\nuse Espresso\\Http\\Request;\nuse Espresso\\Http\\Response;\nuse Espresso\\Http\\Router;\nuse Espresso\\Http\\Server;\n\ndefine('AUTH_CREDENTIALS', (object) [\n    'user' =\u003e 'john.doe@example.com',\n    'pass' =\u003e '1234567890', // Please... don't...\n]);\n\n/**\n * Middleware for admin authentication.\n */\nfunction auth(Request $request, Response $response, callable $next)\n{\n    $authorization = $request-\u003egetHeader('Authorization') ?? '';\n    $auth = explode(' ', $authorization);\n    $type = $auth[0] ?? '';\n    $token = $auth[1] ?? '';\n\n    $credentials = explode(':', base64_decode($token));\n    $user = $credentials[0] ?? null;\n    $pass = $credentials[1] ?? null;\n\n    if ($type != 'Bearer' || $user != AUTH_CREDENTIALS-\u003euser || $pass != AUTH_CREDENTIALS-\u003epass) {\n        return $response-\u003esend([\n            'message' =\u003e Espresso\\Http\\CODES[401],\n            'code' =\u003e 401,\n        ], 401);\n    }\n\n    $next();\n}\n\n/** @var stdClass[] */\n$users = [];\n/** @var int */\n$users_id = 1;\n\n$server = new Server();\n$router = new Router();\n\n// Global middleware to check service status:\n$server-\u003euse(function (Request $request, Response $response, callable $next) use ($argv) {\n    $status = $argv[1] ?? '';\n\n    if ($status == 'service-closed') {\n        return $response-\u003esend([\n            'message' =\u003e 'Service unavailable temporary due to maintenance',\n            'code' =\u003e 503,\n        ], 503);\n    }\n\n    $next();\n});\n\n$router-\u003eget('/users', function (Request $request, Response $response) use (\u0026$users) {\n    return $response-\u003esend($users);\n});\n\n$router-\u003epost('/users', 'auth', function (Request $request, Response $response) use (\u0026$users, \u0026$users_id) {\n    $body = $request-\u003egetJSON();\n\n    $email = $body-\u003eemail ?? null;\n    $name = $body-\u003ename ?? null;\n\n    if (!$email || !$name) {\n        return $response-\u003esend([\n            'message' =\u003e 'Email and Name are required',\n            'code' =\u003e 400,\n        ], 400);\n    }\n\n    $user = (object) [\n        'id' =\u003e $users_id++,\n        'email' =\u003e $email,\n        'name' =\u003e $name,\n    ];\n\n    $users[] = $user;\n\n    return $response-\u003esend([\n        'message' =\u003e 'User created successfully',\n        'code' =\u003e 201,\n        'user' =\u003e $user,\n    ], 201);\n});\n\n$server-\u003euse($router);\n\n$server-\u003elisten(80, function () use ($server) {\n    $server-\u003elog('Listening at port 80...');\n});\n```\n\nIf you run:\n\n```bash\nphp middlewares.php service-closed\n```\n\nAnd do:\n\n```bash\ncurl http://localhost/users\n```\n\nOr:\n\n```bash\ncurl -X POST http://localhost/users -d '{\"email\":\"john.doe@example.com\",\"name\":\"John Doe\"}'\n```\n\nYou will get the following message:\n\n```bash\n{\"message\":\"Service unavailable temporary due to maintenance\",\"code\":503}\n```\n\nIf you kill the previous server with \u003ckbd\u003eCTRL\u003c/kbd\u003e+\u003ckbd\u003eC\u003c/kbd\u003e and then run:\n\n```bash\nphp middlewares.php\n```\n\nYou will be able to retrieve the users list now, e.g:\n\n```bash\ncurl http://localhost/users\n```\n\nTo create a new user you need to be authenticated, to do so, assign an encoded **Bearer Token** using `base64` to a variable and then pass the `Authorization Header` to `curl` command:\n\n```bash\nAUTH_TOKEN=$(echo 'john.doe@example.com:1234567890' | base64)\ncurl -X POST http://localhost/users -d '{\"email\":\"john.doe@example.com\",\"name\":\"John Doe\"}' -H \"Authorization: Bearer ${AUTH_TOKEN}\"\n```\n\n\u003ca name=\"async\"\u003e\u003c/a\u003e\n\n### Asynchronous programming\n\nIt's still possible to do asynchronous programming with **PHP Espresso** by creating an asynchronous server and using the `async` and `$next` functions and callables:\n\n```php\n\u003c?php\n\nrequire_once 'vendor/autoload.php';\n\nuse function Espresso\\Event\\async;\nuse Espresso\\Http\\Request;\nuse Espresso\\Http\\Response;\nuse Espresso\\Http\\Router;\nuse Espresso\\Http\\Server;\n\nconst SMALLER_FILE_PATH = __DIR__ . '/files/smaller-file.txt';\nconst BIGGER_FILE_PATH = __DIR__ . '/files/bigger-file.txt';\n\nfunction read_file(string $path, int $bytes, callable $callable = null): void\n{\n    $file = fopen($path, 'r');\n    $file_size = filesize($path);\n    $content = '';\n    $read_bytes = 0;\n\n    async(function () use ($bytes, $callable, \u0026$file, $file_size, \u0026$content, \u0026$read_bytes) {\n        if ($read_bytes \u003c $file_size) {\n            $chunk_size = min($file_size - $read_bytes, $bytes);\n            $chunk = fread($file, $chunk_size);\n            $content .= $chunk;\n            $read_bytes += $chunk_size;\n\n            return false;\n        }\n\n        if ($callable) {\n            $callable($content);\n        }\n    });\n}\n\n$server = new Server();\n$router = new Router();\n\n$router-\u003eget('/read-file', function (Request $request, Response $response, callable $next) {\n    $size = $request-\u003egetParam('size');\n\n    $file_path = $size == 'big' ? BIGGER_FILE_PATH : SMALLER_FILE_PATH;\n\n    read_file($file_path, 8, function (string $content) use ($request, $response, $next, $size) {\n        $response-\u003esend([\n            'file_content' =\u003e $content,\n            'size' =\u003e $size,\n        ]);\n\n        $next();\n    });\n});\n\n$server-\u003euse($router);\n$server-\u003easync(true);\n\n$server-\u003elisten(80, function () use ($server) {\n    $server-\u003elog('Listening at port 80...');\n});\n```\n\nThe `async` function initiates an **Event Looper** inside of the `listen` method when running in **async mode** by setting `$server-\u003easync(true);`.\n\n`async` may return a boolean value (**false**) when the _async call_ is not done yet and returns **true** or **nothing** when it's finished.\n\nIn this example, the _async call_ inside of the `read_file` function will return **false** as long as the requested file is not completed yet, this by reading `$bytes` as a step for each chunk read through every call inside the **Event Loop** as an asynchronous process.\n\nOnce the whole file is read, the `$callable` callback will be called, passing in the content of the file on the _async call_ by returning nothing at the very end of the function.\n\nIf you execute:\n\n```bash\ncurl 'http://localhost/read-file?size=big'\\\n\u0026 curl 'http://localhost/read-file?size=small'\\\n\u0026 wait\n```\n\nThe smaller file request will respond earlier than the larger file request despite of being executed right at the same time.\n\nThis could be a way to implement asynchronous programs and libraries for streaming, networking, databases, files, I/O operations, etc, although it's not perfect, it would require a vast work to implement lots of **PHP** libraries that were designed initially to be **Single-Threaded** and **Synchronous**.\n\nMaybe the future of **PHP** is promising for this purpose with the introduction of tools like `Fibers` and stuff, but yet, we will see how it goes. 🙂🐘\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgaralexanderfr%2Fphp-espresso","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedgaralexanderfr%2Fphp-espresso","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgaralexanderfr%2Fphp-espresso/lists"}