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

L5 Api Laravel Package

specialtactics/l5-api

Specialtactics/l5-api is a Laravel 5 package for building consistent JSON APIs with less boilerplate. It provides API controller foundations, standardized success and error responses, validation and exception handling, and helpers for cleaner RESTful endpoints.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install

composer require specialtactics/l5-api

On Laravel 5.5+ the service provider is auto-discovered. On 5.4 and below, register it manually in config/app.php:

'providers' => [
    // ...
    Specialtactics\L5Api\Providers\L5ApiServiceProvider::class,
],

2. Publish the config (status codes, headers, error envelope):

php artisan vendor:publish --provider="Specialtactics\L5Api\Providers\L5ApiServiceProvider"

3. Look at these first, in order

  • config/l5-api.php (or config/api.php) — default status codes, error format, headers.
  • src/Http/Controllers/ApiController.php — the base class your controllers will extend. Read the response method signatures before writing anything.
  • The package README's response-format examples — this is your API contract; pin it down before clients integrate.

4. First use case: a read endpoint

namespace App\Http\Controllers\Api;

use App\Models\Post;
use Specialtactics\L5Api\Http\Controllers\ApiController;

class PostsController extends ApiController
{
    public function show($id)
    {
        $post = Post::findOrFail($id);

        return $this->respondWithItem($post);
    }

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

Register a versioned route group (routes/api.php or Route::prefix('v1')) and hit it with Accept: application/json. If you get HTML back, the request isn't being negotiated as JSON — see Gotchas.

Implementation Patterns

Extend the base controller once, app-wide. Don't have every controller extend the vendor class directly. Create app/Http/Controllers/Api/ApiController.php extending Specialtactics\L5Api\Http\Controllers\ApiController and put your envelope tweaks, common headers, and auth helpers there. This is your upgrade buffer if the package stalls.

Items vs collections vs arrays. Use respondWithItem() for a single resource, respondWithCollection() for lists (it handles paginator metadata if you pass a paginator), and respondWithArray() when you need a custom shape. Prefer the first two so the client sees one consistent envelope.

Transformers for output shaping. If the package wires up Fractal transformers, use them instead of ->toArray() on models — it keeps DB columns out of your public contract and lets you add fields without breaking consumers:

return $this->respondWithItem($post, new PostTransformer());
return $this->respondWithCollection($posts, new PostTransformer());

Status codes: set, then respond. Set the code before the response call and return the result:

public function store(StorePostRequest $request)
{
    $post = Post::create($request->validated());

    return $this->setStatusCode(201)->respondWithItem($post, new PostTransformer());
}

Use 204 + the no-content helper for deletes, not an empty 200 body — clients and HTTP caches differentiate.

Centralized error handling. The real value here is one error shape everywhere. Throw standard Laravel/Symfony exceptions (ModelNotFoundException, AuthorizationException, ValidationException) from services or controllers and let the package's handler convert them. Keep your app/Exceptions/Handler.php render() method delegating to the package for API requests and falling back to the HTML renderer for web routes:

public function render($request, Exception $e)
{
    if ($request->expectsJson()) {
        return $this->renderApiException($e); // package helper / your wrapper
    }

    return parent::render($request, $e);
}

Validation responses. Type-hint a FormRequest on the controller method and let the exception fire — the handler produces the 422 envelope. Never hand-roll response()->json(['errors' => ...]); you'll drift from the contract.

Headers and caching. Set custom headers (rate-limit info, ETag, deprecation notices) once per request via the controller's header setter rather than appending to each response. For public GETs, add Cache-Control middleware at the route level so responses stay cacheable through your CDN.

Testing pattern. Assert on structure, not exact JSON, so meta fields can evolve:

$this->getJson('/api/v1/posts/1')
     ->assertStatus(200)
     ->assertJsonStructure(['data' => ['id', 'title']]);

Also test the 404, 422, and 401 paths — the error envelope is the part most likely to regress.

Gotchas and Tips

  • It's a Laravel 5 package. Auto-discovery and provider registration behave differently across 5.4/5.5+. On Laravel 8+ it generally still works, but verify the provider's register()/boot() don't call removed framework APIs. If you're on a modern Laravel, budget an afternoon to fork or vendor the base controller.
  • Accept: application/json is mandatory for the JSON error path. Curl requests and browser hits without that header fall through to Laravel's HTML exception renderer and you'll spend an hour debugging a "missing" envelope. Tests using getJson() are fine.
  • APP_DEBUG=true leaks stack traces into the error payload. Test error responses with debug off, and confirm the handler doesn't substitute the trace for your configured message.
  • Remember return. respondWithError() builds a response; if you call it without returning (or throwing), execution continues and you can end up with two responses or a 200 on a failure path.
  • Status code persistence. If your controller sets 201 early and then returns a different response, the code can carry over within that request. Set the status immediately before the response call, and never set it inside a loop.
  • Don't pass a raw paginator to respondWithCollection if the signature expects a collection — check src/ for the exact expected type. Wrapping the wrong object yields a data array that silently drops meta/links, and pagination breaks in the client.
  • Include/exclude query params (Spatie/Fractal style) usually require an explicit parseIncludes() call — they don't work automatically. If ?include=author returns nothing, that's why.
  • Config caching: run php artisan config:cache after editing the package config; stale cached config is the usual cause of "my status code change did nothing."
  • Vendor files are not yours. Override respondWithArray() in your app-level base controller to change the envelope instead of editing vendor/specialtactics/. Same for the exception handler.
  • Low maintenance risk. 36 stars and a small surface area means fixes may be slow. The package is thin by design — if you need long-term stability, copy ApiController and the exception handler into app/ and drop the dependency. You lose little.
  • Debugging tip: temporarily log the built payload (Log::debug(get_class($response), [$response->getData(true)])) inside your base controller's response override. It's faster than eyeballing nested JSON diffs in test output.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle