Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Api Boilerplate Laravel Package

specialtactics/laravel-api-boilerplate

A Laravel API boilerplate that gives you a clean, ready-to-use foundation for JSON APIs. It wires up common API essentials like authentication, authorization, validation, and consistent responses, so you can skip repetitive setup and start building features.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a project template, not a runtime dependency — the fastest path is to clone it or scaffold from it rather than composer require it into an existing app.

git clone https://github.com/specialtactics/laravel-api-boilerplate my-api
cd my-api && rm -rf .git
composer install
cp .env.example .env
php artisan key:generate
# configure DB in .env, then:
php artisan migrate --seed
php artisan serve   # or ./vendor/bin/sail up if using the bundled Docker setup

Where to look first, in this order:

  1. routes/api.php — the versioned route groups (/api/v1/...) and auth middleware wiring.
  2. app/Http/Controllers/Api/V1/ — controller conventions, how responses are returned.
  3. app/Transformers/ — the Fractal transformers that shape every JSON payload.
  4. app/Exceptions/Handler.php — centralized JSON error rendering (validation, 404, 500, auth).
  5. The service provider that registers response macros (commonly app/Providers/ApiServiceProvider.php or ResponseMacroServiceProvider.php) — this is what gives you the consistent response envelope.
  6. tests/Feature/ — copy an existing test as your scaffolding pattern for new endpoints.

First use case — prove the auth + response loop works end-to-end:

# register
curl -X POST http://localhost:8000/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"name":"Dev","email":"[email protected]","password":"secret123","password_confirmation":"secret123"}'

# login → token
curl -X POST http://localhost:8000/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"secret123"}'

# authenticated call
curl http://localhost:8000/api/v1/me -H "Authorization: Bearer <token>"

Once you see a consistent { "data": ... } (or project-specific envelope) from all three calls and a clean JSON validation error, you understand the boilerplate's contract. Everything else is repetition of that pattern.

Implementation Patterns

The vertical slice: add one resource at a time

The boilerplate rewards a rigid, repeatable slice for every new API resource. Do these five steps in order:

php artisan make:model Post -m          # migration + model
php artisan make:controller Api/V1/PostController --api
php artisan make:request StorePostRequest
# hand-create app/Transformers/PostTransformer.php
  1. Migration + model — add relationships and $fillable/$casts.
  2. Transformer — one transformer per model; keep it dumb (fields only, no queries). This is the single source of truth for the public shape of your resource.
  3. FormRequest — validation lives here, not in the controller. The boilerplate's exception handler already turns ValidationException into a standardized 422.
  4. Controller — thin: fetch, paginate, pass through the transformer, return via the project's response helper.
  5. Route — register inside the existing v1 group in routes/api.php, behind auth:sanctum (or auth:api on Passport-based versions) unless it's truly public.

Let the envelope do the work

Do not return response()->json([...]) ad hoc — you'll break client expectations and your own tests. Use the macro/helper the boilerplate exposes (names vary by version, commonly respondWithItem, respondWithCollection, or $this->response->item(...)):

public function show(Post $post)
{
    return $this->respondWithItem($post, new PostTransformer());
}

public function index()
{
    return $this->respondWithCollection(
        Post::paginate(20),
        new PostTransformer()
    );
}

This is the pattern that makes pagination meta, errors, and success responses mutually consistent. If a new endpoint needs a shape the macros don't provide, add a macro — don't bypass it.

Auth: token issuance and reuse

Login/register controllers issue a token (Sanctum personal access token or Passport token depending on version). In tests and clients, treat the token as opaque and always send Authorization: Bearer <token> plus Accept: application/json.

// Feature test pattern
$user = User::factory()->create();
$token = $user->createToken('test')->plainTextToken;

$this->withHeader('Authorization', "Bearer {$token}")
     ->getJson('/api/v1/posts')
     ->assertOk()
     ->assertJsonStructure(['data' => [['id', 'title']]]);

The Accept: application/json header is what triggers the JSON exception path; without it you'll get HTML error pages and confusing test failures.

Fractal includes and eager loading

Transformers are cheap; N+1 queries are not. Whenever a transformer references a relationship ($post->author->name), eager-load it in the controller:

Post::with(['author', 'tags'])->paginate(20);

Use Fractal's ?include=author,tags query param only where you've explicitly wired $availableIncludes and default-included the safe ones. Treat include as a public API surface — adding one is a backwards-compatible change, removing one is not.

Versioning

Treat routes/api.php's v1 group as frozen once clients ship. New breaking behavior goes in v2 with its own controllers/transformers directory. Shared logic goes in app/Services or model methods, so v1 and v2 controllers can both call it.

Exception handling as a feature, not an afterthought

The boilerplate's Handler maps Laravel exceptions to a stable error schema. Extend it by adding a render() branch for your domain exception classes rather than throwing generic \Exception with a message — clients will depend on the HTTP code and error code field.

Gotchas and Tips

  • It's a template, not a library. There is no composer update that brings in upstream fixes. Fork it mentally on day one; cherry-picking upstream changes later is manual and will conflict. Pin your Laravel version deliberately.
  • Response macros are registered at boot. If a macro returns "method not found" in a test or console command, the service provider isn't booted for that context. Check config/app.php provider registration and any bootstrap/providers.php (Laravel 11+) equivalents.
  • Passport vs Sanctum drift. Older tags of this boilerplate use Passport and need php artisan passport:install plus key files in storage/. Newer tags use Sanctum and don't. Read the version tag's README before assuming which middleware alias (auth:api vs auth:sanctum) is correct.
  • Missing Accept: application/json is the #1 cause of "my API returns HTML" and of 404s leaking HTML in tests. Set it globally in your test base class ($this->withHeader('Accept', 'application/json')) and in every HTTP client.
  • Route model binding returns 404 for missing records, which is correct but will only look like your JSON error schema if the Accept header is present. Verify this once per resource — it's easy to ship a resource that returns a clean error for validation failures but HTML for a missing ID.
  • CORS is config, not code. config/cors.php must include every header your client sends (Authorization, Content-Type, Accept, plus any custom X- headers) and the right paths (api/*). New clients failing preflight almost always means a missing header here, not an auth bug.
  • Rate limiting lives in the route service provider (RateLimiter::for('api', ...)). The default is usually 60/min keyed by user or IP. If you add heavy endpoints, define a dedicated limiter name and attach it per-route; don't raise the global limit.
  • Validation errors and auth errors may not share a schema. Check both 422 and 401/403 responses early. If they differ, normalize in the handler before clients are built against them — this is a cheap fix now and a breaking change later.
  • Don't add business logic to transformers. They run per-item and per-include; anything that touches the DB or a service there becomes an N+1 or a hidden dependency. Transformers format; controllers orchestrate.
  • Testing: prefer RefreshDatabase + factories. withoutExceptionHandling() is useful locally for stack traces but masks whether your error envelope is correct — keep at least one test per endpoint that asserts the JSON error shape (e.g., assertJsonValidationErrors).
  • APP_DEBUG=true in production leaks stack traces past your carefully designed error schema. Assert APP_DEBUG=false in your deploy checklist; the boilerplate's handler is good, but debug mode bypasses it in places.
  • Docker/Sail parity. If you develop in the bundled container but CI runs on the host, PHP extensions (bcmath, pdo_pgsql, etc.) and PHP version drift are the usual breakages. Pin the same PHP major/minor in both.
  • Where to extend when the boilerplate doesn't fit: new response macros (for a new payload shape), new exceptions with render() branches (for domain errors), and app/Services (for anything longer than ~15 lines in a controller). Avoid editing the boilerplate's internal traits unless you're prepared to reapply that change on every future version bump.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource