Skip to content

Example: hello world ​

One endpoint, GET /api/v1/hello?name=Ada, answering {"message":"Hello Ada!"}.

Open the diagram at full size

The handler ​

php
<?php

declare(strict_types=1);

namespace App\Api;

use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Params\GetInputParam;
use Tomaj\NetteApi\Response\JsonApiResponse;
use Tomaj\NetteApi\Response\ResponseInterface;

final class HelloHandler extends BaseHandler
{
    public function summary(): string
    {
        return 'Say hello';
    }

    public function params(): array
    {
        return [
            (new GetInputParam('name'))->setDefault('world')->setDescription('Who to greet'),
        ];
    }

    public function handle(array $params): ResponseInterface
    {
        return new JsonApiResponse(200, ['message' => 'Hello ' . $params['name'] . '!']);
    }
}

name is optional with the default world, so GET /api/v1/hello answers {"message":"Hello world!"}.

The route ​

php
<?php

declare(strict_types=1);

namespace App;

use Nette\Application\Routers\RouteList;

final class RouterFactory
{
    public static function create(): RouteList
    {
        $router = new RouteList();
        // every API call goes to the presenter that ships with nette-api
        $router->addRoute('/api/v<version>/<package>[/<apiAction>][/<params>]', 'Api:Api:default');
        return $router;
    }
}

The services ​

yaml
application:
	mapping:
		# Api:Api:default is Tomaj\NetteApi\Presenters\ApiPresenter
		Api: Tomaj\NetteApi\Presenters\*Presenter

services:
	router: App\RouterFactory::create

	# Nette 3.3 creates presenters only from the DI container
	apiPresenter:
		factory: Tomaj\NetteApi\Presenters\ApiPresenter

	# required by ApiPresenter: when to validate output schemas and show error details
	- Tomaj\NetteApi\Output\Configurator\DebuggerConfigurator
	# required by ApiPresenter: turns errors into JSON responses
	- Tomaj\NetteApi\Error\DefaultErrorHandler

	apiDecider:
		factory: Tomaj\NetteApi\ApiDecider
		setup:
			# GET /api/v1/hello
			- addApi(
				Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'hello'),
				App\Api\HelloHandler(),
				Tomaj\NetteApi\Authorization\NoAuthorization()
			)

The test ​

bash
#!/usr/bin/env bash
# End-to-end check: start the app with the PHP built-in server and call the endpoint with curl.
set -euo pipefail
cd "$(dirname "$0")"
[[ -f vendor/autoload.php ]] || { echo "fail: vendor/ is missing, run composer install first" >&2; exit 1; }
source ../ci/lib.sh
serve 8081

request "$BASE/api/v1/hello"
expect_status 200 "GET /api/v1/hello"
expect_body '{"message":"Hello world!"}' "default name"
ok "GET /api/v1/hello says hello to the world"

request "$BASE/api/v1/hello?name=Nette"
expect_body '{"message":"Hello Nette!"}' "name from the query"
ok "GET /api/v1/hello?name=Nette uses the parameter"

request "$BASE/api/v1/nothing"
expect_status 404 "unknown endpoint"
expect_body '"Unknown api endpoint"' "unknown endpoint"
ok "an unknown endpoint answers 404"

request -X POST "$BASE/api/v1/hello"
expect_status 404 "wrong method"
ok "POST to a GET endpoint answers 404"

FAQ ​

Why does POST /api/v1/hello answer 404? ​

The endpoint is registered for GET only. The method is part of the endpoint, see Handlers.

Why DebuggerConfigurator? ​

It shows error details and validates outputs when Tracy is in development mode. See Outputs.

Where does the JSON come from? ​

JsonApiResponse encodes the array and sets Content-Type: application/json. See Outputs and responses.

Can I add a second endpoint? ​

Add another addApi() line with a new EndpointIdentifier. The params example has two.

What does each step of the setup do? ​

Getting started explains the same files step by step.

Released under the MIT License.