REST API design tips
Tips
- Always have separate delete endpoint to delete a resource even for soft delete where you just set is_deleted flag to true.
- Never allow PATCH to delete resource.
- Send 204 to already deleted resource without raising exception of resource already deleted ( Idempotency principles).
Naming Conventions
Use kebab-case for URL paths and snake_case or camelCase for query parameters.
When using hyphens in query params, it can clash with minus‑sign semantics in some contexts. most libraries won't parse them as identifiers. Hence, avoid hyphens in query keys.
shellPOST /sign-in POST /sign-in?redirectUrl=https://example.com/dashboard # camelCase query params when JS frameworks POST /sign_in?redirect_url=https://example.com/dashboard # snake_case query params when Python, Ruby, etc.
2. Query Parameters
You have two main, equally valid options—pick one and be consistent:
A. snake_case
Lowercase + underscores
Common in many REST APIs and frameworks
Example:
GET /products?category=electronics&price_min=100&sort_by=popularity
B. camelCase
Lowercase first word, then capital letters for subsequent words
Aligns with many JavaScript front‑end conventions
Example:
GET /products?category=electronics&priceMin=100&sortBy=popularity
A Note on Hyphens in Query Strings
- Hyphens can clash with minus‑sign semantics in some contexts; most libraries won't parse them as identifiers.
- For that reason, avoid hyphens in query keys.
3. General Tips
Keep names semantic
- Use real words:
sortBy
,filter
,pageSize
,lang
- Use real words:
Document your choice in your API style guide so every team follows the same format.
Versioning: if you version in the URL, do it as its own segment:
/v1/products
API endpoint should be resource not action
https://api.example.com/tasks
https://api.example.com/get-tasks
Collection name should be plural
https://api.example.com/tasks
https://api.example.com/tasks/{task_id}
https://api.example.com/task
https://api.example.com/task/{task_id}
Group auth related endpoints under /auth
prefix
POST /auth/register
POST /auth/login
POST /auth/verification-emails
GET /auth/verification-emails/{token}
POST /auth/forgot-password
POST /auth/reset-password
Entity CRUD endpoints conventions
GET /tasks # Get all tasks
POST /tasks # Create new task
GET /tasks/{task_id} # Get task by id
PUT /tasks/{task_id} # Update task by id (Replace)
PATCH /tasks/{task_id} # Partial update task by id
DELETE /tasks/{task_id} # Delete task by id
POST /tasks/batch # Create multiple tasks
GET /tasks/{task_id}/comments # Get all comments for task
POST /tasks/{task_id}/comments # Create new comment for task
GET /comments/{comment_id} # Get comment by id for task
PUT /comments/{comment_id} # Update comment by id for task
PATCH /comments/{comment_id} # Partial update comment by id for task
DELETE /comments/{comment_id} # Delete comment by id for task
Don't go deeper than 3 levels: collection/resource/collection
https://api.example.com/tasks/1/comments
https://api.example.com/tasks/1/comments/1/replies
Filter, Sorting & Pagination
<!-- Filter: Use key-value pair -->
https://api.example.com/tasks?completed=true
https://api.example.com/users?last_name=john&age=36
<!-- Filter: Provide filtering on specific property/column/field with global search -->
https://api.example.com/users?q=john
https://api.example.com/tasks?q=john&fields=username,email
<!--
Pagination: Prefer using limit-offset instead of page-size
?page=1&size=10 => fine for simple pagination
🤔 => How to get 10 items starting from 4th item with page-size?
-->
https://api.example.com/tasks?limit=10&offset=0
https://api.example.com/tasks?limit=10&offset=3 <- You can get 10 items starting from 4th item with limit-offset
Instead of returning list return object with name for making your response future proof
# --- Before
[
{ "id": 1, "title": "Buy Tomato", "is_completed": False },
{ "id": 2, "title": "Learn Sanskrit", "is_completed": False },
]
# --- After
{
"tasks": [
{ "id": 1, "title": "Buy Tomato", "is_completed": False },
{ "id": 2, "title": "Learn Sanskrit", "is_completed": False },
],
"total": 2,
}
Don't return map structure in response, instead return list of objects
# --- Before
{
"john": [
{ "id": 1, "title": "Buy Tomato", "is_completed": False },
{ "id": 2, "title": "Learn Sanskrit", "is_completed": False },
],
"jane": [
{ "id": 3, "title": "Buy Apple", "is_completed": False },
{ "id": 4, "title": "Finish reading book", "is_completed": False },
]
}
# --- After
{
"tasks": [
{ "id": 1, "title": "Buy Tomato", "is_completed": False, "user": "john" },
{ "id": 2, "title": "Learn Sanskrit", "is_completed": False, "user": "john" },
{ "id": 3, "title": "Buy Apple", "is_completed": False, "user": "jane" },
{ "id": 4, "title": "Finish reading book", "is_completed": False, "user": "jane" },
],
"total": 4
}
Async/Longer Operations
- For longer processes (
POST
{ processId: 99 }
=>/long-process/create
) Send202
status code to indicate that request has been accepted for processing- Add status endpoint URL in response header
Location
to check the status of the operation
- Add status endpoint URL in response header
- Expose
GET
(/long-process/status/99
) endpoint to check the status of the operationReturn
200
status code if operation isn't completed with any of the below response:Provide status of the operation
{ status: "In Progress" }
Provide estimated time to complete
{ "time_to_complete_in_minutes": "15" }
You can also provide link to cancel/stop the operation
{ "rel": "cancel", "method": "delete", "href": "/long-process/cancel/99" }
Return 303 status code with
Location
header with URL to created resource once operation is completed{ "status": "completed" }
API response should be generic and agnostic
- Even if you are building API for a specific client, make sure your API response is generic and agnostic
- Don't return client specific data in response. Doing so will make your API tightly coupled with the client and requires changing both client and server code whenever requirement changes.
- E.g. For chat page, instead of single endpoint for chat details, chat messages, meta data, etc. create separate endpoints for each of them.
SEO
- Hyphens improve keyword recognition in URL (excluding query parameters). E.g.
/product-details