1. Version from day one
Put the version in the path, such as /api/v1/orders. Changing a response shape later is painful for existing clients, and a version prefix gives you room to do it without breaking anyone.
2. Validate input with Form Requests
Keep validation out of controllers. A Form Request class holds the rules, authorizes the caller, and returns a 422 response with field-level errors automatically.
public function rules(): array
{
return [
'email' => ['required', 'email'],
'amount' => ['required', 'integer', 'min:1'],
];
}
3. Shape output with API Resources
Return data through Eloquent API Resources rather than raw models. You control exactly which fields are exposed, so adding a column to a table never leaks it into the API by accident.
4. Use one error format everywhere
Clients should not have to guess how errors look. Pick a single structure and use it for validation errors, authentication failures and server errors alike, with correct HTTP status codes: 400 or 422 for bad input, 401 for unauthenticated, 403 for forbidden, 404 for missing, 409 for conflicts, 429 for rate limits.
{
"message": "The given data was invalid.",
"errors": { "amount": ["The amount must be at least 1."] }
}
5. Paginate every list
An endpoint that returns "all orders" works in development and falls over in production. Use paginate() or cursor pagination for large, fast-changing data, and cap the page size on the server.
6. Authenticate and authorize deliberately
Laravel Sanctum covers token authentication for most first-party and mobile clients. Authentication says who the caller is; policies decide what they may do. Check ownership on every resource lookup so one user can never fetch another user's record by changing an ID.
7. Rate limit
Apply the built-in throttle middleware, with stricter limits on login and other sensitive endpoints. Return the standard 429 status so clients can back off.
8. Make unsafe retries safe
For operations that create things or move money, accept an Idempotency-Key header and store it under a unique index, so a client retry returns the first result instead of repeating the action. The same idea is covered in my post on payment webhooks.
9. Avoid N+1 queries in resources
Resources that touch relationships can trigger one query per row. Eager load with with() in the controller. See how to find and fix N+1 queries.
10. Write feature tests for the contract
- Happy path returns the documented status and JSON shape
- Invalid input returns 422 with field errors
- Unauthenticated and unauthorized calls are rejected
- Another user's resource returns 403 or 404
Need an API built to this standard? Have a look at the REST API development service.
Original article written by Saad Ali Khan. Code samples are illustrative and free to adapt in your own projects.