REST API Best Practices
Why API Design Decisions Matter
An API (Application Programming Interface) is a contract between your server and every client that uses it — mobile apps, frontend code, third-party developers. Breaking that contract midway through a project is expensive and frustrating. Getting the design right from the start prevents months of refactoring.
Professional backend developers follow a set of community-accepted conventions that make APIs predictable, discoverable, and maintainable. This lesson covers the most important ones.
Consistent Naming: Plural Nouns and Kebab-Case
REST (Representational State Transfer) APIs revolve around resources — the things your API exposes. Use plural nouns for resource names, not verbs.
| Bad | Good |
|---|---|
| /getUser | /users |
| /createPost | /posts |
| /deleteComment | /comments |
For multi-word resources, use kebab-case (hyphens, not underscores or camelCase):
/blog-posts -- correct
/blog_posts -- avoid
/blogPosts -- avoid (this is a URL, not JavaScript)
HTTP methods already supply the verb:
GET /users -- list all users
POST /users -- create a user
GET /users/123 -- get user 123
PUT /users/123 -- replace user 123
PATCH /users/123 -- partially update user 123
DELETE /users/123 -- delete user 123
API Versioning
APIs evolve. When you change a response shape or remove a field, you risk breaking existing clients. Versioning lets you ship breaking changes without destroying old integrations.
The most common approach is a URL prefix:
/api/v1/users
/api/v2/users
In Express, you can namespace versions cleanly:
const express = require('express');
const app = express();
const v1Router = require('./routes/v1');
const v2Router = require('./routes/v2');
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
Keep old versions alive until you have confirmed that no clients use them. Always document when a version will be sunset.
Pagination, Filtering, and Sorting
Never return an unlimited list of resources. If your users table grows to 100,000 rows, a GET /users that returns all of them will crash your server and the client.
Page-Based Pagination
GET /users?page=2&limit=20
app.get('/users', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const skip = (page - 1) * limit;
const users = await User.find().skip(skip).limit(limit);
const total = await User.countDocuments();
res.json({
data: users,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit),
},
});
});
Cursor-Based Pagination
For very large, real-time datasets (like social feeds), cursor-based pagination is more reliable because it does not drift when new records are inserted:
GET /posts?cursor=eyJpZCI6MTIzfQ&limit=20
The cursor is typically a Base64-encoded last-seen record ID.
Filtering and Sorting
GET /products?category=electronics&minPrice=5000&sort=price&order=asc
app.get('/products', async (req, res) => {
const { category, minPrice, sort = 'createdAt', order = 'desc' } = req.query;
const filter = {};
if (category) filter.category = category;
if (minPrice) filter.price = { $gte: Number(minPrice) };
const products = await Product.find(filter).sort({ [sort]: order === 'asc' ? 1 : -1 });
res.json({ data: products });
});
HTTP Status Codes Used Correctly
Status codes tell the client what happened. Using the wrong code confuses developers and breaks error-handling logic.
| Code | Meaning | When to use it |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST that creates a resource |
| 204 | No Content | Successful DELETE (no body to return) |
| 400 | Bad Request | Invalid input from the client |
| 401 | Unauthorized | Not authenticated |
| 403 | Forbidden | Authenticated but lacks permission |
| 404 | Not Found | Resource does not exist |
| 422 | Unprocessable Entity | Validation error |
| 500 | Internal Server Error | Something went wrong on the server |
The Response Envelope Pattern
Wrapping all responses in a consistent shape makes client code simpler and more predictable:
// Success response
res.status(200).json({
success: true,
data: { id: 1, name: 'Amara' },
message: 'User retrieved successfully',
});
// Error response
res.status(404).json({
success: false,
data: null,
message: 'User not found',
});
HATEOAS: Hypermedia as the Engine of Application State
HATEOAS (Hypermedia as the Engine of Application State) is a REST constraint that says responses should include links to related actions, so clients can navigate the API without hard-coding URLs.
{
"data": {
"id": 123,
"name": "Amara Okafor",
"links": {
"self": "/api/v1/users/123",
"posts": "/api/v1/users/123/posts",
"delete": "/api/v1/users/123"
}
}
}
Full HATEOAS is complex to implement; start with the envelope pattern and add links incrementally.
Practice Exercise
Build a GET /api/v1/products endpoint that:
- Accepts
page,limit,category, andsortquery parameters - Returns a response envelope with
data,pagination,success, andmessagefields - Returns a 400 status if
limitis greater than 100 - Uses correct HTTP status codes for all responses
Try it yourself
Key Takeaways
- Use plural nouns and kebab-case for resource names, and let HTTP methods supply the verb.
- Version your API with a URL prefix like /api/v1/ to prevent breaking changes for existing clients.
- Always paginate list endpoints; page-based pagination is simple, cursor-based is better for real-time data.
- Use HTTP status codes correctly: 201 for creation, 204 for deletion, 400 for client errors, 500 for server errors.
- The response envelope pattern wraps all responses in a consistent shape with success, data, and message fields.
Quick Quiz
1.Which URL follows REST API naming conventions correctly?
2.What HTTP status code should a successful POST request that creates a new resource return?
3.Why is cursor-based pagination preferred over page-based pagination for large real-time datasets?
4.What is the purpose of HATEOAS (Hypermedia as the Engine of Application State)?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx