laravel-gateway maintained by wuwx
Laravel Gateway
A lightweight HTTP gateway proxy for Laravel applications. Define proxy routes with a familiar routing syntax, transform requests and responses per-route, and stream upstream responses back to clients — all within Laravel's native routing and middleware system.
Features
- Route-based proxy definitions using
Gateway::get/post/put/patch/delete/any - Dynamic
base_uriresolution — handlers receive the currentRequest - Streaming responses enabled by default (SSE / chunked transfer)
- Per-route request and response transformation hooks
- Global and per-handler Guzzle options (timeout, headers, etc.)
- Works with Laravel route groups, middleware, named routes, and
route:list - Secure by default: hop-by-hop and sensitive client headers stripped
- Path traversal and scheme injection protection
Requirements
- PHP 8.4+
- Laravel 11.x, 12.x, or 13.x
Installation
composer require wuwx/laravel-gateway
Publish the config file:
php artisan vendor:publish --tag="gateway-config"
Usage
Defining proxy routes
Gateway routes are defined in your route files using the Gateway facade:
use Wuwx\LaravelGateway\Facades\Gateway;
Gateway::post('v1/chat/completions', ChatHandler::class);
Gateway::any('v1/{path}', CatchAllHandler::class);
Each call registers a real Laravel route, so proxy routes appear in php artisan route:list.
The {path} parameter captures the sub-path forwarded to the upstream service:
POST /v1/chat/completions → POST https://api.openai.com/v1/chat/completions
GET /proxy/users/123 → GET https://api.example.com/users/123
Route groups & middleware
Gateway routes work inside any Laravel route group:
Route::prefix('v1')->middleware(['auth:sanctum', 'throttle:api'])->group(function () {
Gateway::any('chat/completions', AiHandler::class);
Gateway::any('embeddings', AiHandler::class);
});
Route chaining also works:
Gateway::post('orders/{path}', OrderHandler::class)
->middleware('throttle:60,1')
->name('orders.proxy');
Creating a handler
Every Gateway route requires a handler class. Extend GatewayHandler for no-op defaults:
use Illuminate\Http\Request;
use Wuwx\LaravelGateway\GatewayHandler;
class OpenAiHandler extends GatewayHandler
{
public function getOptions(): array
{
return [
'base_uri' => 'https://api.openai.com/v1',
'timeout' => 120,
];
}
public function getRequest(Request $request): Request
{
$request->headers->set('Authorization', 'Bearer '.config('services.openai.key'));
return $request;
}
}
The three interface methods:
| Method | Purpose |
|---|---|
getOptions(): array |
Upstream base_uri and Guzzle options. Use request() for dynamic resolution. |
getRequest(Request $request): Request |
Transform the request before forwarding (inject credentials, rewrite headers). |
getResponse(Request $request, Response $response): Response |
Transform the upstream response before returning to the client. Not called for streamed responses. |
If you prefer to implement the interface directly, implement GatewayControllerInterface instead of extending GatewayHandler.
Dynamic base_uri
Use the request() helper inside getOptions() to resolve the upstream target dynamically:
public function getOptions(): array
{
$provider = request()->attributes->get('gateway.provider');
return ['base_uri' => $provider->base_url.'/v1/chat/completions'];
}
This is useful when the upstream is determined by middleware, database lookup, or request content.
Streaming
Streaming is enabled by default. Responses are forwarded chunk-by-chunk via StreamedResponse, making it ideal for SSE and large payloads.
Disable streaming globally:
GATEWAY_STREAMING=false
Or per-handler:
public function getOptions(): array
{
return [
'base_uri' => 'https://api.example.com/v1',
'streaming' => false,
];
}
When streaming is disabled, the full response is buffered and the getResponse() hook is called.
Configuration
// config/gateway.php
return [
'enabled' => env('GATEWAY_ENABLED', true),
'options' => [
'timeout' => env('GATEWAY_TIMEOUT', 30),
'connect_timeout' => env('GATEWAY_CONNECT_TIMEOUT', 10),
'http_errors' => false,
],
'streaming' => env('GATEWAY_STREAMING', true),
'security' => [
'strip_client_headers' => ['cookie', 'proxy-authorization'],
'hop_by_hop_headers' => [
'host', 'connection', 'transfer-encoding', 'upgrade',
'keep-alive', 'te', 'trailer', 'proxy-authenticate',
'proxy-authorization',
],
],
];
Security
Header stripping
Sensitive client headers (cookie, proxy-authorization) are stripped before forwarding. Hop-by-hop headers (RFC 7230) are always removed from both request and response.
Handlers can re-add credentials from your own config inside getRequest():
public function getRequest(Request $request): Request
{
$request->headers->set('Authorization', 'Bearer '.config('services.github.token'));
return $request;
}
Path protection
Gateway rejects paths containing traversal segments (../, ./) or scheme/authority injection (http://, //) with a 400 response before any upstream call is made.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.