Documentation Index
Fetch the complete documentation index at: https://docs.apivalk.com/llms.txt
Use this file to discover all available pages before exploring further.
The pieces
ApivalkConfiguration is the single object that holds the runtime wiring. Apivalk (the entry point) takes one and does nothing else — so everything you do to customise the framework happens on the configuration.
A full constructor looks like this:
use apivalk\apivalk\ApivalkConfiguration;
new ApivalkConfiguration(
$router, // AbstractRouter (required)
$renderer, // ?RendererInterface — defaults to JsonRenderer
$exceptionHandler, // ?callable — defaults to none
$container, // ?Psr\Container\ContainerInterface
$logger, // ?Psr\Log\LoggerInterface — defaults to NullLogger
$localizationConfiguration // ?LocalizationConfiguration — defaults to English
);
Minimal bootstrap
The smallest working index.php:
<?php
declare(strict_types=1);
use apivalk\apivalk\Apivalk;
use apivalk\apivalk\ApivalkConfiguration;
use apivalk\apivalk\ApivalkExceptionHandler;
use apivalk\apivalk\Cache\FilesystemCache;
use apivalk\apivalk\Middleware\RequestValidationMiddleware;
use apivalk\apivalk\Middleware\SanitizeMiddleware;
use apivalk\apivalk\Router\Router;
use apivalk\apivalk\Util\ClassLocator;
require __DIR__ . '/vendor/autoload.php';
// 1. Discover controllers and cache the route index
$classLocator = new ClassLocator(__DIR__ . '/src/Http/Controller', 'App\\Http\\Controller');
$routerCache = new FilesystemCache(__DIR__ . '/var/cache/apivalk');
$router = new Router($classLocator, $routerCache);
// 2. Build the configuration
$configuration = new ApivalkConfiguration(
$router,
null, // default JsonRenderer
[ApivalkExceptionHandler::class, 'handle'] // default exception handler
);
// 3. Register middlewares — order matters (see below)
$configuration->getMiddlewareStack()->add(new SanitizeMiddleware());
$configuration->getMiddlewareStack()->add(new RequestValidationMiddleware());
// 4. Run
$apivalk = new Apivalk($configuration);
$response = $apivalk->run();
$apivalk->getRenderer()->render($response);
That’s a complete, public-only API. Hitting a configured GET /health route goes through Sanitize → Validate → Controller and returns JSON.
Adding authentication
AuthenticationMiddleware populates the identity; SecurityMiddleware enforces it. Always add them in that order:
use apivalk\apivalk\Middleware\AuthenticationMiddleware;
use apivalk\apivalk\Middleware\SecurityMiddleware;
use apivalk\apivalk\Security\Authenticator\JwtAuthenticator;
$authenticator = new JwtAuthenticator(
'https://your-tenant.auth0.com/.well-known/jwks.json',
$routerCache, // reuse any CacheInterface
'https://your-tenant.auth0.com/',
'https://api.example.com'
);
$configuration->getMiddlewareStack()->add(new AuthenticationMiddleware($authenticator));
$configuration->getMiddlewareStack()->add(new SecurityMiddleware());
See how to authenticate with JWT and how to authenticate with a custom API key.
Adding a PSR-11 container
If you pass a container, Apivalk uses it to build controllers — so constructor injection works:
$configuration = new ApivalkConfiguration(
$router,
null,
[ApivalkExceptionHandler::class, 'handle'],
$container // any PSR-11 container (PHP-DI, Symfony DI, Laravel, ...)
);
Without a container the controller factory falls back to new $controllerClass(), so constructor-less controllers still work.
Adding a logger
Apivalk accepts any PSR-3 logger and exposes it via $apivalk->getLogger():
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('apivalk');
$logger->pushHandler(new StreamHandler(__DIR__ . '/var/log/app.log'));
$configuration = new ApivalkConfiguration(
$router,
null,
null,
$container,
$logger
);
Localization
See the full Localization reference. The short version:
use apivalk\apivalk\Http\i18n\Locale;
use apivalk\apivalk\Http\i18n\LocalizationConfiguration;
$localization = new LocalizationConfiguration(Locale::en());
$localization->addSupportedLocale(Locale::en());
$localization->addSupportedLocale(Locale::de());
$localization->addSupportedLocale(Locale::deDe());
$configuration = new ApivalkConfiguration(
$router,
null,
null,
$container,
$logger,
$localization
);
If you pass nothing, Apivalk defaults to Locale::en().
Middleware order (LIFO onion)
MiddlewareStack::handle() reverses the list, so the last middleware you add() is the innermost layer (closest to the controller). That means:
SanitizeMiddleware and RequestValidationMiddleware run as close to the controller as possible → add them last.
AuthenticationMiddleware must run before SecurityMiddleware (authentication before authorization) → add Authentication first, Security second.
RateLimitMiddleware usually runs before anything expensive → add early.
The recommended order is:
$stack = $configuration->getMiddlewareStack();
$stack->add(new AuthenticationMiddleware($authenticator));
$stack->add(new SecurityMiddleware());
$stack->add(new RateLimitMiddleware($rateLimiter));
$stack->add(new SanitizeMiddleware());
$stack->add(new RequestValidationMiddleware());
How to extend your configuration
A raw index.php with every middleware listed inline is fine for a small service but gets noisy fast. Grow into one of these patterns:
1. Move bootstrapping into an application class
Wrap everything the request-scoped entry point needs in a factory:
namespace App\Bootstrap;
final class ApivalkBootstrap
{
public static function create(string $rootDir): Apivalk
{
$router = self::buildRouter($rootDir);
$container = self::buildContainer($rootDir);
$configuration = new ApivalkConfiguration(
$router,
null,
[ApivalkExceptionHandler::class, 'handle'],
$container,
self::buildLogger($rootDir),
self::buildLocalization()
);
self::registerMiddlewares($configuration, $container);
return new Apivalk($configuration);
}
// private static build... methods per concern
}
Your public/index.php then shrinks to:
$apivalk = App\Bootstrap\ApivalkBootstrap::create(__DIR__ . '/..');
$response = $apivalk->run();
$apivalk->getRenderer()->render($response);
Tests, CLI tools, and the OpenAPI generator script can reuse the same factory so routing/middleware definitions never drift.
2. Register middlewares through the container
Pull middlewares out of the bootstrap file and into the container as first-class services. That lets them take dependencies (database, PSR-3 logger, cache, feature flags) without you constructing them by hand:
// In your container definitions:
AuthenticationMiddleware::class => function ($c) {
return new AuthenticationMiddleware($c->get(AuthenticatorInterface::class));
},
And in the bootstrap:
foreach ([
AuthenticationMiddleware::class,
SecurityMiddleware::class,
SanitizeMiddleware::class,
RequestValidationMiddleware::class,
] as $id) {
$configuration->getMiddlewareStack()->add($container->get($id));
}
This is also how you add your own middlewares — see Write a custom middleware.
3. Swap the renderer or exception handler per environment
JsonRenderer and ApivalkExceptionHandler::handle are defaults, not requirements. Both are constructor arguments:
$renderer = $isDebug ? new DebugJsonRenderer() : new JsonRenderer();
$exHandler = $isDebug ? [DebugExceptionHandler::class, 'handle'] : [ApivalkExceptionHandler::class, 'handle'];
$configuration = new ApivalkConfiguration($router, $renderer, $exHandler, $container, $logger, $localization);
Ship your own RendererInterface implementation when you need non-JSON output or custom envelopes; ship your own exception handler when you need to integrate with Sentry / Datadog / Bugsnag.
4. Keep framework bridges thin
If you’re running inside Laravel or Symfony, use the framework bridges rather than re-implementing wiring. The bridges do the ApivalkBootstrap work and hand you an Apivalk instance tuned for that framework’s lifecycle.