Build Your First REST API with Node.js and Express
Understand resources, HTTP methods and Express routing by building a small API from project setup to error handling.
1. What REST means in practice
| Method | Route | Meaning | Typical success |
|---|---|---|---|
| GET | /notes | List notes | 200 |
| GET | /notes/:id | Read one note | 200 |
| POST | /notes | Create note | 201 |
| PATCH | /notes/:id | Update fields | 200 |
| DELETE | /notes/:id | Delete note | 204 |
2. Create the project
Setupbash
mkdir notes-api
cd notes-api
npm init -y
npm install express
npm install --save-dev nodemon3. Start the server
server.jsjs
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.get('/health', (req, res) => {
res.json({ ok: true });
});
app.listen(PORT, () => {
console.log(`API running on http://localhost:${PORT}`);
});4. Add in-memory data first
Temporary data storejs
let notes = [
{ id: 1, title: 'Revise networking', done: false },
{ id: 2, title: 'Read C pointers', done: true },
];
let nextId = 3;5. GET routes
List and readjs
app.get('/notes', (req, res) => {
res.json({ items: notes });
});
app.get('/notes/:id', (req, res) => {
const id = Number(req.params.id);
const note = notes.find((item) => item.id === id);
if (!note) return res.status(404).json({ error: 'Note not found' });
res.json({ item: note });
});6. POST with validation
Create notejs
app.post('/notes', (req, res) => {
const title = String(req.body?.title || '').trim();
if (!title) return res.status(400).json({ error: 'title is required' });
const note = { id: nextId++, title, done: false };
notes.push(note);
res.status(201).json({ item: note });
});7. PATCH and DELETE
Update notejs
app.patch('/notes/:id', (req, res) => {
const id = Number(req.params.id);
const note = notes.find((item) => item.id === id);
if (!note) return res.status(404).json({ error: 'Note not found' });
if (typeof req.body?.title === 'string') note.title = req.body.title.trim();
if (typeof req.body?.done === 'boolean') note.done = req.body.done;
res.json({ item: note });
});
app.delete('/notes/:id', (req, res) => {
const id = Number(req.params.id);
const before = notes.length;
notes = notes.filter((item) => item.id !== id);
if (notes.length === before) return res.status(404).json({ error: 'Note not found' });
res.status(204).end();
});8. Middleware and request logging
Small loggerjs
app.use((req, res, next) => {
const started = Date.now();
res.on('finish', () => {
console.log(req.method, req.originalUrl, res.statusCode, `${Date.now() - started}ms`);
});
next();
});9. Central error handling
Error middlewarejs
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
});10. Test with curl
Requestsbash
curl http://localhost:3000/notes
curl -X POST http://localhost:3000/notes \
-H "Content-Type: application/json" \
-d '{"title":"Practise Express"}'
curl -X PATCH http://localhost:3000/notes/1 \
-H "Content-Type: application/json" \
-d '{"done":true}'11. Replace memory with a database without breaking the contract
Once the route behaviour is clear, move persistence behind a repository/service layer. Your clients should still call the same resource URLs and receive the same response shapes. Add database constraints so correctness does not depend only on route code.
12. Production security checklist
- ✓Validate every untrusted input.
- ✓Authenticate protected operations.
- ✓Authorise each user against the resource they are changing.
- ✓Use rate limiting where abuse is realistic.
- ✓Keep secrets in environment variables.
- ✓Restrict CORS instead of using * blindly on authenticated APIs.
- ✓Use structured logs and monitoring.
- ✓Paginate large collections.
- ✓Return predictable error shapes without leaking stack traces.
References
RESEARCH TRAIL
Sources and further reading
Clarvo articles are independently written. These references let you inspect the original inspiration and primary or official sources used to deepen the guide.
KEEP READING

