REST API Design Made Simple in Express.js
Why predictable routing matters, how to map HTTP methods to resources, and the anatomy of a clean API.
When you build your first API, the instinct is to name your routes exactly like the JavaScript functions you write.
You need to fetch all users, so you create GET /getAllUsers. You need to add a user, so you make POST /createNewUser. You need to delete one, so you write POST /deleteUser?id=5.
Technically, this works. The server receives a request, does the job, and sends a response. But as your application grows from three routes to thirty, this naming strategy becomes a nightmare. If a frontend developer wants to update a user's email, they have to guess: is it /updateUser, /edit-user, or /modifyUserById?
An API is fundamentally a communication contract between a client (the browser or mobile app) and a server. If that communication lacks rules, every new feature requires a meeting.
This is exactly the problem REST solves. REST (Representational State Transfer) isn't a library you install or a framework you download. It is a set of design conventions. It’s an agreement that if we structure our URLs and requests in a specific, predictable way, developers can use our API without having to constantly read the documentation.
Let’s look at how to build a clean, RESTful API in Express.js.
The Core Shift: Think in Nouns, Not Verbs
The foundational rule of REST is that your URLs should represent Resources (things), not Actions (verbs).
In a web application, a resource is any entity your system manages. If you are building a social network, your resources are Users, Posts, and Comments.
In a REST API, the URL strictly identifies the resource.
Bad:
/getUsers(Contains a verb)Good:
/users(Strictly a noun)
But if the URL only contains a noun, how does the server know what action the client wants to perform?
The Verbs: HTTP Methods
We rely on the HTTP protocol itself to provide the verbs. Every HTTP request carries a method. In a RESTful API, we map these standard HTTP methods directly to CRUD (Create, Read, Update, Delete) operations.
Here is the standard REST mapping:
GET: Read data. (Does not modify anything).
POST: Create new data.
PUT (or PATCH): Update existing data.
DELETE: Destroy data.
Notice the elegance here. The URL stays exactly the same, but the intent changes based on the HTTP method.
Designing the Routes: The users Example
Let’s put this into practice using Express.js. We are going to build a complete API for managing a users resource. Notice how clean and predictable the paths are.
const express = require('express');
const app = express();
app.use(express.json());
// 1. GET /users
// Action: Get a list of all users
app.get('/users', (req, res) => {
const users = db.getAllUsers();
res.status(200).json(users);
});
// 2. GET /users/:id
// Action: Get one specific user by their ID
app.get('/users/:id', (req, res) => {
const user = db.getUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: "User not found" });
}
res.status(200).json(user);
});
// 3. POST /users
// Action: Create a brand new user
app.post('/users', (req, res) => {
const newUser = req.body;
const createdUser = db.createUser(newUser);
res.status(201).json(createdUser);
});
// 4. PUT /users/:id
// Action: Update a specific user
app.put('/users/:id', (req, res) => {
const updatedData = req.body;
const updatedUser = db.updateUser(req.params.id, updatedData);
res.status(200).json(updatedUser);
});
// 5. DELETE /users/:id
// Action: Delete a specific user
app.delete('/users/:id', (req, res) => {
db.deleteUser(req.params.id);
res.status(204).send(); // 204 means "No Content" (successfully deleted)
});
Look at the Delete route compared to the Read One route. The URL (/users/:id) is identical. Express routes the request to the correct function purely based on whether the client used app.get or app.delete.
If a frontend developer knows you have a users resource, they instantly know how to create one, read one, or delete one without even looking at your code. That is the power of REST.
Status Codes: The Silent Conversation
If the data payload is the message, the HTTP Status Code is the tone of voice.
One of the worst things you can do in API design is send an error message but attach a 200 OK status code. That is like smiling warmly while telling someone their car just exploded. It confuses the client application, which relies on status codes to trigger success or error screens.
REST heavily utilizes proper HTTP status codes to tell the client what happened. You don't need to memorize all of them, just the categories:
2xx (Success): Everything went well.
200 OK: Standard success (used for GET and PUT).201 Created: Successfully created a resource (used for POST).204 No Content: The action succeeded, but there is no data to send back (often used for DELETE).
4xx (Client Error): The client messed up.
400 Bad Request: The client sent invalid data (e.g., missing an email field).401 Unauthorized: The client didn't provide a valid authentication token.403 Forbidden: The client is logged in, but isn't allowed to do this (e.g., a normal user trying to delete an admin).404 Not Found: The client asked for a resource that doesn't exist (e.g.,GET /users/999when user 999 isn't in the database).
5xx (Server Error): The server messed up.
500 Internal Server Error: Your code crashed, the database is down, or something broke on your end.
Look back at the Express code snippet above. When a user requests an ID that doesn't exist, we don't just send back an empty object. We explicitly set .status(404). This allows the frontend React or Vue app to automatically catch the error and show a "User Not Found" component.
The Takeaway
Designing a REST API is an exercise in restraint.
It is tempting to invent custom URLs for every unique action your server performs. Resist that urge. Force your logic into the framework of Resources and standard HTTP Methods.
By mapping Nouns to URLs, Verbs to Methods, and outcomes to Status Codes, you build APIs that are predictable, scalable, and a joy for other developers to consume. You aren't just writing code that works—you are designing a professional system.

