skedli / http-middleware
Provides PSR-15 middleware for HTTP requests, including correlation ID propagation, structured request/response logging, and error handling.
Installs: 7
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/skedli/http-middleware
Requires
- php: ^8.5
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- ramsey/uuid: ^4.7
- tiny-blocks/http: ^4.8
- tiny-blocks/logger: ^1.1
Requires (Dev)
- guzzlehttp/psr7: ^2.7
- infection/infection: ^0.32
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
- squizlabs/php_codesniffer: ^4.0
This package is not auto-updated.
Last update: 2026-02-25 05:00:58 UTC
README
Overview
Provides PSR-15 middleware for HTTP requests, including correlation ID propagation, structured request/response logging, and error handling.
Built on top of PSR-15 and PSR-7, the
middleware can be used with any framework that supports the MiddlewareInterface standard.
Installation
composer require skedli/http-middleware
How to use
Correlation ID
The middleware reads the Correlation-Id header from the incoming request. If present and non-empty, it reuses the
value. Otherwise, it generates a new UUID v4. In both cases, the correlation ID is:
- Injected as a request attribute (
correlationId) for downstream handlers. - Added to the response as the
Correlation-Idheader.
Default usage
Create the middleware with CorrelationIdMiddleware::create() and register it in your application. The default provider
generates a UUID v4 when no Correlation-Id header is present.
use Skedli\HttpMiddleware\CorrelationIdMiddleware; $middleware = CorrelationIdMiddleware::create()->build();
In a Slim 4 application:
use Skedli\HttpMiddleware\CorrelationIdMiddleware; $app->add(CorrelationIdMiddleware::create()->build());
The correlation ID is accessible in any handler via the request attribute:
use Psr\Http\Message\ServerRequestInterface; use Skedli\HttpMiddleware\CorrelationId; $correlationId = $request->getAttribute('correlationId'); /** @var CorrelationId $correlationId */ $correlationId->toString(); # "550e8400-e29b-41d4-a716-446655440000"
Reusing an existing correlation ID
When the incoming request already contains the Correlation-Id header, the middleware preserves it instead of
generating a new one. This enables end-to-end traceability across service boundaries.
Client → BFF (generates Correlation-Id: req-abc-123)
→ Service X (reuses req-abc-123)
→ Service Y (reuses req-abc-123)
→ Service Z (reuses req-abc-123)
No additional configuration is needed. The middleware handles this automatically.
Custom provider
Implement the CorrelationIdProvider interface to replace the default UUID v4 generation strategy:
use Skedli\HttpMiddleware\CorrelationId; use Skedli\HttpMiddleware\CorrelationIdProvider; use Skedli\HttpMiddleware\Internal\CorrelationId\UuidCorrelationId; final readonly class PrefixedCorrelationIdProvider implements CorrelationIdProvider { public function __construct(private string $prefix) { } public function generate(): CorrelationId { return UuidCorrelationId::from(value: sprintf('%s-%s', $this->prefix, bin2hex(random_bytes(8)))); } }
Then configure the middleware with the custom provider:
use Skedli\HttpMiddleware\CorrelationIdMiddleware; $middleware = CorrelationIdMiddleware::create() ->withProvider(provider: new PrefixedCorrelationIdProvider(prefix: 'bff')) ->build();
Integration with tiny-blocks/logger
The middleware is designed to work seamlessly with tiny-blocks/logger. Extract the correlation ID from the request attribute and bind it to the logger context:
use Psr\Http\Message\ServerRequestInterface; use Skedli\HttpMiddleware\CorrelationId; use TinyBlocks\Logger\LogContext; use TinyBlocks\Logger\Logger; final readonly class CreateUserHandler { public function __construct(private Logger $logger) { } public function __invoke(ServerRequestInterface $request): void { /** @var CorrelationId $correlationId */ $correlationId = $request->getAttribute('correlationId'); $logger = $this->logger->withContext( context: LogContext::from(correlationId: $correlationId->toString()) ); $logger->info(message: 'user.creating', context: ['email' => 'john@example.com']); } }
Output:
2026-02-21T16:00:00+00:00 component=identity correlation_id=550e8400-e29b-41d4-a716-446655440000 level=INFO key=user.creating data={"email":"jo**@example.com"}
Request and response logging
The LogMiddleware provides structured logging for every HTTP request and response. It captures method, URI,
query parameters, body, status code, and duration automatically.
Default usage
Create the middleware with a tiny-blocks/logger instance:
use Skedli\HttpMiddleware\LogMiddleware; use TinyBlocks\Logger\Logger; $middleware = LogMiddleware::create(logger: $logger);
In a Slim 4 application:
use Skedli\HttpMiddleware\LogMiddleware; $app->add(LogMiddleware::create(logger: $logger));
What is logged
The middleware logs two entries per request cycle, one for the incoming request and one for the outgoing **response **.
Request is always logged at info level:
level=INFO key=request data={"method":"POST","uri":"/api/users","query_parameters":{"page":"1"},"body":{"name":"John","email":"john@example.com"}}
Response is logged at info for success (2xx/3xx) and error for failures (4xx/5xx):
level=INFO key=response data={"method":"POST","uri":"/api/users","status_code":201,"duration_ms":45.32,"body":{"id":"550e8400","name":"John"}}
level=ERROR key=response data={"method":"POST","uri":"/api/users","status_code":422,"duration_ms":12.87,"body":{"error":"Validation failed"}}
The query_parameters and body fields are only included when present. A GET request without query parameters
or body produces a minimal log:
level=INFO key=request data={"method":"GET","uri":"/api/health"}
level=INFO key=response data={"method":"GET","uri":"/api/health","status_code":200,"duration_ms":1.04}
Automatic correlation ID binding
When used together with the CorrelationIdMiddleware, the LogMiddleware automatically binds the correlation ID
to the logger context. No additional configuration is needed, just register both middleware in the correct order:
use Skedli\HttpMiddleware\CorrelationIdMiddleware; use Skedli\HttpMiddleware\LogMiddleware; // In Slim 4, middleware executes in LIFO order (last added = first to run). // CorrelationIdMiddleware must run before LogMiddleware. $app->add(LogMiddleware::create(logger: $logger)); $app->add(CorrelationIdMiddleware::create()->build());
The correlation ID is then automatically included in every log entry:
2026-02-24T10:00:00+00:00 component=identity correlation_id=550e8400-e29b-41d4-a716-446655440000 level=INFO key=request data={"method":"POST","uri":"/api/users","body":{"name":"John"}}
2026-02-24T10:00:00+00:00 component=identity correlation_id=550e8400-e29b-41d4-a716-446655440000 level=INFO key=response data={"method":"POST","uri":"/api/users","status_code":201,"duration_ms":45.32}
If the CorrelationIdMiddleware is not registered, the LogMiddleware works normally without the correlation ID.
Error handling
The ErrorMiddleware catches uncaught exceptions during request processing and transforms them into structured JSON
responses. Error logging and response detail exposure are fully configurable through ErrorHandlingSettings.
Default usage
Register the middleware to automatically catch exceptions. If the exception contains a valid HTTP error code (4xx or 5xx), it is used, otherwise, it defaults to 500 Internal Server Error.
By default, error details are not displayed in the response and errors are not logged.
use Skedli\HttpMiddleware\ErrorMiddleware; $middleware = ErrorMiddleware::create()->build();
Response body:
{
"error": "Unexpected database error"
}
Error handling settings
Use ErrorHandlingSettings to control how errors are displayed and logged:
| Setting | Default | Description |
|---|---|---|
displayErrorDetails |
false |
Includes exception class, file, line, and stack trace in the response body |
logErrors |
false |
Enables logging of exceptions when a logger is provided |
logErrorDetails |
false |
Includes exception class, file, line, and stack trace in the log context |
Development full visibility in response and logs:
use Skedli\HttpMiddleware\ErrorMiddleware; use Skedli\HttpMiddleware\Internal\Error\ErrorHandlingSettings; $middleware = ErrorMiddleware::create() ->withLogger(logger: $logger) ->withSettings(settings: ErrorHandlingSettings::from( logErrors: true, logErrorDetails: true, displayErrorDetails: true )) ->build();
Response body:
{
"error": "Unexpected database error",
"exception": "RuntimeException",
"file": "/app/src/Application/Handlers/UserCreatingHandler.php",
"line": 42,
"trace": [
"#0 /app/src/Driver/Http/Endpoints/User/CreateUser.php(25): ...",
"#1 /app/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php(30): ..."
]
}
Production log with details, minimal response:
use Skedli\HttpMiddleware\ErrorMiddleware; use Skedli\HttpMiddleware\Internal\Error\ErrorHandlingSettings; $middleware = ErrorMiddleware::create() ->withLogger(logger: $logger) ->withSettings(settings: ErrorHandlingSettings::from( logErrors: true, logErrorDetails: true, displayErrorDetails: false )) ->build();
Response body:
{
"error": "Unexpected database error"
}
Log output:
level=ERROR key=Unexpected database error data={"exception":"RuntimeException","file":"/app/src/...","line":42,"trace":"#0 /app/src/..."}
Logging errors
To enable error logging, provide a tiny-blocks/logger instance and enable
logErrors in the settings. The logger alone is not sufficient, logErrors must be explicitly enabled.
use Skedli\HttpMiddleware\ErrorMiddleware; use Skedli\HttpMiddleware\Internal\Error\ErrorHandlingSettings; $middleware = ErrorMiddleware::create() ->withLogger(logger: $logger) ->withSettings(settings: ErrorHandlingSettings::from( logErrors: true, logErrorDetails: false, displayErrorDetails: false )) ->build();
Log output:
level=ERROR key=Unexpected database error data={}
License
HTTP Middleware is licensed under MIT.