Documentation

Requests and responses

Build a JSON endpoint, validate input, and check denied requests.

Build an application with a named route and a JSON endpoint. Start with the Composer application in the README. Run all commands from your application directory.

Add routes and validate input

Replace public/index.php with:

<?php
declare(strict_types=1);

use Stage\Http\Application;
use Stage\Http\HttpError;
use Stage\Http\Request;
use Stage\Http\Response;
use Stage\Http\Route;

require dirname(__DIR__) . '/vendor/autoload.php';

(new Application(
	Route::get('/', fn () => Response::json(['hello' => 'world'])),
	Route::get('/hello/{name}', fn (Request $request) => Response::json([
		'hello' => $request->parameters['name'],
	])),
	new Route('POST', '/hello', function (Request $request): Response {
		$data = $request->json();
		if (!is_array($data) || !is_string($data['name'] ?? null) || trim($data['name']) === '') {
			throw new HttpError(422);
		}

		return Response::json(['hello' => trim($data['name'])]);
	}),
))->run();

Route::get creates a GET route. Use new Route for other methods. The named route exposes the decoded name segment in Request::parameters. The POST handler reads JSON and accepts a nonempty string in name.

Start the development server:

php -S 127.0.0.1:8080 -t public public/index.php

Check successful requests

In a second terminal, call the named route:

curl -i http://127.0.0.1:8080/hello/World

Expect status 200, a JSON content type, and {"hello":"World"}. Try /hello/Ada%20Lovelace; the response contains "Ada Lovelace".

Send a JSON request:

curl -i http://127.0.0.1:8080/hello \
	-H 'Content-Type: application/json' \
	-d '{"name":"Ada"}'

Expect status 200 and {"hello":"Ada"}.

Check rejected input

Send malformed JSON:

curl -i http://127.0.0.1:8080/hello \
	-H 'Content-Type: application/json' \
	-d '{'

Expect status 400 and {"error":400}. Request::json() rejects malformed JSON.

Send valid JSON with an invalid name:

curl -i http://127.0.0.1:8080/hello \
	-H 'Content-Type: application/json' \
	-d '{"name":42}'

Expect status 422 and {"error":422}. The handler rejects the value's shape. An empty or whitespace-only name also returns 422.

Call the POST endpoint with GET:

curl -i http://127.0.0.1:8080/hello

Expect status 405 with Allow: OPTIONS, POST. An unknown path returns 404. Press Ctrl+C in the server terminal when you finish.

Check a response without a server

Create check.php in your application directory:

<?php
declare(strict_types=1);

use Stage\Http\Application;
use Stage\Http\Request;
use Stage\Http\Response;
use Stage\Http\Route;

require __DIR__ . '/vendor/autoload.php';

$app = new Application(
	Route::get('/', fn () => Response::json(['hello' => 'world'])),
);
$response = $app->handle(new Request('GET', '/'));

if ($response->status !== 200 || $response->body !== '{"hello":"world"}') {
	throw new RuntimeException('Unexpected response.');
}

echo "Response checked", PHP_EOL;

Run it:

php check.php

Expect Response checked. handle returns a response without writing headers or output. Use the same method in your application's tests.

Move rules into a feature

For an operation shared by HTTP, a command, or an agent adapter, keep its rules and permission checks in a PHP object. Continue with Compose features to build and call one in this application. Use the HTTP reference to look up routing, messages, and body limits.

Version and source

This guide targets Stage 0.1.4. Read the versioned source.