← Back to Examples
REST API Design
REST = Representational State Transfer. A convention for designing web APIs using HTTP methods and URLs.
REST Conventions
| Method |
URL |
Action |
Status |
| GET |
/api/books |
List all books |
200 |
| GET |
/api/books/:id |
Get one book |
200 / 404 |
| POST |
/api/books |
Create a book |
201 |
| PUT |
/api/books/:id |
Update a book |
200 / 404 |
| DELETE |
/api/books/:id |
Delete a book |
204 / 404 |
Complete REST API: Book Store
Save this as bookstore-api.js and run with node bookstore-api.js
// bookstore-api.js
const express = require('express');
const app = express();
const PORT = 3000;
// Middleware
app.use(express.json());
// Request logger
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
// ============================================
// DATA STORE (In-memory - resets on restart)
// ============================================
let books = [
{ id: 1, title: 'JavaScript: The Good Parts', author: 'Douglas Crockford', year: 2008, genre: 'Programming' },
{ id: 2, title: 'Eloquent JavaScript', author: 'Marijn Haverbeke', year: 2018, genre: 'Programming' },
{ id: 3, title: 'Node.js Design Patterns', author: 'Mario Casciaro', year: 2020, genre: 'Programming' }
];
let nextId = 4;
// ============================================
// ROUTES
// ============================================
// GET /api/books - List all books
app.get('/api/books', (req, res) => {
// Support filtering by genre
const { genre, author } = req.query;
let result = books;
if (genre) {
result = result.filter(b =>
b.genre.toLowerCase() === genre.toLowerCase()
);
}
if (author) {
result = result.filter(b =>
b.author.toLowerCase().includes(author.toLowerCase())
);
}
res.json({
count: result.length,
data: result
});
});
// GET /api/books/:id - Get one book
app.get('/api/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const book = books.find(b => b.id === id);
if (!book) {
return res.status(404).json({
error: 'Book not found',
id: id
});
}
res.json(book);
});
// POST /api/books - Create a book
app.post('/api/books', (req, res) => {
const { title, author, year, genre } = req.body;
// Validation
if (!title || !author) {
return res.status(400).json({
error: 'Title and author are required'
});
}
const book = {
id: nextId++,
title,
author,
year: year || new Date().getFullYear(),
genre: genre || 'General'
};
books.push(book);
res.status(201).json(book);
});
// PUT /api/books/:id - Update a book
app.put('/api/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const book = books.find(b => b.id === id);
if (!book) {
return res.status(404).json({
error: 'Book not found'
});
}
// Update fields
const { title, author, year, genre } = req.body;
if (title) book.title = title;
if (author) book.author = author;
if (year) book.year = year;
if (genre) book.genre = genre;
res.json(book);
});
// DELETE /api/books/:id - Delete a book
app.delete('/api/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = books.findIndex(b => b.id === id);
if (index === -1) {
return res.status(404).json({
error: 'Book not found'
});
}
books.splice(index, 1);
res.status(204).send();
});
// ============================================
// ERROR HANDLING
// ============================================
// 404 handler (no route matched)
app.use((req, res) => {
res.status(404).json({
error: 'Route not found',
path: req.url,
method: req.method
});
});
// Error handler
app.use((err, req, res, next) => {
console.error('Server error:', err.message);
res.status(500).json({
error: 'Internal server error'
});
});
// ============================================
// START SERVER
// ============================================
app.listen(PORT, () => {
console.log(`Book Store API running at http://localhost:${PORT}`);
console.log('');
console.log('Endpoints:');
console.log(' GET /api/books - List all books');
console.log(' GET /api/books/:id - Get one book');
console.log(' POST /api/books - Create a book');
console.log(' PUT /api/books/:id - Update a book');
console.log(' DELETE /api/books/:id - Delete a book');
});
Testing the API
$ curl http://localhost:3000/api/books
$ curl http://localhost:3000/api/books/1
$ curl -X POST http://localhost:3000/api/books \
-H "Content-Type: application/json" \
-d '{"title":"Learning Node.js","author":"Shelley Powers","year":2024}'
$ curl -X PUT http://localhost:3000/api/books/1 \
-H "Content-Type: application/json" \
-d '{"year":2024}'
$ curl -X DELETE http://localhost:3000/api/books/1
$ curl "http://localhost:3000/api/books?genre=Programming"
HTTP Status Codes Reference
| Code | Meaning | Use Case |
| 200 | OK | Successful GET, PUT |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input data |
| 401 | Unauthorized | Missing/invalid auth |
| 403 | Forbidden | No permission |
| 404 | Not Found | Resource doesn't exist |
| 500 | Server Error | Unexpected error |
← Back to Examples