NODE.JS DEVELOPMENT:Day 6: Building a REST API with Express
Mastering day 6: building a rest api with express concepts and implementation.
Building a REST API with Express.js
A REST API (Representational State Transfer Application Programming Interface) is a web service that uses HTTP methods to allow clients to interact with server resources. Express.js makes building REST APIs straightforward.
REST Principles
- Stateless: Every request contains all information needed to process it
- Uniform Interface: Standard HTTP methods and status codes
- Resource-Based: URLs identify resources, not actions
- Client-Server: Clear separation between frontend and backend
HTTP Methods and Their Meanings
| Method | Purpose | Body? | Idempotent? |
|---|---|---|---|
| GET | Read resource | No | Yes |
| POST | Create resource | Yes | No |
| PUT | Replace resource | Yes | Yes |
| PATCH | Partial update | Yes | No |
| DELETE | Delete resource | No | Yes |
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 OK | Success |
| 201 Created | Resource created |
| 204 No Content | Success, no body |
| 400 Bad Request | Invalid input |
| 401 Unauthorized | Auth required |
| 403 Forbidden | Insufficient permissions |
| 404 Not Found | Resource doesn't exist |
| 409 Conflict | Duplicate resource |
| 422 Unprocessable | Validation failure |
| 500 Internal Server Error | Server bug |
Building a Books REST API
const express = require('express');
const router = express.Router();
// In-memory data store (replace with database in production)
let books = [
{ id: 1, title: 'Clean Code', author: 'Robert Martin', year: 2008 },
{ id: 2, title: 'You Don't Know JS', author: 'Kyle Simpson', year: 2015 },
];
let nextId = 3;
// GET /api/books — list all books (with optional filter)
router.get('/', (req, res) => {
const { author, year } = req.query;
let result = books;
if (author) result = result.filter(b => b.author.toLowerCase().includes(author.toLowerCase()));
if (year) result = result.filter(b => b.year === parseInt(year));
res.json({ data: result, count: result.length });
});
// GET /api/books/:id — get single book
router.get('/:id', (req, res) => {
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) return res.status(404).json({ error: 'Book not found' });
res.json({ data: book });
});
// POST /api/books — create book
router.post('/', (req, res) => {
const { title, author, year } = req.body;
// Validation
if (!title || !author) {
return res.status(422).json({ error: 'title and author are required' });
}
const book = { id: nextId++, title, author, year: year || null };
books.push(book);
res.status(201).json({ data: book });
});
// PATCH /api/books/:id — partial update
router.patch('/:id', (req, res) => {
const index = books.findIndex(b => b.id === parseInt(req.params.id));
if (index === -1) return res.status(404).json({ error: 'Book not found' });
books[index] = { ...books[index], ...req.body, id: books[index].id }; // id immutable
res.json({ data: books[index] });
});
// DELETE /api/books/:id — delete book
router.delete('/:id', (req, res) => {
const index = books.findIndex(b => b.id === parseInt(req.params.id));
if (index === -1) return res.status(404).json({ error: 'Book not found' });
books.splice(index, 1);
res.status(204).send(); // 204 No Content
});
module.exports = router;
Mounting the Router
const express = require('express');
const app = express();
app.use(express.json());
app.use('/api/books', require('./routes/books'));
app.listen(3000);
Input Validation with express-validator
const { body, param, validationResult } = require('express-validator');
const bookValidation = [
body('title').trim().notEmpty().withMessage('Title is required').isLength({ max: 200 }),
body('author').trim().notEmpty().withMessage('Author is required'),
body('year').optional().isInt({ min: 1000, max: 2100 }).withMessage('Invalid year'),
];
router.post('/', bookValidation, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// Process valid request...
});
Error Handling Middleware
// Catch-all error handler — must have 4 parameters
app.use((err, req, res, next) => {
console.error(err.stack);
// Handle specific error types
if (err.name === 'ValidationError') {
return res.status(422).json({ error: err.message });
}
if (err.name === 'CastError') {
return res.status(400).json({ error: 'Invalid ID format' });
}
// Default error response
const status = err.status || 500;
res.status(status).json({
error: process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message
});
});
// 404 handler — after all routes
app.use((req, res) => {
res.status(404).json({ error: `Route ${req.method} ${req.path} not found` });
});
Hands-on Examples
Testing the Books API with curl
# Create a book
curl -X POST http://localhost:3000/api/books \
-H "Content-Type: application/json" \
-d '{"title":"Node.js Design Patterns","author":"Mario Casciaro","year":2020}'
# Get all books
curl http://localhost:3000/api/books
# Get by id
curl http://localhost:3000/api/books/1
# Update
curl -X PATCH http://localhost:3000/api/books/1 \
-H "Content-Type: application/json" \
-d '{"year":2020}'
# Delete
curl -X DELETE http://localhost:3000/api/books/1curl is the standard command-line tool for testing HTTP APIs. The -X flag sets the method, -H adds headers, and -d sends the request body.