haseebmirza/laravel-api-response

A simple and consistent API response helper for Laravel applications.

Maintainers

Package info

github.com/haseebmirza/laravel-api-response

pkg:composer/haseebmirza/laravel-api-response

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-08-06 06:48 UTC

This package is auto-updated.

Last update: 2026-08-06 06:53:48 UTC


README

Latest Version on Packagist License PHP Version

A lightweight, zero-config package that gives your Laravel API a consistent JSON response format — so every endpoint speaks the same language.

Why?

Without a standard response helper, API responses across your app end up looking different — some return status, others return success, error formats vary, and pagination metadata is inconsistent. This package solves that with a single static class and an optional trait.

Before:

// Controller A
return response()->json(['status' => 'ok', 'result' => $users], 200);

// Controller B
return response()->json(['success' => true, 'data' => $post, 'msg' => 'Done']);

// Controller C
return response()->json(['error' => 'Not found'], 404);

After:

return ApiResponse::success($users);
return ApiResponse::created($post);
return ApiResponse::notFound();

Every response follows the same structure. Every time.

Requirements

  • PHP 8.1+
  • Laravel 10, 11, 12, or 13

Installation

composer require haseebmirza/laravel-api-response

The service provider is auto-discovered — no manual registration needed.

Quick Start

use HaseebMirza\ApiResponse\ApiResponse;

class UserController extends Controller
{
    public function index()
    {
        return ApiResponse::success(User::all(), 'Users retrieved');
    }

    public function store(StoreUserRequest $request)
    {
        $user = User::create($request->validated());
        return ApiResponse::created($user);
    }

    public function show(User $user)
    {
        return ApiResponse::success($user);
    }

    public function destroy(User $user)
    {
        $user->delete();
        return ApiResponse::noContent();
    }
}

Example JSON response:

{
    "success": true,
    "message": "Users retrieved",
    "data": [
        {
            "id": 1,
            "name": "Alice"
        }
    ]
}

Usage

Choose The Right Place For Extra Data

Use each part of the response for a specific purpose:

  • data: the main business payload such as user, order, token payload, or collection
  • meta: structured metadata related to the response such as pagination, API version, filters, or source
  • custom top-level fields: optional extra fields such as request_id, debug, trace_id, links, or anything your frontend/client needs outside meta

Example:

return ApiResponse::success(
    ['user' => $user],
    'Profile loaded',
    200,
    ['version' => 'v1', 'source' => 'mobile'],
    ['request_id' => 'req_123', 'debug' => app()->isLocal()]
);

Static Class

Use ApiResponse directly anywhere in your application:

use HaseebMirza\ApiResponse\ApiResponse;

// Success (HTTP 200)
return ApiResponse::success($data, 'Operation successful');

// Success with custom status code
return ApiResponse::success($data, 'Accepted', 202);

// Success with extra metadata
return ApiResponse::success($data, 'OK', 200, [
    'version' => '2.1',
    'request_id' => 'req_abc123',
]);

// Success with custom top-level fields
return ApiResponse::success($data, 'OK', 200, [
    'version' => '2.1',
], [
    'request_id' => 'req_abc123',
    'debug' => true,
]);

// Created (HTTP 201)
return ApiResponse::created($resource);

// No Content (HTTP 204, empty body)
return ApiResponse::noContent();

// Error (HTTP 400)
return ApiResponse::error('Invalid request');

// Not Found (HTTP 404)
return ApiResponse::notFound('User not found');

// Unauthorized (HTTP 401)
return ApiResponse::unauthorized('Invalid credentials');

// Forbidden (HTTP 403)
return ApiResponse::forbidden();

// Validation Error (HTTP 422)
return ApiResponse::validationError($validator->errors());

// Server Error (HTTP 500)
return ApiResponse::serverError();

Common Real-World Snippets

1. Return a single resource

return ApiResponse::success($user, 'User fetched successfully');

2. Return a newly created resource

$post = Post::create($request->validated());

return ApiResponse::created($post);

3. Return validation errors

$validator = Validator::make($request->all(), [
    'email' => ['required', 'email'],
    'password' => ['required', 'min:8'],
]);

if ($validator->fails()) {
    return ApiResponse::validationError($validator->errors());
}

4. Return custom top-level fields

return ApiResponse::success(
    $order,
    'Order fetched',
    200,
    ['version' => 'v1'],
    ['request_id' => (string) Str::uuid(), 'trace_id' => 'trace_456']
);

5. Return an API token after login

$token = $user->createToken('api')->plainTextToken;

return ApiResponse::withToken(
    $token,
    $user,
    'Bearer',
    'Login successful',
    ['source' => 'mobile'],
    ['request_id' => 'req_login_123']
);

6. Return an empty 204 response

return ApiResponse::noContent();

noContent() returns HTTP 204 with an empty response body. That matches HTTP semantics and avoids clients discarding a JSON payload.

Pagination

Wrap Laravel's paginator to include pagination metadata automatically:

$users = User::where('active', true)->paginate(15);

return ApiResponse::paginated($users, 'Active users');

Response:

{
    "success": true,
    "message": "Active users",
    "data": [
        {"id": 1, "name": "Alice"},
        {"id": 2, "name": "Bob"}
    ],
    "meta": {
        "current_page": 1,
        "last_page": 5,
        "per_page": 15,
        "total": 74,
        "from": 1,
        "to": 15
    }
}

You can also pass extra metadata:

return ApiResponse::paginated($users, 'Filtered users', [
    'filter' => 'active',
    'sort' => 'created_at',
]);

And if you need custom top-level fields outside meta:

return ApiResponse::paginated($users, 'Filtered users', [
    'filter' => 'active',
], [
    'request_id' => 'req_abc123',
]);

Authentication / Token Response

Return a token with optional user data — works with Sanctum, JWT, or any token-based auth:

// With user data
$token = $user->createToken('api')->plainTextToken;
return ApiResponse::withToken($token, $user);

// Token only
return ApiResponse::withToken($token);

// Custom token type
return ApiResponse::withToken($apiKey, $user, 'API-Key');

// Token response with metadata and custom top-level fields
return ApiResponse::withToken($token, $user, 'Bearer', 'Authenticated successfully', [
    'request_source' => 'mobile',
], [
    'request_id' => 'req_abc123',
]);

Response:

{
    "success": true,
    "message": "Authenticated successfully",
    "data": {
        "token": "1|abc123...",
        "token_type": "Bearer",
        "user": {
            "id": 1,
            "name": "Haseeb",
            "email": "haseeb@example.com"
        }
    }
}

Controller Trait

Prefer $this-> syntax? Add the ApiResponder trait to your controller or a base controller:

use HaseebMirza\ApiResponse\Traits\ApiResponder;

class Controller extends BaseController
{
    use ApiResponder;
}

Then use it in any controller:

class OrderController extends Controller
{
    public function index()
    {
        $orders = Order::paginate(20);
        return $this->paginatedResponse($orders, 'Orders retrieved');
    }

    public function store(StoreOrderRequest $request)
    {
        $order = Order::create($request->validated());
        return $this->createdResponse($order);
    }

    public function show(Order $order)
    {
        return $this->successResponse($order);
    }

    public function update(UpdateOrderRequest $request, Order $order)
    {
        $order->update($request->validated());
        return $this->successResponse($order, 'Order updated');
    }

    public function destroy(Order $order)
    {
        $order->delete();
        return $this->noContentResponse();
    }
}

Trait example with extra fields:

return $this->successResponse(
    $order,
    'Order loaded',
    200,
    ['version' => 'v1'],
    ['request_id' => 'req_order_123']
);

Response Format

All responses follow a consistent JSON structure:

Success:

{
    "success": true,
    "message": "Success",
    "data": { }
}

Success with metadata:

{
    "success": true,
    "message": "Success",
    "data": { },
    "meta": { }
}

Success with custom top-level fields:

{
    "success": true,
    "message": "Success",
    "data": { },
    "meta": { },
    "request_id": "req_123",
    "debug": true
}

No content:

HTTP 204 with an empty body.

Error:

{
    "success": false,
    "message": "Error message"
}

Error with details:

{
    "success": false,
    "message": "Validation failed",
    "errors": { }
}

API Reference

Static Methods (ApiResponse)

Method Status Description
success($data, $message, $statusCode, $meta, $additional) 200 General success
created($data, $message, $meta, $additional) 201 Resource created
noContent($message, $additional) 204 No content, empty non-JSON body
error($message, $statusCode, $errors, $additional) 400 General error
unauthorized($message, $additional) 401 Authentication required
forbidden($message, $additional) 403 Access denied
notFound($message, $additional) 404 Resource not found
validationError($errors, $message, $additional) 422 Validation failed
serverError($message, $additional) 500 Internal server error
paginated($paginator, $message, $meta, $additional) 200 Paginated collection
withToken($token, $user, $tokenType, $message, $meta, $additional) 200 Auth token response

Reserved keys:

  • success
  • message
  • data
  • meta
  • errors

If a developer passes any of these inside $additional, the package ignores them so the response contract stays stable.

Trait Methods (ApiResponder)

Trait Method Calls
$this->successResponse(...) ApiResponse::success()
$this->errorResponse(...) ApiResponse::error()
$this->createdResponse(...) ApiResponse::created()
$this->noContentResponse(...) ApiResponse::noContent()
$this->notFoundResponse(...) ApiResponse::notFound()
$this->unauthorizedResponse(...) ApiResponse::unauthorized()
$this->forbiddenResponse(...) ApiResponse::forbidden()
$this->validationErrorResponse(...) ApiResponse::validationError()
$this->serverErrorResponse(...) ApiResponse::serverError()
$this->paginatedResponse(...) ApiResponse::paginated()
$this->tokenResponse(...) ApiResponse::withToken()

Testing

composer test

If your machine has multiple PHP versions installed, make sure you run tests with a supported version for your current dependency lock file. Example:

php83 $(which composer) test

Recommended pre-publish check:

composer test

Then install the package in a fresh Laravel app and verify a real route:

Route::get('/test-package', function () {
    return \HaseebMirza\ApiResponse\ApiResponse::success(
        ['ok' => true],
        'Package working',
        200,
        ['version' => 'v1'],
        ['request_id' => 'req_demo_123']
    );
});

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

The MIT License (MIT). Please see LICENSE for more information.