REST API Design Made Simple with Express.js
If you’ve ever wondered how your phone gets the latest posts from Instagram or how a website saves your profile information, you’re looking at the work of an API.
Specifically, most modern apps use something called a REST API. While it sounds technical, the concept is as simple as ordering food at a restaurant. Let’s break it down using Express.js, the most popular tool for building these in the world of JavaScript.
What is a REST API?
Imagine you're at a restaurant. You (the client) look at a menu and tell the waiter (the API) what you want. The waiter goes to the kitchen (the server), grabs your food, and brings it back. You never need to go into the kitchen yourself.
A REST API works the same way. It's a set of rules that lets two applications talk to each other — your browser or app asks for something, and the server responds with data.
💡 Simple Definition
REST stands for Representational State Transfer. It's just a style of building APIs that is clean, predictable, and easy to understand. When an API follows REST rules, developers know exactly how to use it — no guesswork.
The key rules REST follows are: use URLs to identify things, use HTTP methods to say what to do, and return data in JSON format.
Resources in REST Architecture
In REST, everything is a resource. A resource is simply a "thing" your API can work with — like a user, a product, an order, or a post.
Resources are identified by URLs (also called endpoints). Think of a URL as a home address for your data.
Resource URL Examples
A /users resource might represent all your users. When you go to:
/users→ All users/users/42→ The user with ID 42/users/42/posts→ All posts by user 42
Golden Rule:
Always use nouns (things) in URLs, never verbs (actions). The action is expressed by the HTTP method instead. So it's
/users— never/getUsersor/deleteUser.
HTTP Methods
Once you have a resource URL, you need to tell the server what to do with it. That's what HTTP methods are for. There are four main ones, and they map directly to four database operations called CRUD.
CRUD vs HTTP Methods Mapping
| CRUD Operation | HTTP Method | What it does | Example URL |
|---|---|---|---|
| Create | POST | Add new data | POST /users |
| Read | GET | Fetch existing data | GET /users |
| Update | PUT | Replace existing data | PUT /users/1 |
| Delete | DELETE | Remove data | DELETE /users/1 |
GET — Fetching Data
GET is the most common method. It's like reading a book — you look at the data, but you don't change anything. Safe to call as many times as you want.
// GET all users
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
app.get('/users', (req, res) => {
res.json(users);
});
// GET a single user by ID
app.get('/users/:id', (req, res) => {
const userId = Number(req.params.id);
const user = users.findIndex(user => user.id === userId)
res.json({ user });
});
POST — Creating Data
POST sends new data to the server to be created. Think of it like filling out a form and hitting Submit.
// POST — Create a new user
app.post('/users', (req, res) => {
const { name, email } = req.body;
// Basic validation
if (!name || !email) {
return res.status(400).json({ message: "Name and email are required" });
}
// Create new user
const newUser = {
id: users.length + 1,
name,
email
};
// 🔥 Save to array
users.push(newUser);
res.status(201).json(newUser);
});
PUT — Updating Data
PUT replaces an existing item completely. You're saying "replace everything about this user with what I'm sending now."
// PUT — Update a user
app.put('/users/:id', (req, res) => {
const { name, email } = req.body;
const userId = Number(req.params.id);
const index = users.findIndex(user => user.id === userId);
if (index === -1) {
return res.status(404).json({ message: "User not found" });
}
// 🔥 Update the actual data
users[index] = {
...users[index], // keep existing fields
name,
email
};
res.status(200).json(users[index]);
});
DELETE — Removing Data
DELETE removes an item from the server. Simple and permanent — like tossing something in the bin.
app.delete('/users/:id', (req, res) => {
const userId = Number(req.params.id);
const index = users.findIndex(user => user.id === userId);
if (index === -1) {
return res.status(404).json({ message: "User not found" });
}
// 🔥 Remove user from array
users.splice(index, 1);
// 204 = success, no content
res.status(204).send();
});
Status Codes Basics
Every time your server responds, it sends a status code — a 3-digit number that tells the client what happened. Think of them like emoji reactions: the server is telling you "thumbs up", "not found", or "something broke".
💡 Analogy
Status codes are like delivery status notifications — 200 is "Delivered!", 404 is "Address not found", and 500 is "Delivery truck broke down."
Common Status Codes You Must Know
✅ Success Codes (2xx)
| Code | Name | Meaning |
|---|---|---|
| 200 | OK | Request successful, data returned |
| 201 | Created | New resource created successfully |
| 204 | No Content | Success, but nothing to return |
GET /users → 200 OK
POST /users → 201 Created
DELETE /users/1 → 204 No Content
❌ Client Errors (4xx)
| Code | Name | Meaning |
|---|---|---|
| 400 | Bad Request | Invalid data sent |
| 401 | Unauthorized | Login required |
| 403 | Forbidden | No permission |
| 404 | Not Found | Resource doesn’t exist |
GET /users/999 → 404 Not Found
POST /users (missing name) → 400 Bad Request
💥 Server Errors (5xx)
| Code | Name | Meaning |
|---|---|---|
| 500 | Internal Server Error | Something broke on server |
GET /users → 500 Server Error (bug in code)
Designing Routes Using REST Principles
A route is a URL + HTTP method combination. Great REST routes are clean, consistent, and instantly understandable to any developer.
REST Route Checklist
Use plural nouns for collections:
/users, not/userUse IDs in the path for single items:
/users/5Never put verbs in routes: ❌
/getUser, ❌/createUserKeep them lowercase and use hyphens for multi-word resources:
/blog-posts
Good Routes vs ❌ Bad Routes
| ✅ Good | ❌ Bad | Why it matters |
|---|---|---|
| GET /users | GET /getUsers | Verb belongs in the HTTP method, not the URL |
| POST /users | POST /createUser | POST already means "create" |
| DELETE /users/5 | GET /deleteUser?id=5 | Use DELETE method + ID in path |
| GET /blog-posts | GET /BlogPosts | URLs should be lowercase, hyphens for spaces |
Full Example: Users API in Express.js
Let's put it all together. Here's a complete, working Express.js REST API for a Users resource. This is what a real beginner-level API looks like:
👥 Users API — All Routes at a Glance
| Method | Route | What it does | Status Code |
|---|---|---|---|
| GET | /users | Get all users | 200 OK |
| GET | /users/:id | Get one user by ID | 200 OK |
| POST | /users | Create a new user | 201 Created |
| PUT | /users/:id | Update existing user | 200 OK |
| DELETE | /users/:id | Delete a user | 204 No Content |
const express = require('express');
const app = express();
app.use(express.json()); // Parse JSON request bodies
// Fake in-memory "database"
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
// ─────────────────────────────────────────
// GET /users — Fetch all users
// ─────────────────────────────────────────
app.get('/users', (req, res) => {
res.status(200).json(users);
});
// ─────────────────────────────────────────
// GET /users/:id — Fetch one user
// ─────────────────────────────────────────
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === +req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.status(200).json(user);
});
// ─────────────────────────────────────────
// POST /users — Create a new user
// ─────────────────────────────────────────
app.post('/users', (req, res) => {
const { name, email } = req.body;
const newUser = { id: users.length + 1, name, email };
users.push(newUser);
res.status(201).json(newUser);
});
// ─────────────────────────────────────────
// PUT /users/:id — Update a user
// ─────────────────────────────────────────
app.put('/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === +req.params.id);
if (index === -1) {
return res.status(404).json({ error: 'User not found' });
}
users[index] = { ...users[index], ...req.body };
res.status(200).json(users[index]);
});
// ─────────────────────────────────────────
// DELETE /users/:id — Delete a user
// ─────────────────────────────────────────
app.delete('/users/:id', (req, res) => {
users = users.filter(u => u.id !== +req.params.id);
res.status(204).send();
});
// ─────────────────────────────────────────
// Start server
// ─────────────────────────────────────────
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
🎉 What You Just Built
That's a fully working REST API! It handles Create, Read, Update, and Delete for users — follows clean REST conventions — and returns the right status codes. This is the foundation every backend developer needs to know.
Run it Yourself in 3 Steps
# Step 1: Create a project and install Express
mkdir my-api && cd my-api
npm init -y
npm install express
# Step 2: Create your file and paste the code above
touch users-api.js
# Step 3: Run the server
node users-api.js
# → Server running on http://localhost:3000
Now open your browser or a tool like Postman/requestkit and visit http://localhost:3000/users — you'll see your users list in JSON!
What You Learned
🌐REST = Rules: A style that makes APIs predictable and easy to use by any developer.
📦Resources = URLs: Everything your API exposes is a named resource with its own URL.
🔧Methods = Actions: GET, POST, PUT, DELETE tell the server what to do with the resource.
📊Status Codes = Replies: 200s = success, 400s = client error, 500s = server error.
