REST API Design Rules I Wish Someone Had Told Me in Year One
The first API I built professionally had endpoints like /getUser, /updateUserInfo, and /do_delete_post. They all used POST. Errors came back as {"success": false, "msg": "something went wrong"}. There was no versioning. The mobile team sent me a four-page document of complaints after three months.
Here is what I know now after nine years of API work.
Name Resources, Not Actions
URLs should identify a resource (a noun). The HTTP verb tells you what you are doing to it:
// Bad — actions in the URL
POST /createPost
GET /getPostById/5
POST /deletePost/5
// Good — resources + verbs
POST /posts → create
GET /posts/5 → read
PUT /posts/5 → replace
PATCH /posts/5 → partial update
DELETE /posts/5 → delete
Use HTTP Status Codes Correctly
Stop returning 200 OK with an error in the body. The status code is part of the contract:
201— resource created (use on successful POST)204— success with no body (use on DELETE)400— bad request data401— not authenticated403— authenticated but not authorized422— validation failed429— rate limited
Consistent Error Responses Everywhere
{
"message": "The given data was invalid.",
"errors": {
"email": ["The email field is required."],
"name": ["The name must be at most 255 characters."]
}
}
Every error, from every endpoint, looking exactly like this. The mobile team should never have to write conditional error parsing logic.
Version From Day One
https://api.example.com/v1/posts
https://api.example.com/v2/posts
When v2 introduces a breaking change, v1 keeps working. Clients migrate on their schedule. I use URL versioning — header versioning is technically purer but harder to work with in practice.
Always Paginate Collections
{
"data": [...],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 4200,
"last_page": 210
},
"links": {
"first": "/api/v1/posts?page=1",
"next": "/api/v1/posts?page=2",
"last": "/api/v1/posts?page=210"
}
}
An endpoint that returns an unbounded collection is a slow-motion disaster. The table has 10 rows today. It will have 10 million in two years.
Response Envelope Consistency
// GET /posts — collection
{ "data": [{...}, {...}], "meta": {...} }
// GET /posts/5 — single item
{ "data": {...} }
// Never mix naked objects with enveloped ones
Use Query Parameters for Filtering and Sorting
GET /posts?status=published&author_id=5&sort=published_at&order=desc&q=laravel
Do not invent new endpoints for filtered views. One resource endpoint, composable query params.