REST API Design
Resource naming, HTTP verb semantics, status codes, consistent response shapes, and versioning for production-grade APIs.
Resource Naming
URLs are nouns, not verbs. Resources are plural. Hierarchy expresses ownership.
| Rule | Wrong | Right |
|---|---|---|
| Nouns, not verbs | POST /createPayment |
POST /payments |
| Plural collections | GET /webhook |
GET /webhooks |
| Nested for ownership | GET /events?payment_id=42 |
GET /payments/42/events |
| kebab-case for multi-word | /paymentEvents |
/payment-events |
| Actions as sub-resources | POST /cancelPayment/42 |
POST /payments/42/cancellation |
| No file extensions | GET /payments.json |
GET /payments + Accept: application/json |
HTTP Verb Semantics
The verb expresses what to do; the URL expresses what to do it to. Use the right verb — clients and caches depend on it.
| Verb | Meaning | Idempotent? | Body? | Use for |
|---|---|---|---|---|
| GET | Read | Yes | No | Fetch resource or collection |
| POST | Create | No | Yes | New resource; server assigns ID |
| PUT | Replace | Yes | Yes | Full update; client supplies complete representation |
| PATCH | Partial update | No | Yes | Update one or a few fields |
| DELETE | Remove | Yes | No | Delete a resource |
HTTP Status Codes
Status codes are part of the API contract. Be precise — "it worked" vs "it worked but nothing changed" vs "your fault" vs "our fault" all matter.
2xx — Success
4xx — Client Error
5xx — Server Error
Consistent Response Shapes
Clients shouldn't guess your response format. Use consistent envelopes for success, errors, and paginated lists.
Single resource — 200
{
"data": {
"id": "evt_abc123",
"type": "payment.completed",
"amount": 25000,
"currency": "usd",
"created_at": "2026-04-21T14:00:00Z"
}
}
Validation error — 422
{
"message": "The given data was invalid.",
"errors": {
"amount": [
"The amount must be a positive integer."
],
"currency": [
"The currency field is required."
]
}
}
Paginated list — 200
{
"data": [ ... ],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 143,
"last_page": 8
},
"links": {
"next": "/payments?page=2",
"prev": null
}
}
JsonResource and ResourceCollection produce these shapes automatically. Use them — don't build custom array responses by hand.
API Versioning
Version APIs from day one. Breaking changes to a deployed API break clients. A version prefix gives you freedom to evolve.
- Removing or renaming a field
- Changing a field's type (
string→int) - Changing a status code meaning
- Making an optional field required
- Changing URL structure
- New optional fields in response
- New optional query parameters
- New endpoints
- Adding new status codes for new conditions
- Looser validation rules
Version in the URL path — simple, explicit, cache-friendly:
// routes/api.php
Route::prefix('v1')->group(function () {
Route::apiResource('payments', PaymentController::class);
Route::apiResource('webhooks', WebhookController::class);
});
Route::prefix('v2')->group(function () {
Route::apiResource('payments', V2\PaymentController::class);
});