REST APIs are where Laravel genuinely shines — but the gap between a tutorial API and a production API is authentication, consistency, and the dozen small decisions that determine whether the mobile team building against your API loves you or curses you. After building APIs for mobile apps, SPAs, and third-party integrations, these are the practices I consider non-negotiable.

Sanctum vs Passport: Stop Overthinking It

The most common question, with the simplest answer: use Sanctum unless you specifically need OAuth2 server capabilities. Sanctum covers token-based auth for mobile apps and SPAs with a fraction of Passport's complexity — no clients table, no key management, no OAuth ceremony. Passport earns its place only when third parties need to build applications against your API with proper OAuth flows (authorization codes, client credentials, scoped consent screens).

// Sanctum: issue a token on login
$token = $user->createToken('mobile-app', ['orders:read'])->plainTextToken;

// Protect routes
Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('orders', OrderController::class);
});

Token abilities (the second argument) are Sanctum's quietly powerful feature — scope tokens to what each client actually needs instead of issuing all-powerful tokens everywhere.

Version from Day One

Once a mobile app ships with your API baked in, you cannot change responses without breaking users who never update. Versioning costs one route-prefix decision today and saves a rewrite later:

Route::prefix('v1')->group(base_path('routes/api_v1.php'));

When a breaking change becomes necessary, v2 routes appear alongside v1, old clients keep working, and you deprecate on your schedule instead of your users'.

API Resources: Your Response Contract

Never return Eloquent models directly. toArray() on a model exposes every column — including ones you add next month without thinking about the API. Resources make the response shape explicit and transformable:

class OrderResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'     => $this->id,
            'status' => $this->status,
            'total'  => $this->total,
            'items'  => OrderItemResource::collection($this->whenLoaded('items')),
        ];
    }
}

whenLoaded() deserves special attention: it only includes the relationship when it was eager-loaded, which prevents resources from silently triggering N+1 queries — the most common performance leak in Laravel APIs.

Consistent Errors, Validated Input

Consumers should be able to handle every error the same way. Laravel's form requests give you validation with automatic 422 responses; your job is to keep the rest consistent — 401 for unauthenticated, 403 for forbidden, 404 for missing resources, and a JSON body with a stable shape for all of them. Register a custom exception renderer for API routes so a stray exception never leaks an HTML error page (or a stack trace) to a JSON client.

Pagination, Filtering, and Sorting: Decide the Conventions Once

Every collection endpoint eventually needs pagination, filtering, and sorting — and nothing erodes an API's usability faster than each endpoint inventing its own query-parameter dialect. Pick conventions on day one and apply them everywhere:

GET /api/v1/orders?status=processing&sort=-created_at&per_page=25&page=2

Laravel's paginate() already returns meta and links blocks that mobile clients can consume directly. For filtering, whitelist the filterable fields explicitly — passing request input straight into where() clauses is how APIs leak columns they never meant to expose and acquire accidental full-table scans. The spatie/laravel-query-builder package formalizes exactly this pattern (allowed filters, allowed sorts, allowed includes) and is worth its dependency weight on any API with more than a handful of list endpoints.

One convention teams skip and regret: cap per_page. A client asking for per_page=100000 should get 100 rows and a clear response, not a memory spike and a 30-second query.

Tests and Documentation Are Part of the API

An untested API contract is a rumor. Laravel's HTTP tests make endpoint coverage almost free, and the assertions worth writing are about the contract, not the framework:

public function test_orders_index_returns_expected_shape(): void
{
    $user = User::factory()->has(Order::factory()->count(3))->create();

    $this->actingAs($user)
        ->getJson('/api/v1/orders')
        ->assertOk()
        ->assertJsonStructure(['data' => [['id', 'status', 'total']], 'meta']);
}

Cover the unhappy paths too: unauthenticated requests return 401 JSON (not a login redirect), validation failures return 422 with field-level errors, and requesting another user's resource returns 404 or 403 — whichever your policy chooses, consistently. For documentation, generate it from the code instead of maintaining it by hand: Scribe reads your routes, form requests, and resources and emits OpenAPI + readable HTML docs. A documented API gets integrated in an afternoon; an undocumented one generates a week of Slack questions.

Rate Limiting and the Production Checklist

Laravel's rate limiter takes minutes to configure and saves you from both abuse and accidental self-DDoS by a buggy client:

RateLimiter::for('api', fn (Request $request) =>
    Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()));

Beyond that, my pre-launch checklist: tokens expire and can be revoked, all timestamps in UTC ISO-8601, pagination on every collection endpoint (paginate(), never all()), queries eager-loaded, and the API documented — even a Postman collection beats nothing.

Final Thoughts

A production Laravel API is not clever — it is consistent. Sanctum for auth, versioned routes, resources as the response contract, uniform errors, and rate limiting cover 90% of what separates professional APIs from tutorial code. If you need an API designed and built properly — for a mobile app, a SPA, or third-party integrations — that is exactly the kind of project I take on through my web development services.

Related Service

💻 Web Development

Custom websites and web applications built with PHP, Laravel, WordPress, and React — fast, secure, scalable, and tailored to your business goals.

Explore Web Development →