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 Response Builder Laravel Package

marcin-orlowski/laravel-api-response-builder

Build consistent, structured JSON API responses in Laravel. Standardize success and error payloads, map custom internal codes to HTTP statuses, and keep every endpoint returning the same predictable, documented format.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config:

composer require marcin-orlowski/laravel-api-response-builder
php artisan vendor:publish --provider="MarcinOrlowski\ResponseBuilder\ResponseBuilderServiceProvider"

Laravel auto-discovery registers the service provider and ResponseBuilder facade. Look first at:

  1. config/response_builder.php — response codes, HTTP mappings, debug behavior, encoding.
  2. The package README’s success/error examples.
  3. MarcinOrlowski\ResponseBuilder\BaseApiCodes — subclass this for your application error codes.
  4. MarcinOrlowski\ResponseBuilder\ResponseBuilder — the facade/builder you will use in controllers.

Create one central error-code class early:

namespace App\Support;

use MarcinOrlowski\ResponseBuilder\BaseApiCodes;

class ApiCode extends BaseApiCodes
{
    public const USER_NOT_FOUND = 1001;
    public const VALIDATION_FAILED = 1002;
    public const UNAUTHORIZED = 1003;
}

First use case: return a normalized success response from a controller and a mapped error response when a model is missing:

use App\Support\ApiCode;
use MarcinOrlowski\ResponseBuilder\ResponseBuilder;

public function show(int $id)
{
    $user = User::find($id);

    if (! $user) {
        return ResponseBuilder::asError()
            ->withCode(ApiCode::USER_NOT_FOUND)
            ->withHttpCode(404)
            ->withMessage('User not found')
            ->build();
    }

    return ResponseBuilder::asSuccess()
        ->withData($user)
        ->build();
}

The payoff is a consistent envelope—typically success, code, message, and data—without hand-building response()->json([...]) in every endpoint.

Implementation Patterns

Use the builder fluently for non-default responses

Convenience helpers are fine for simple cases, but the fluent builder is the day-to-day tool because it makes HTTP status, application code, message, and payload explicit:

return ResponseBuilder::asSuccess()
    ->withHttpCode(201)
    ->withMessage('User created')
    ->withData($user)
    ->build();
return ResponseBuilder::asError()
    ->withCode(ApiCode::VALIDATION_FAILED)
    ->withHttpCode(422)
    ->withMessage('Validation failed')
    ->withData(['errors' => $validator->errors()])
    ->build();

Keep HTTP status and application code deliberately separate. Clients should branch on the application code or success flag, while infrastructure and monitoring can use the HTTP status.

Centralize error codes and mappings

Treat ApiCode constants as a public API contract. Add new codes rather than renumbering existing ones. Map codes to HTTP statuses and human-readable messages in one place—either the package config or a small application service that wraps the builder. Avoid scattering magic integers through controllers.

A good pattern is a thin ApiResponse service or base controller method:

protected function ok(mixed $data = null, string $message = 'OK')
{
    return ResponseBuilder::asSuccess()
        ->withData($data)
        ->withMessage($message)
        ->build();
}

protected function fail(int $code, int $httpCode, string $message, mixed $data = null)
{
    return ResponseBuilder::asError()
        ->withCode($code)
        ->withHttpCode($httpCode)
        ->withMessage($message)
        ->withData($data)
        ->build();
}

This keeps controllers readable and makes future package replacement less painful.

Integrate with the exception handler

The highest-leverage integration is rendering API exceptions through the same envelope. In app/Exceptions/Handler.php, handle validation, authentication, authorization, and not-found exceptions for JSON requests before delegating to the parent handler:

public function render($request, Throwable $e)
{
    if ($request->expectsJson()) {
        if ($e instanceof ValidationException) {
            return ResponseBuilder::asError()
                ->withCode(ApiCode::VALIDATION_FAILED)
                ->withHttpCode(422)
                ->withMessage('Validation failed')
                ->withData(['errors' => $e->errors()])
                ->build();
        }

        if ($e instanceof AuthenticationException) {
            return ResponseBuilder::asError()
                ->withCode(ApiCode::UNAUTHORIZED)
                ->withHttpCode(401)
                ->withMessage('Unauthenticated')
                ->build();
        }
    }

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

Be selective: do not convert every exception into a generic API response in production, or you lose framework logging and debugging behavior.

Keep formatting out of domain/services

Let services return models, DTOs, arrays, or paginators. Let controllers or a response layer decide how to wrap them. This avoids coupling business logic to the package facade and makes it easier to test services without HTTP concerns.

Handle pagination explicitly

Do not assume the package will infer your preferred pagination shape. Pick one convention and apply it consistently:

$users = User::paginate();

return ResponseBuilder::asSuccess()
    ->withData([
        'items' => $users->items(),
        'meta' => [
            'current_page' => $users->currentPage(),
            'per_page' => $users->perPage(),
            'total' => $users->total(),
        ],
    ])
    ->build();

Document that shape for frontend consumers.

Test the envelope, not just the payload

Use Laravel’s JSON assertions to lock down the contract:

$response->assertOk()
    ->assertJsonPath('success', true)
    ->assertJsonPath('code', 0)
    ->assertJsonStructure(['success', 'code', 'message', 'data']);

For errors, assert the application code and HTTP status separately:

$response->assertStatus(404)
    ->assertJsonPath('success', false)
    ->assertJsonPath('code', ApiCode::USER_NOT_FOUND);

Avoid asserting exact translated message strings unless localization is part of the test contract.

Gotchas and Tips

  • Zero usually means success. Application code 0 is conventionally the success code; non-zero codes are errors. Do not use 0 for an application error.
  • HTTP status is not derived automatically from every application code. If you add a code, verify that it has an HTTP mapping or pass withHttpCode() explicitly. Otherwise clients may receive a misleading status.
  • Respect the configured code range. If config/response_builder.php defines min_code/max_code, codes outside that range may be rejected. Keep application codes inside the configured range.
  • Clear config cache after changes. After editing config/response_builder.php, run php artisan config:clear locally. In production, rebuild with php artisan config:cache.
  • Disable debug output in production. Debug details can leak stack traces, SQL, or internal paths. Tie debug behavior to APP_DEBUG only in local/staging, and assert debug keys conditionally in tests.
  • Do not double-wrap responses. If a method already returns the package’s JsonResponse, do not pass it through response()->json(...) again.
  • Do not reuse builder state casually. Construct a fresh fluent chain per response. Reusing a partially configured builder can leak message, data, or debug flags between responses.
  • Keep exception details out of message. Put validation details or safe diagnostic context in data; log the real exception. Avoid exposing raw exception messages to API consumers.
  • Localization may change messages. If messages are translated by the package or your config, tests should assert codes and structure rather than exact prose.
  • Use the package as an adapter, not a foundation. Keep a small application-level response helper around it. That gives you one place to change if you upgrade major versions, replace the package, or need a different envelope.
  • Pin the major version and read the changelog before upgrading. The fluent builder and config keys are the main integration surface; verify them against the installed version rather than relying on examples from older releases.
  • Main extension points are BaseApiCodes, the package config, and your own response service. Avoid forking the package for small formatting changes; wrap it instead.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin