REST API Basics
REST (Representational State Transfer) is an architectural style for designing APIs. Most modern web APIs follow REST principles to create predictable, easy-to-use interfaces.
What is REST?
REST uses HTTP methods to perform operations on resources:
GET /api/users → Get all users
GET /api/users/1 → Get user with ID 1
POST /api/users → Create new user
PUT /api/users/1 → Update user 1
DELETE /api/users/1 → Delete user 1
Key concepts:
- Resources (users, posts, products)
- HTTP methods (GET, POST, PUT, DELETE)
- Status codes (200, 404, 500)
- Stateless (each request is independent)
HTTP Methods
GET - Retrieve Data
const express = require('express');
const app = express();
// Get all items
app.get('/api/products', (req, res) => {
const products = [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Phone', price: 599 }
];
res.json(products);
});
// Get single item
app.get('/api/products/:id', (req, res) => {
const id = parseInt(req.params.id);
const product = { id, name: 'Laptop', price: 999 };
res.json(product);
});
POST - Create Data
app.use(express.json());
app.post('/api/products', (req, res) => {
const { name, price } = req.body;
const newProduct = {
id: Date.now(), // Simple ID generation
name,
price
};
// Return 201 Created with new resource
res.status(201).json(newProduct);
});
PUT - Update Data (Replace)
app.put('/api/products/:id', (req, res) => {
const id = parseInt(req.params.id);
const { name, price } = req.body;
const updatedProduct = {
id,
name,
price
};
res.json(updatedProduct);
});
PATCH - Update Data (Partial)
app.patch('/api/products/:id', (req, res) => {
const id = parseInt(req.params.id);
// Only update provided fields
const updates = req.body;
const product = {
id,
name: 'Laptop',
price: 999,
...updates // Apply partial updates
};
res.json(product);
});
DELETE - Remove Data
app.delete('/api/products/:id', (req, res) => {
const id = parseInt(req.params.id);
// Return 204 No Content (successful deletion)
res.status(204).send();
// Or return deleted item with 200
// res.json({ message: 'Deleted', id });
});
HTTP Status Codes
Use appropriate status codes:
// 200 OK - Successful GET, PUT, PATCH
res.status(200).json(data);
// 201 Created - Successful POST
res.status(201).json(newResource);
// 204 No Content - Successful DELETE
res.status(204).send();
// 400 Bad Request - Invalid input
res.status(400).json({ error: 'Invalid data' });
// 401 Unauthorized - Not authenticated
res.status(401).json({ error: 'Please login' });
// 403 Forbidden - Not authorized
res.status(403).json({ error: 'Access denied' });
// 404 Not Found - Resource doesn't exist
res.status(404).json({ error: 'Not found' });
// 500 Internal Server Error - Server error
res.status(500).json({ error: 'Server error' });
Common Status Codes
| Code | Meaning | Use Case |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input |
| 401 | Unauthorized | Not logged in |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource doesn’t exist |
| 500 | Server Error | Something went wrong |
RESTful URL Design
Good RESTful URLs
✅ /api/users → Collection
✅ /api/users/123 → Specific user
✅ /api/users/123/posts → User's posts
✅ /api/posts/456/comments → Post's comments
Bad URLs
❌ /api/getUsers → Don't use verbs
❌ /api/user → Use plural
❌ /api/users/delete/123 → Use DELETE method
❌ /api/users?id=123 → Use /users/123
Resource Relationships
// User's posts
app.get('/api/users/:userId/posts', (req, res) => {
const userId = req.params.userId;
const posts = []; // Get user's posts
res.json(posts);
});
// Post's comments
app.get('/api/posts/:postId/comments', (req, res) => {
const postId = req.params.postId;
const comments = []; // Get post's comments
res.json(comments);
});
Building a Simple REST API
Complete example with in-memory data:
const express = require('express');
const app = express();
app.use(express.json());
// In-memory database
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];
let nextId = 3;
// GET /api/users - Get all users
app.get('/api/users', (req, res) => {
res.json(users);
});
// GET /api/users/:id - Get user by ID
app.get('/api/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const user = users.find(u => u.id === id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
// POST /api/users - Create new user
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
// Validation
if (!name || !email) {
return res.status(400).json({
error: 'Name and email are required'
});
}
const newUser = {
id: nextId++,
name,
email
};
users.push(newUser);
res.status(201).json(newUser);
});
// PUT /api/users/:id - Update user
app.put('/api/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const { name, email } = req.body;
const userIndex = users.findIndex(u => u.id === id);
if (userIndex === -1) {
return res.status(404).json({ error: 'User not found' });
}
// Validation
if (!name || !email) {
return res.status(400).json({
error: 'Name and email are required'
});
}
const updatedUser = { id, name, email };
users[userIndex] = updatedUser;
res.json(updatedUser);
});
// PATCH /api/users/:id - Partial update
app.patch('/api/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const updates = req.body;
const user = users.find(u => u.id === id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
Object.assign(user, updates);
res.json(user);
});
// DELETE /api/users/:id - Delete user
app.delete('/api/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === id);
if (userIndex === -1) {
return res.status(404).json({ error: 'User not found' });
}
users.splice(userIndex, 1);
res.status(204).send();
});
app.listen(3000, () => {
console.log('API running on http://localhost:3000');
});
Testing the API
Using curl
# Get all users
curl http://localhost:3000/api/users
# Get user by ID
curl http://localhost:3000/api/users/1
# Create user
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Charlie","email":"charlie@example.com"}'
# Update user
curl -X PUT http://localhost:3000/api/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"Alice Smith","email":"alice.smith@example.com"}'
# Partial update
curl -X PATCH http://localhost:3000/api/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"Alice Johnson"}'
# Delete user
curl -X DELETE http://localhost:3000/api/users/1
Using JavaScript fetch
// Get all users
fetch('http://localhost:3000/api/users')
.then(res => res.json())
.then(users => console.log(users));
// Get user by ID
fetch('http://localhost:3000/api/users/1')
.then(res => res.json())
.then(user => console.log(user));
// Create user
fetch('http://localhost:3000/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Charlie',
email: 'charlie@example.com'
})
})
.then(res => res.json())
.then(user => console.log('Created:', user));
// Update user
fetch('http://localhost:3000/api/users/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Alice Smith',
email: 'alice.smith@example.com'
})
})
.then(res => res.json())
.then(user => console.log('Updated:', user));
// Delete user
fetch('http://localhost:3000/api/users/1', {
method: 'DELETE'
})
.then(res => console.log('Deleted:', res.status));
Query Parameters
Filter and paginate with query strings:
// GET /api/users?role=admin
app.get('/api/users', (req, res) => {
let result = users;
// Filter by role
if (req.query.role) {
result = result.filter(u => u.role === req.query.role);
}
// Search by name
if (req.query.search) {
result = result.filter(u =>
u.name.toLowerCase().includes(req.query.search.toLowerCase())
);
}
res.json(result);
});
// GET /api/users?page=2&limit=10
app.get('/api/users', (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const paginatedUsers = users.slice(skip, skip + limit);
res.json({
data: paginatedUsers,
page,
limit,
total: users.length
});
});
Response Formats
Standard Success Response
app.get('/api/users', (req, res) => {
res.json({
success: true,
data: users,
count: users.length
});
});
Standard Error Response
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({
success: false,
error: {
message: 'User not found',
code: 'USER_NOT_FOUND'
}
});
}
res.json({
success: true,
data: user
});
});
Organizing Routes
Use Express Router:
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json(users);
});
router.get('/:id', (req, res) => {
// Get user by ID
});
router.post('/', (req, res) => {
// Create user
});
router.put('/:id', (req, res) => {
// Update user
});
router.delete('/:id', (req, res) => {
// Delete user
});
module.exports = router;
// server.js
const express = require('express');
const usersRouter = require('./routes/users');
const app = express();
app.use(express.json());
app.use('/api/users', usersRouter);
app.listen(3000);
REST API Best Practices
✅ DO:
- Use nouns for resources (/users, /posts)
- Use plural names (/users, not /user)
- Use HTTP methods correctly
- Return appropriate status codes
- Version your API (/api/v1/users)
- Validate input
- Handle errors gracefully
❌ DON’T:
- Use verbs in URLs (/getUser, /deletePost)
- Return 200 for errors
- Ignore status codes
- Expose internal errors to clients
- Change URLs without versioning
Summary
| Method | URL | Purpose | Status |
|---|---|---|---|
| GET | /api/users | Get all users | 200 |
| GET | /api/users/:id | Get one user | 200 or 404 |
| POST | /api/users | Create user | 201 |
| PUT | /api/users/:id | Update user (full) | 200 or 404 |
| PATCH | /api/users/:id | Update user (partial) | 200 or 404 |
| DELETE | /api/users/:id | Delete user | 204 or 404 |
Key Principles:
- Resources are nouns
- HTTP methods are verbs
- Status codes communicate results
- Each request is independent (stateless)
Next Article: Building a CRUD API