dantepiazza/laravel-base

Base package for Laravel microservices: inter-service communication, JWT middleware, response helpers and notifications.

Maintainers

Package info

github.com/dantepiazza/laravel-base

pkg:composer/dantepiazza/laravel-base

Transparency log

Statistics

Installs: 7

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2.0 2026-07-06 23:48 UTC

This package is auto-updated.

Last update: 2026-07-06 23:59:47 UTC


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.0
  • sentry/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, docker-compose.override.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

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 Value of config('app.maintenance')
X-Cache-Last-Update ISO 8601 timestamp of last cache invalidation — see CacheLastUpdate below
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');
CacheLastUpdate — runtime-updatable cache invalidation timestamp

SPA clients cache lookup data (categories, settings, etc.) in localStorage and use X-Cache-Last-Update to decide whether to refetch it. Because that needs to change the moment backoffice data changes — not at deploy time — the value isn't config, it's stored in the application cache and updated at runtime:

use DantePiazza\LaravelBase\Support\CacheLastUpdate;

// Read the current value (defaults to now() the first time it's read)
CacheLastUpdate::get();

// Bump it — call this from an observer/listener whenever data that SPA
// clients cache locally is created, updated or deleted
CacheLastUpdate::touch();
class CategoryObserver
{
    public function saved(Category $category): void
    {
        CacheLastUpdate::touch();
    }
}

GlobalHeaders calls CacheLastUpdate::get() automatically on every response — no extra wiring needed.

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
docker-compose.override.yml
docker/
├── mysql/my.cnf
├── nginx/default.conf
├── nginx/nginx.conf
├── php/php.ini
├── php/php-fpm.conf
└── supervisor/supervisord.conf

docker-compose.yml is the production compose file (uses a pre-built image via APP_IMAGE). docker-compose.override.yml is the local development override — it builds the image from source and mounts the project directory for live reload.

9. CI/CD — GitHub Actions deploy workflow

The package's own repository ships .github/workflows/deploy.yml: on every push to main or development it builds the app image, pushes it to ghcr.io, then SSHes into the target server to pull and restart it. This workflow is for the package's own reference app / VPS — copy it into a consuming project if you want the same pipeline there.

Configure it under the repo's Settings → Secrets and variables → Actions:

Name Kind Description
SERVER_SSH_KEY Secret Private SSH key used to connect to the server. The only sensitive value.
SERVER_HOST Variable Server hostname or IP.
SERVER_USER Variable SSH user.
SERVER_SSH_PORT Variable SSH port.
SERVER_PROD_PATH Variable Absolute path on the server where main deploys (uses the sha-<short-sha> image tag).
SERVER_DEV_PATH Variable Absolute path on the server where development deploys (uses the latest image tag).

None of these values are secret except the SSH key, so they're set as repository variables, not secrets — that keeps them visible/diffable in the Actions UI instead of hidden.

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
│   └── Support/
│       └── CacheLastUpdate.php          # Runtime-updatable X-Cache-Last-Update value
├── 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
    ├── docker-compose.override.yml
    ├── mysql/my.cnf
    ├── nginx/default.conf
    ├── nginx/nginx.conf
    ├── php/php.ini
    ├── php/php-fpm.conf
    └── supervisor/supervisord.conf