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 Exceptions Laravel Package

lanin/laravel-api-exceptions

Standardize error responses in Laravel APIs. This package converts exceptions into consistent, configurable JSON payloads (including JSON:API style), so clients always get predictable, well-formed errors instead of HTML stack traces.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer and let Laravel's package discovery register the service provider:

composer require lanin/laravel-api-exceptions

The whole value of this package is one decision: stop hand-building error responses and start throwing typed exceptions. Three wiring steps get you there.

  1. Point your exception handler at the package's renderer. In app/Exceptions/Handler.php, replace (or extend) the default render() with the package's handler base class / trait. This is what converts a thrown exception into a JSON body with the right HTTP status and a machine-readable error code.
  2. Publish and skim the config (php artisan vendor:publish --tag=...) so you know where the error envelope shape, debug/trace toggle, and default status mapping live before you need to change them.
  3. Confirm content negotiation. The handler should produce JSON for /api/* regardless of Accept. If you rely on $request->expectsJson(), make sure your API clients always send Accept: application/json — otherwise you'll get an HTML error page back mid-integration.

First use case: take the most common ad-hoc response in your codebase — abort(404) or return response()->json(['error' => 'Not found'], 404) — and replace it with the package's NotFoundException (or an app-specific subclass). Once that single call site round-trips with a consistent {error: {code, message}} shaped body, you've validated the wiring and can convert the rest wholesale.

Where to look first: the base Exception class and the list of shipped exception types in src/. That list is effectively your API's error vocabulary — read it before inventing your own.

Implementation Patterns

Throw from services, render at the edge. The package's biggest architectural payoff is that domain and service code can throw semantic exceptions without knowing about HTTP. A repository throws NotFoundException when a record is missing; a policy check throws ForbiddenException; the handler maps both to 404/403. Controllers stay thin and testable without HTTP assertions.

Map the framework's exceptions once, centrally. Laravel throws its own exceptions before your code ever runs: ValidationException from FormRequests, ModelNotFoundException from route-model binding, AuthenticationException, AuthorizationException, ThrottleRequestsException, NotFoundHttpException. Register mappings for all of these in one place. FormRequest validation in particular is the single most common source of inconsistent error shapes, because it fires before the controller.

Define an app-level error code registry. HTTP status tells the client category; the package's error code tells it exactly what happened. Treat codes as a public contract: a config/error-codes.php or an enum mapping UserEmailTaken => 1001 beats scattering magic integers. Client SDKs and frontends branch on these — changing one is a breaking change.

Custom exceptions should be one-liners. Extend the package's base class, set the status, code, and default message, and you're done. Avoid overriding rendering logic per-exception; if two exceptions need the same extra payload key, that belongs in the shared renderer.

class InsufficientFundsException extends ApiException
{
    protected $status  = 422;
    protected $code    = 2041;
    protected $message = 'Insufficient funds for this transaction.';
}

Use the constructor for context, not for prose. Pass structured data (field name, conflicting resource ID) into the exception and let the renderer decide whether it appears in the payload. Don't concatenate user-facing strings at the throw site — that's where localization and PII leaks creep in.

Consistent testing. With a single envelope shape, your test helpers get simple: one assertion for status, one for error.code. Write a shared assertApiError($response, $status, $code) trait and use it everywhere.

Gotchas and Tips

APP_DEBUG leaks. The package exposes trace/exception detail when debug is on. Verify what APP_DEBUG=false actually emits — including the message field for 5xx. Uncaught \Exception should surface as a generic 500 with a generic message plus a correlation ID, never $e->getMessage().

Some exceptions bypass your handler. Route-level 404s (no matching route) and middleware-thrown exceptions (throttling, auth guards) don't originate in your controllers. Confirm each of those still renders through the package by hitting the route, not by unit-testing the exception class in isolation.

CORS headers on error responses. A 401 without Access-Control-Allow-Origin looks like a network failure to a browser client and sends people debugging the wrong layer. Check that your CORS middleware runs on the exception path.

Lumen needs manual wiring. No package discovery — register the provider in bootstrap/app.php, enable facades/eloquent as needed, and bind the handler explicitly. Budget an hour for this if you're on Lumen; it's the roughest edge of the package.

Small ecosystem, recent releases. At ~39 stars and a single maintainer, treat this as a convenience layer, not bedrock. Two mitigations: (a) wrap it — have all your app exceptions extend your App\Exceptions\ApiException which extends the package's, so swapping the package later is one file; (b) pin the version and read the changelog before minor bumps.

4xx vs 5xx logging. The handler shouldn't flood your logs with expected 404s and 422s. Ensure 4xx renders without Log::error, and 5xx logs with context. This is the single highest-value tweak after initial setup.

Extension point: the renderer. If you need to add a request_id, docs_url, or nested errors bag to every response, override the render method once rather than post-processing responses in middleware. Middleware-based mutation breaks when the response isn't a JsonResponse (e.g., a streamed download that errored halfway).

abort() still works, but loses typing. abort(404, 'msg') produces a valid response via HttpException, but you get no error code and no structured context. Use it only for genuinely throwaway cases.

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