nycorp / lite-api
The skeleton application for the Laravel framework.
Requires
- php: ^8.1
- darkaonline/l5-swagger: ^8.6
- dcarbone/php-consul-api: ^2.0
- laravel/scout: ^11.5
- reliese/laravel: ^1.4
- tymon/jwt-auth: ^2.1
Requires (Dev)
- bennett-treptow/laravel-migration-generator: ^4.4
- laravel/pint: ^1.16
- orchestra/testbench: ^9.1
- pestphp/pest: ^2.34
- phpstan/phpstan: ^1.11
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-master
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.58
- 1.1.57
- 1.1.56
- 1.1.55
- 1.1.54
- 1.1.53
- 1.1.52
- 1.1.51
- 1.1.50
- 1.1.49
- 1.1.48
- 1.1.47
- 1.1.46
- 1.1.45
- 1.1.44
- 1.1.43
- 1.1.42
- 1.1.41
- 1.1.40
- 1.1.39
- 1.1.38
- 1.1.37
- 1.1.36
- 1.1.35
- 1.1.34
- 1.1.33
- 1.1.32
- 1.1.3.1
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
This package is auto-updated.
Last update: 2026-10-23 03:52:25 UTC
README
- Log into database and into micro-service
- Swagger documentation using darkaonline/l5-swagger
- Service discovery using dcarbone/php-consul-api
- Jwt auth using tymon/jwt-auth
- SQL to migration using bennett-treptow/laravel-migration-generator
- SQL to model using reliese/laravel
- API versioning (Meta Graph API style): frozen versions, deprecation and sunset headers, one response envelope and one OpenAPI documentation per version, contract tests
Installation (with Composer)
composer require nycorp/lite-api
Usage
php artisan vendor:publish --provider="Nycorp\LiteApi\Providers\LiteApiServiceProvider"
Logging :
Enable logger to call your remote logger service or log in app database
the default value is false
LOG_REMOTE=false
To enable log service in .env
LOG_CHANNEL=stack LOG_STACK=daily,service_log LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug
use the logger provide by Laravel as usual
Log::info("Delete: started {$this->modelLogger($id)} ");
API versioning
A version (v1.0, v2.0...) is a frozen contract: request formats, response formats, behaviour and webhook payloads.
Old versions keep working untouched while new ones evolve.
| Allowed in a released version | Needs a new version |
|---|---|
| New endpoint, new optional input field | Removing or renaming a field, changing its type/format |
| New field in a response, new webhook event type | Making a field required, changing an error code/default |
1. Declare the versions
php artisan vendor:publish --tag=lite-api-versioning
// config/lite-api.php 'versions' => [ 'v1.0' => [ 'envelope' => LegacyEnvelope::class, // historical format, always HTTP 200 'released_at' => '2025-01-01', 'deprecated_at' => '2027-01-01', // announced to clients from now on 'sunset_at' => '2027-07-01', // HTTP 410 from this date 'changelog' => 'https://example.com/docs/changelog#v1.0', ], 'v2.0' => [ 'envelope' => StandardEnvelope::class, // real HTTP statuses 'released_at' => '2026-11-01', ], ],
The status is computed from the dates: active, deprecated once deprecated_at is reached, sunset once sunset_at is reached.
2. Register the routes
use Nycorp\LiteApi\LiteApi; // /api/v2.0/messages/... LiteApi::version('v2.0', function () { MessageController::expose('messages'); }); // Existing unversioned routes: tag them as v1.0 without moving them LiteApi::version('v1.0', base_path('routes/api.php'), prefix: ''); // GET /api/versions : declared versions and their lifecycle LiteApi::versionsRoute();
Every versioned response carries X-Api-Version (configurable). Once deprecated_at / sunset_at are set,
it also carries Deprecation (RFC 9745), Sunset (RFC 8594) and Link: <changelog>; rel="deprecation".
A sunset version answers 410 with the code VERSION_SUNSET (1200) and the latest version in the body.
3. Response envelopes
liteResponse() delegates to the envelope of the version serving the request: controllers do not change.
Outside versioned routes (unversioned routes, jobs, console) versioning.default_envelope is used (legacy).
LegacyEnvelope: unchanged historical format.
{"success": false, "message": "...", "code": 1002, "body": {...}, "trace_id": "..."}
StandardEnvelope: HTTP status from the response code (422 validation, 404 not found, 401 token, 409 duplicate...,
overridable with lite-api.http_status).
{"data": {...}}
{"error": {"code": 1002, "type": "REQUEST_VALIDATION_ERROR", "message": "...", "details": {...}, "trace_id": "..."}}
The envelope also applies to responses built before the version middleware (auth middlewares...), the version is read from the route.
Custom envelope: implement Nycorp\LiteApi\Response\Envelopes\ResponseEnvelope.
4. Track usage before sunsetting
Nycorp\LiteApi\Events\ApiVersionUsed (version, request, HTTP status) is fired on every versioned request.
Listen to it to know which clients still call a version before deprecating it; storage is up to the application.
5. One OpenAPI documentation per version
Give a version its own annotation folder; an l5-swagger documentation is registered for it automatically
(api-docs-v2.0.json, UI on api/docs/v2.0), with the envelope schemas (LiteApiLegacyResponse, LiteApiError).
'v2.0' => [ 'envelope' => StandardEnvelope::class, 'docs' => [ 'annotations' => [app_path('Http/Docs/V2')], 'route' => 'api/docs/v2.0', // optional 'title' => 'My API', // optional, version appended 'base' => env('APP_URL') . '/api/v2.0', // optional, replaces l5-swagger paths.base for this version ], ],
- Each version folder holds its own
#[OA\Info(version: '2.0')]and#[OA\Server(url: '.../api/v2.0')]. - Annotations common to all versions go in
versioning.docs.shared_annotations. - The folders of the other versions are excluded automatically. If an existing documentation scans the whole
appfolder, add the version folders to itspaths.excludes. - Documentations declared by hand in
config/l5-swagger.phpwith the same name win.
php artisan l5-swagger:generate v2.0
6. Contract tests
Freeze the shape of each endpoint once a version is released:
use Nycorp\LiteApi\Testing\AssertsApiContract; class AgentApiTest extends TestCase { use AssertsApiContract; public function test_search_contract(): void { $this->assertApiContract($this->getJson('/api/v2.0/agent/search'), 'v2.0/agent.search'); } }
The first run records tests/Contracts/v2.0/agent.search.json (commit it). Later runs fail when a field is removed,
changes type or becomes null; new fields pass. Re-record with LITE_API_UPDATE_CONTRACTS=1 only when opening a new version.
Outbound webhooks
Opt-in: nothing is migrated until you publish the migrations.
php artisan vendor:publish --tag=lite-api-webhooks php artisan migrate
Endpoints belong to an owner (company, account...): add HasWebhookEndpoints to that model.
1. Declare and dispatch events
// config/lite-api.php 'webhooks' => [ 'header_prefix' => 'X-MyApp', // X-MyApp-Event, -Delivery, -Timestamp, -Signature, -Api-Version 'events' => [ 'message.received' => 'A contact sent a message', ], ],
class MessageReceived extends WebhookEvent { public function __construct(private Message $message) {} public function type(): string { return 'message.received'; } public function owner(): Model { return $this->message->company; } // Endpoints can restrict themselves to some values through their `filters` public function context(): array { return ['app' => $this->message->app->hashid]; } // Payload per API version: endpoints keep the version they were created with public function data(?string $apiVersion): array { return MessagePresenter::present($this->message); } } Webhooks::dispatch(new MessageReceived($message)); // waits for the DB commit
Each subscribed endpoint gets a delivery, queued on the webhooks queue (run a worker for it).
2. Delivery
- Body:
{"id": "evt_…", "type": "...", "api_version": "v2.0", "created_at": "...", "data": {...}} - Signature:
sha256=hex(hmac_sha256(secret, "{timestamp}.{raw body}")); receivers can useWebhookSigner::verify() - Success: 2xx within
timeoutseconds, redirects are not followed - Retries:
backoff(10s, 1min, 5min, 30min, 2h, 6h, 12h), thenWebhookDeliveryFailed - An endpoint failing for
disable_after_hours(72h) is disabled:WebhookEndpointDisabled(notify the owner) - SSRF: URLs resolving to private/reserved addresses are refused on save and before each delivery
(
allow_private_urlsfor local development only) - History:
webhook_deliveries, pruned afterretention_days(schedulemodel:prune)
3. Management API
Inside an authenticated (versioned) group; the owner is the authenticated user by default
(WebhookEndpointController::resolveOwnerUsing() to change it).
LiteApi::version('v2.0', function () { LiteApi::webhookRoutes(); // /webhooks... });
| Route | |
|---|---|
GET webhooks/events |
Event catalog |
GET/POST webhooks, GET/PUT/DELETE webhooks/{id} |
Endpoints (secret returned once, on creation) |
POST webhooks/{id}/rotate-secret |
New secret |
POST webhooks/{id}/test |
Sends webhook.test now |
GET webhooks/{id}/deliveries |
History (?status=pending|succeeded|failed) |
POST webhooks/{id}/deliveries/{delivery}/retry |
Redelivery, same event id |
Add LiteApi::webhookDocs() to the version docs.annotations to document these routes.