
A good REST API is predictable, resource-oriented, secure, and easy to evolve. This guide covers URLs, HTTP semantics, errors, pagination, versioning, security, documentation, and Laravel implementation.
A good REST API feels unsurprising. A developer can predict an endpoint before opening the documentation, understand a response without reading server code, and recover from errors without guessing. That quality comes from consistent design decisions—not from adding more endpoints.
A strong API models business resources clearly, follows HTTP semantics, uses consistent representations, returns actionable errors, protects data, and evolves without breaking clients. Optimize for the developer consuming the API: names should be obvious, defaults safe, and exceptional behavior documented.
Model stable business concepts such as orders, customers, and invoices. Use plural nouns and express relationships through paths. Prefer /orders/42/items over RPC-style URLs such as /getOrderItems. Keep nesting shallow; deeply nested URLs are hard to authorize and maintain.
GET /api/v1/orders
POST /api/v1/orders
GET /api/v1/orders/42
PATCH /api/v1/orders/42
DELETE /api/v1/orders/42
GET reads without changing state. POST creates or triggers a non-idempotent operation. PUT replaces a resource and should be idempotent. PATCH partially updates it. DELETE removes it and should normally be safe to retry. Following RFC 9110 HTTP semantics makes clients, proxies, caches, and monitoring tools behave correctly.
Use 200 OK for successful reads or updates, 201 Created plus a Location header for creation, and 204 No Content when success has no body. Distinguish 400 malformed input, 401 missing or invalid authentication, 403 forbidden access, 404 missing resources, 409 conflicts, 422 validation failure, 429 rate limiting, and 5xx server failures.
Choose conventions once: field naming, date format, money representation, nullability, and envelope structure. Use ISO 8601 timestamps with time zones. Do not expose database columns accidentally; treat the response as a public contract.
{
"data": {
"id": 42,
"status": "paid",
"total": 129.90,
"currency": "USD"
}
}
Every error should be machine-readable and useful to a human. Include a stable type or code, a short title, the HTTP status, a safe explanation, field-level validation errors, and a request or trace ID. RFC 9457 Problem Details provides a standard format.
{
"type": "https://api.example.com/problems/validation",
"title": "Validation failed",
"status": 422,
"detail": "One or more fields are invalid.",
"errors": {"email": ["The email field is required."]}
}
Use explicit query parameters such as ?status=paid&sort=-created_at&page=2&per_page=25. Document allowed filters and sort fields. Set a maximum page size. Cursor pagination is preferable for large or rapidly changing datasets; offset pagination is convenient for small administrative lists.
Additive changes are usually safe: new optional fields, new endpoints, and new enum values when clients tolerate unknown values. Renaming fields, changing types, or removing behavior is breaking. Version deliberately—often with /api/v1—and publish a deprecation window and migration notes.
Require HTTPS, authenticate with an appropriate mechanism, and authorize every resource action. Validate input with allowlists, limit request sizes, rate-limit by identity and route sensitivity, and never return secrets or stack traces. Prefer opaque public IDs where enumeration creates risk. Log security-relevant events without logging credentials or personal data.
For safe reads, use Cache-Control, ETag, and conditional requests where appropriate. For concurrent updates, accept If-Match with an ETag or another version token so one client does not silently overwrite another. Idempotency keys make retried payment or creation requests safer.
Maintain an OpenAPI contract with examples for success, validation, authentication, and rate-limit responses. Use it for generated docs, client SDKs, schema validation, and contract tests. Add correlation IDs, structured logs, latency and error metrics, and distributed traces so production behavior is explainable.
Laravel provides a strong foundation. Route::apiResource creates conventional CRUD routes, Sanctum can authenticate clients, and the throttle middleware protects endpoints.
use App\Http\Controllers\Api\OrderController;
use Illuminate\Support\Facades\Route;
Route::prefix('v1')
->middleware(['auth:sanctum', 'throttle:api'])
->group(function () {
Route::apiResource('orders', OrderController::class);
});Use Form Request classes for validation and authorization, Eloquent policies for resource permissions, and API Resources to control the JSON contract. Keep controllers focused on HTTP orchestration while services or actions hold complex business logic.
public function show(Order $order): OrderResource
{
return new OrderResource($order);
}
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'status' => $this->status,
'total' => $this->total,
'currency' => $this->currency,
];
}
Usually no. Use nouns for resources and HTTP methods for actions. A domain command such as POST /orders/42/cancellation is acceptable when it represents a real business resource or state transition.
Every public API needs a compatibility strategy. A version in the URL is useful when breaking changes are likely; a small internal API may instead rely on strict additive evolution.
Consistency. A slightly imperfect convention applied everywhere is easier to consume than individually clever endpoints that behave differently.
No approved comments are visible yet. New community replies may wait for moderation.