On this page
REST API with Express
Create a backend API for managing tasks. You will practice Express routing, HTTP methods, JSON responses, and REST conventions.
Requirements
- Node.js 18+ installed
- Familiarity with REST API design and HTTP module
npmorpnpmfor package management
Features
- CRUD endpoints for a
tasksresource - In-memory data store (no database required)
- Input validation and proper HTTP status codes
- JSON request/response bodies
Step 1: Initialize the Project
mkdir task-api && cd task-api
npm init -y
npm install express
Add to package.json:
"type": "module"
Step 2: Basic Server
server.js
import express from 'express';
const app = express();
const PORT = 3000;
app.use(express.json());
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Run with node server.js and test: curl http://localhost:3000/health
Step 3: In-Memory Data Store
let tasks = [
{ id: 1, title: 'Learn Express', completed: false },
{ id: 2, title: 'Build REST API', completed: false }
];
let nextId = 3;
Step 4: CRUD Endpoints
// List all tasks
app.get('/api/tasks', (req, res) => {
res.json(tasks);
});
// Get one task
app.get('/api/tasks/:id', (req, res) => {
const task = tasks.find(t => t.id === Number(req.params.id));
if (!task) return res.status(404).json({ error: 'Task not found' });
res.json(task);
});
// Create task
app.post('/api/tasks', (req, res) => {
const { title } = req.body;
if (!title || typeof title !== 'string') {
return res.status(400).json({ error: 'Title is required' });
}
const task = { id: nextId++, title: title.trim(), completed: false };
tasks.push(task);
res.status(201).json(task);
});
// Update task
app.put('/api/tasks/:id', (req, res) => {
const index = tasks.findIndex(t => t.id === Number(req.params.id));
if (index === -1) return res.status(404).json({ error: 'Task not found' });
const { title, completed } = req.body;
if (title !== undefined) tasks[index].title = title.trim();
if (completed !== undefined) tasks[index].completed = Boolean(completed);
res.json(tasks[index]);
});
// Delete task
app.delete('/api/tasks/:id', (req, res) => {
const index = tasks.findIndex(t => t.id === Number(req.params.id));
if (index === -1) return res.status(404).json({ error: 'Task not found' });
tasks.splice(index, 1);
res.status(204).send();
});
Step 5: Test with curl
curl http://localhost:3000/api/tasks
curl -X POST http://localhost:3000/api/tasks -H "Content-Type: application/json" -d '{"title": "Write tests"}'
curl -X PUT http://localhost:3000/api/tasks/1 -H "Content-Type: application/json" -d '{"completed": true}'
curl -X DELETE http://localhost:3000/api/tasks/1
Step 6: Add Middleware
Add logging, a 404 catch-all, and an error handler after your routes:
app.use((req, res, next) => { console.log(`${req.method} ${req.url}`); next(); });
app.use((req, res) => res.status(404).json({ error: 'Route not found' }));
app.use((err, req, res, next) => { console.error(err); res.status(500).json({ error: 'Internal server error' }); });
Extension Ideas
- Query filters — support
?completed=trueon the list endpoint - Persistent storage — replace the array with SQLite or MongoDB
- Pagination —
?page=1&limit=10query parameters - Authentication — protect write endpoints with JWT middleware
- Input sanitization — use a validation library like
zodorjoi - API documentation — generate OpenAPI/Swagger docs with
swagger-jsdoc - Automated tests — write integration tests with
supertestandvitest