What We'll Build
In this tutorial, you'll build a simple but complete REST API for managing a list of tasks. By the end, you'll understand how to set up an Express server, define routes, handle request/response cycles, use middleware, and return proper HTTP status codes.
Prerequisites
- Node.js (v18+) installed on your machine
- Basic familiarity with JavaScript
- A terminal and a code editor
Step 1: Initialize Your Project
Create a new directory and initialize a Node.js project:
mkdir task-api && cd task-api
npm init -y
npm install express
Create the entry file:
touch index.js
Step 2: Set Up the Express Server
Open index.js and add the following boilerplate:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware to parse JSON request bodies
app.use(express.json());
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Run it with node index.js — you should see the server start message.
Step 3: Create In-Memory Data Storage
For this tutorial we'll use a simple array. In a real app, you'd replace this with a database.
let tasks = [
{ id: 1, title: 'Buy groceries', done: false },
{ id: 2, title: 'Read a book', done: true },
];
let nextId = 3;
Step 4: Define Your Routes
A RESTful API follows predictable URL patterns. Here's the full CRUD implementation:
GET /tasks — List All Tasks
app.get('/tasks', (req, res) => {
res.json(tasks);
});
GET /tasks/:id — Get a Single Task
app.get('/tasks/:id', (req, res) => {
const task = tasks.find(t => t.id === parseInt(req.params.id));
if (!task) return res.status(404).json({ error: 'Task not found' });
res.json(task);
});
POST /tasks — Create a Task
app.post('/tasks', (req, res) => {
const { title } = req.body;
if (!title) return res.status(400).json({ error: 'Title is required' });
const newTask = { id: nextId++, title, done: false };
tasks.push(newTask);
res.status(201).json(newTask);
});
PATCH /tasks/:id — Update a Task
app.patch('/tasks/:id', (req, res) => {
const task = tasks.find(t => t.id === parseInt(req.params.id));
if (!task) return res.status(404).json({ error: 'Task not found' });
const { title, done } = req.body;
if (title !== undefined) task.title = title;
if (done !== undefined) task.done = done;
res.json(task);
});
DELETE /tasks/:id — Delete a Task
app.delete('/tasks/:id', (req, res) => {
const index = tasks.findIndex(t => t.id === parseInt(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: Add a Global Error Handler
Always add a catch-all error handler as the last middleware in your app:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong' });
});
REST HTTP Status Code Reference
| Status Code | Meaning | When to Use |
|---|---|---|
| 200 | OK | Successful GET or PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input from client |
| 404 | Not Found | Resource doesn't exist |
| 500 | Internal Server Error | Unhandled server error |
Testing Your API
Use curl or a tool like Postman or Insomnia to test your endpoints:
# Get all tasks
curl http://localhost:3000/tasks
# Create a new task
curl -X POST http://localhost:3000/tasks \
-H "Content-Type: application/json" \
-d '{"title": "Write tests"}'
Next Steps
You now have a working REST API. From here, you can:
- Connect a real database like MongoDB or PostgreSQL
- Add authentication with JWT
- Add request validation with Zod or Joi
- Write automated tests with Jest + Supertest