Building an API¶
Erlenmeyer makes it simple to build clean, lightweight, and fully structured APIs
— with routes, middleware, error handling, and JSON responses — all in plain PHP.
This guide walks you through creating a basic RESTful API step by step.
🧩 Project Setup¶
Create a new project folder:
mkdir my-api
cd my-api
Install Erlenmeyer via Composer:
composer require adaiasmagdiel/erlenmeyer
Create the main entry point:
my-api/
└── public/
└── index.php
⚙️ Basic Application¶
Start by creating a minimal API that returns a JSON response.
<?php
require __DIR__ . '/../vendor/autoload.php';
use AdaiasMagdiel\Erlenmeyer\App;
use AdaiasMagdiel\Erlenmeyer\Request;
use AdaiasMagdiel\Erlenmeyer\Response;
$app = new App();
$app->get('/hello', function (Request $req, Response $res, stdClass $params): Response {
return $res->withJson(['message' => 'Hello, API!']);
});
$app->run();
Now run the built-in PHP server:
php -S localhost:8000 -t public
Visit: 👉 http://localhost:8000/hello
You should see:
{ "message": "Hello, API!" }
🧱 Defining Routes¶
Erlenmeyer supports all common HTTP verbs:
$app->get('/users', $handler);
$app->post('/users', $handler);
$app->put('/users/[id]', $handler);
$app->delete('/users/[id]', $handler);
Example¶
use AdaiasMagdiel\Erlenmeyer\Request;
use AdaiasMagdiel\Erlenmeyer\Response;
$users = [
['id' => 1, 'name' => 'Alice'],
['id' => 2, 'name' => 'Bob'],
];
$app->get('/users', function (Request $req, Response $res, stdClass $params): Response {
global $users;
return $res->withJson($users);
});
$app->get('/users/[id]', function (Request $req, Response $res, stdClass $params): Response {
global $users;
$id = (int) $params->id;
$user = array_values(array_filter($users, fn($u) => $u['id'] === $id))[0] ?? null;
if (!$user) {
return $res->withJson(['error' => 'User not found'])
->setStatusCode(404);
}
return $res->withJson($user);
});
🧩 Handling JSON Requests¶
To read JSON input from the client:
use AdaiasMagdiel\Erlenmeyer\Request;
use AdaiasMagdiel\Erlenmeyer\Response;
$app->post('/users', function (Request $req, Response $res, stdClass $params): Response {
$data = $req->getJson(true);
if (!$data || empty($data['name'])) {
return $res->withJson(['error' => 'Invalid input'])
->setStatusCode(400);
}
return $res->withJson([
'message' => 'User created successfully',
'user' => $data,
])->setStatusCode(201);
});
🧠 Using Middleware¶
Middleware can handle cross-cutting concerns such as authentication or logging.
use AdaiasMagdiel\Erlenmeyer\Request;
use AdaiasMagdiel\Erlenmeyer\Response;
$app->addMiddleware(function (Request $req, Response $res, callable $next, stdClass $params): void {
$token = $req->getHeader('Authorization');
if ($token !== 'Bearer secret123') {
$res->withJson(['error' => 'Unauthorized'])
->setStatusCode(401)
->send();
return;
}
$next($req, $res, $params);
});
This will run before every route — you can also apply it to specific routes if needed.
🧰 Error Handling¶
Erlenmeyer lets you define custom error handlers for exceptions or HTTP 404s.
use Throwable;
$app->set404Handler(function (Request $req, Response $res) {
$res->withJson(['error' => 'Endpoint not found'])
->setStatusCode(404)
->send();
});
$app->setExceptionHandler(Throwable::class, function (Request $req, Response $res, Throwable $e) {
$res->withJson([
'error' => 'Server error',
'message' => $e->getMessage(),
])->setStatusCode(500)->send();
});
🧪 Testing the API¶
You can use ErlenClient to test your API routes programmatically.
use AdaiasMagdiel\Erlenmeyer\ErlenClient;
$client = new ErlenClient($app);
$response = $client->get('/users');
echo $response->getBody();
This simulates HTTP requests without running a web server — perfect for automated testing.
🧩 Full Example¶
Here’s a small but complete API that supports listing, adding, and fetching users:
<?php
require __DIR__ . '/../vendor/autoload.php';
use AdaiasMagdiel\Erlenmeyer\App;
use AdaiasMagdiel\Erlenmeyer\Request;
use AdaiasMagdiel\Erlenmeyer\Response;
$app = new App();
$users = [
['id' => 1, 'name' => 'Alice'],
['id' => 2, 'name' => 'Bob'],
];
$app->get('/users', function (Request $req, Response $res, stdClass $params): Response {
global $users;
return $res->withJson($users);
});
$app->get('/users/[id]', function (Request $req, Response $res, stdClass $params): Response {
global $users;
$id = (int) $params->id;
$user = array_values(array_filter($users, fn($u) => $u['id'] === $id))[0] ?? null;
if (!$user) {
return $res->withJson(['error' => 'User not found'])
->setStatusCode(404);
}
return $res->withJson($user);
});
$app->post('/users', function (Request $req, Response $res, stdClass $params): Response {
global $users;
$data = $req->getJson();
$id = count($users) + 1;
$user = ['id' => $id, 'name' => $data['name']];
$users[] = $user;
return $res->withJson([
'message' => 'User added',
'user' => $user,
])->setStatusCode(201);
});
$app->run();
Run it, then test with:
curl http://localhost:8000/users
curl http://localhost:8000/users/1
curl -X POST -H "Content-Type: application/json" \
-d '{"name": "Charlie"}' http://localhost:8000/users
🚀 Summary¶
| Concept | Description |
|---|---|
| Routes | Define endpoints for GET, POST, PUT, DELETE |
| JSON | Use withJson() for structured responses |
| Requests | Parse body with $req->getJson() |
| Middleware | Handle authentication, logging, etc. |
| Errors | Customize 404 and exception handlers |
| Testing | Use ErlenClient to simulate API calls |
Keep it modular
For larger APIs, organize your routes into separate files or controllers — Erlenmeyer is flexible enough to scale cleanly without adding complexity.