Designing REST APIs: Naming, Status Codes, and Structure
A well-designed REST API is predictable and easy to consume. Consistency in naming, status codes, and response shape helps frontend and mobile developers integrate quickly and avoid confusion. Here’s a practical guide.
Naming: resources and URLs
Use nouns for resources and plural for collections. HTTP methods (GET, POST, PUT, PATCH, DELETE) describe the action—so you don’t need verbs in the URL.
Good vs bad
✅ GET /users → list users
✅ GET /users/42 → one user
✅ POST /users → create user
✅ PUT /users/42 → replace user
✅ PATCH /users/42 → partial update
✅ DELETE /users/42 → delete user
❌ GET /getUsers
❌ POST /createUser
❌ GET /user/42/getOrders → prefer GET /users/42/ordersFor nested resources that truly “belong” to a parent (e.g. a user’s orders), use paths like /users/42/orders. Keep one naming style (e.g. kebab-case or camelCase) across the whole API.
Status codes
Status codes tell the client what happened. Use them consistently so clients can handle success and errors reliably.
- 2xx Success: 200 OK (generic success), 201 Created (after POST), 204 No Content (success with no body).
- 4xx Client errors: 400 Bad Request (validation), 401 Unauthorized (not logged in), 403 Forbidden (no permission), 404 Not Found.
- 5xx Server errors: 500 Internal Server Error—use when something unexpected broke on the server; avoid for validation or “not found” cases.
Express example: naming + status codes
// Express example: naming + status codes
const express = require('express');
const app = express();
// GET collection → 200 OK
app.get('/api/courses', (req, res) => {
const courses = [{ id: 1, name: 'MERN Stack' }];
res.status(200).json({ data: courses });
});
// GET one → 200 or 404
app.get('/api/courses/:id', (req, res) => {
const course = findCourseById(req.params.id);
if (!course) {
return res.status(404).json({ error: 'Course not found' });
}
res.status(200).json({ data: course });
});
// POST create → 201 Created
app.post('/api/courses', (req, res) => {
const newCourse = createCourse(req.body);
res.status(201).json({ data: newCourse });
});
// Validation error → 400 Bad Request
app.post('/api/courses', (req, res) => {
if (!req.body.name) {
return res.status(400).json({ error: 'name is required' });
}
// ...
});Response structure
Keep responses consistent. Many APIs use a data key for the payload and optional meta (pagination, timestamps). Errors use a dedicated shape (e.g. error withcode and message).
// Success
{ "data": { "id": 1, "name": "MERN Stack" }, "meta": { "timestamp": "..." } }
// Error
{ "error": { "code": "VALIDATION_ERROR", "message": "name is required" } }Takeaway
Use nouns and plurals for URLs, the right HTTP methods and status codes for each action, and a consistent JSON shape for success and errors. Your future self and anyone integrating your API will thank you.