dantepiazza / laravel-base
Base package for Laravel microservices: inter-service communication, JWT middleware, response helpers and notifications.
Requires
- php: ^8.3
- dantepiazza/laravel-api-response: ^1.0.0
- firebase/php-jwt: ^7.0.5
- laravel/framework: ^11.0|^12.0|^13.0
- sentry/sentry-laravel: ^4.0
README
Base package for Laravel backend projects. Provides a pre-configured bootstrap/app.php, standardized middleware, structured API responses, webhook handling, error tracking, and API documentation — all publishable via a single command.
Requirements
- PHP 8.3+
- Laravel 11, 12 or 13
dantepiazza/laravel-api-response ^1.0sentry/sentry-laravel ^4.0- A configured queue driver (Redis recommended) for webhook processing
Installation
composer require dantepiazza/laravel-base
Publish all stubs at once:
php artisan vendor:publish --tag=laravel-base
Or publish individually:
| Tag | Publishes |
|---|---|
laravel-base |
Everything below at once |
laravel-base-bootstrap |
bootstrap/app.php |
laravel-base-logging |
config/logging.php |
laravel-base-cors |
config/cors.php |
laravel-base-sentry |
config/sentry.php |
laravel-base-controller |
app/Http/Controllers/Controller.php |
laravel-base-env |
.env.example |
laravel-base-docker |
Dockerfile, docker-compose.yml and all Docker config files |
Scribe config is merged from the package by default — no need to publish it unless you need to customize it:
php artisan ms:publish-scribe
# Overwrite without prompt:
php artisan ms:publish-scribe --force
Environment variables
# ─── App ────────────────────────────────────────────────────────────────────── APP_NAME=my-service APP_ENV=production APP_URL=https://my-service.yourdomain.com # ─── GlitchTip (via Sentry SDK) ─────────────────────────────────────────────── GLITCHTIP_DSN= GLITCHTIP_TRACES_SAMPLE_RATE=0.1 GLITCHTIP_PROFILES_SAMPLE_RATE=0.1 # ─── Logging ────────────────────────────────────────────────────────────────── LOG_CHANNEL=stack LOG_STACK=daily,slack LOG_LEVEL=error LOG_SLACK_WEBHOOK_URL= LOG_SLACK_USERNAME= LOG_SLACK_LEVEL=critical # ─── Scribe ─────────────────────────────────────────────────────────────────── SCRIBE_AUTH_KEY= # ─── CORS ───────────────────────────────────────────────────────────────────── CORS_ALLOWED_ORIGINS=http://localhost
Features
1. Base Controller — dantepiazza/laravel-api-response
This package requires dantepiazza/laravel-api-response and pre-wires it into the base Controller stub. After publishing, every controller in your project gets ApiResponse injected automatically:
// app/Http/Controllers/Controller.php (published stub) namespace App\Http\Controllers; use DantePiazza\LaravelApiResponse\ApiResponse; abstract class Controller { public function __construct(protected ApiResponse $api) {} }
Usage in any controller:
class InvoiceController extends Controller { public function index(): JsonResponse { return $this->api->success('Invoices retrieved.')->data(Invoice::paginate(20))->response(); } public function store(StoreInvoiceRequest $request): JsonResponse { return $this->api->created('Invoice created.')->data(Invoice::create($request->validated()))->response(); } public function show(Invoice $invoice): JsonResponse { return $this->api->success()->data($invoice)->response(); } public function destroy(Invoice $invoice): JsonResponse { $invoice->delete(); return $this->api->success('Invoice deleted.')->response(); } }
2. Middleware
ForceJsonResponse
Forces Accept: application/json on every request so Laravel always returns JSON errors. Register it globally in bootstrap/app.php:
$middleware->api(append: [ \DantePiazza\LaravelBase\Http\Middleware\ForceJsonResponse::class, ]);
GlobalHeaders
Attaches standard headers to every API response. Register it in bootstrap/app.php:
$middleware->api(append: [ \DantePiazza\LaravelBase\Http\Middleware\GlobalHeaders::class, ]);
Headers added to every response:
| Header | Description |
|---|---|
X-Maintenance |
Whether the service is in maintenance mode (app.maintenance) |
X-Cache-Last-Update |
ISO 8601 timestamp of last cache invalidation (app.cache_last_update) |
X-Correlation-ID |
Trace ID — reused from the incoming request or generated as UUID v4 |
The X-Correlation-ID is also injected back into the request object so any controller or service can read it:
$correlationId = $request->header('X-Correlation-ID'); // or $correlationId = request()->header('X-Correlation-ID');
3. Webhook handling
The package ships a generic WebhookController that receives webhooks from any source, verifies the signature, and dispatches a Laravel event asynchronously.
Route setup
The controller expects a {source} route parameter that identifies the origin:
use DantePiazza\LaravelBase\Http\Controllers\WebhookController; Route::post('webhooks/{source}', [WebhookController::class, 'handle']);
This dispatches events named webhook.{source}.{event_type}, so each origin is isolated:
| URL called | Event dispatched |
|---|---|
POST /webhooks/convoy |
webhook.convoy.invoice.created |
POST /webhooks/stripe |
webhook.stripe.charge.succeeded |
POST /webhooks/github |
webhook.github.push |
Signature verification
By default the controller rejects all requests (verifySignature returns false). To enable a source, extend the controller and override the method:
namespace App\Http\Controllers; use Illuminate\Http\Request; use DantePiazza\LaravelBase\Http\Controllers\WebhookController as BaseWebhookController; class ConvoyWebhookController extends BaseWebhookController { protected function verifySignature(Request $request, string $source): bool { return hash_equals( hash_hmac('sha256', $request->getContent(), config('services.convoy.secret')), (string) $request->header('X-Convoy-Signature') ); } }
Then point the route to your controller:
Route::post('webhooks/convoy', [ConvoyWebhookController::class, 'handle']);
Listening to webhook events
use Illuminate\Support\Facades\Event; // Single event Event::listen('webhook.stripe.charge.succeeded', function (array $payload) { $chargeId = $payload['data']['id']; }); // Wildcard — all events from one source Event::listen('webhook.convoy.*', ConvoyWebhookListener::class); // Wildcard — every webhook regardless of source Event::listen('webhook.*', GeneralWebhookListener::class);
Payload passed to every listener:
[
'source' => 'stripe',
'event_type' => 'charge.succeeded',
'data' => [...], // $body['data'] if present, otherwise full body
'headers' => [...], // all request headers
'raw' => [...], // full decoded request body
]
4. Exception handling (bootstrap/app.php)
The published bootstrap/app.php includes a full exception handler wired to dantepiazza/laravel-api-response:
| Exception | HTTP status | Response |
|---|---|---|
ValidationException |
422 | Field-level errors via validationError() |
AuthenticationException |
401 | unauthorized() |
AccessDeniedHttpException |
403 | forbidden() |
NotFoundHttpException / ModelNotFoundException |
404 | notFound() |
ThrottleRequestsException |
429 | tooManyRequests() |
Throwable (catchall) |
500 | server_error with message |
Rate limiters included out of the box:
| Limiter | Limit | Keyed by |
|---|---|---|
api |
120 req/min | user ID or IP |
auth |
10 req/min | IP |
microservices |
600 req/min | IP |
5. GlitchTip / Sentry error tracking
Uses sentry/sentry-laravel, which is fully compatible with GlitchTip. Publish the config and set the DSN:
php artisan vendor:publish --tag=laravel-base-sentry
GLITCHTIP_DSN=https://key@glitchtip.yourdomain.com/1
The X-Correlation-ID on every request can be used to correlate GlitchTip events with specific API calls.
6. Logging
The published config/logging.php includes a pre-configured stack with daily file rotation and optional Slack alerts for critical errors:
LOG_STACK=daily,slack LOG_LEVEL=error LOG_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/... LOG_SLACK_LEVEL=critical
7. API Documentation (Scribe)
Scribe is configured out of the box — the package merges its own config/scribe.php automatically, no publishing needed.
To generate docs:
php artisan scribe:generate
To customize the config for a specific project:
php artisan ms:publish-scribe
# Edit config/scribe.php, then regenerate
8. Docker
A production-ready Docker setup is included. Publish it with:
php artisan vendor:publish --tag=laravel-base-docker
Publishes:
Dockerfile
docker-compose.yml
.env.docker
docker/
├── mysql/my.cnf
├── nginx/default.conf
├── nginx/nginx.conf
├── php/php.ini
├── php/php-fpm.conf
├── supervisor/supervisord.conf
└── entrypoint.sh
Package structure
dantepiazza/laravel-base/
├── composer.json
├── README.md
├── src/
│ ├── BaseServiceProvider.php
│ ├── Console/Commands/
│ │ └── PublishScribeConfig.php # php artisan ms:publish-scribe
│ └── Http/
│ ├── Controllers/
│ │ └── WebhookController.php # Generic webhook receiver
│ └── Middleware/
│ ├── ForceJsonResponse.php
│ └── GlobalHeaders.php
├── stubs/
│ ├── .env.example
│ ├── app/Http/Controllers/
│ │ └── Controller.php # Base controller with ApiResponse injected
│ ├── bootstrap/
│ │ └── app.php # Pre-configured with exception handling and rate limiters
│ ├── config/
│ │ ├── cors.php
│ │ ├── logging.php
│ │ ├── scribe.php
│ │ └── sentry.php
│ └── routes/
│ └── api.php
└── docker/
├── Dockerfile
├── docker-compose.yml
├── .env.docker
├── entrypoint.sh
├── mysql/my.cnf
├── nginx/default.conf
├── nginx/nginx.conf
├── php/php.ini
├── php/php-fpm.conf
└── supervisor/supervisord.conf