CRUD Operations with Express
CRUD stands for Create, Read, Update, and Delete — the four basic operations you perform on data. Building a REST API means mapping these operations to HTTP methods and route handlers.
We will build a complete CRUD API for a 'books' resource. For now, we will store data in an array (in-memory). Later lessons will show how to connect to a real database.
const express = require('express');
const app = express();
app.use(express.json());
// In-memory data store
let books = [
{ id: 1, title: '1984', author: 'George Orwell' },
{ id: 2, title: 'Dune', author: 'Frank Herbert' }
];
let nextId = 3;
// GET all books
app.get('/api/books', (req, res) => {
res.json(books);
});
// GET a single book by ID
app.get('/api/books/: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(book);
});
// POST — Create a new book
app.post('/api/books', (req, res) => {
const { title, author } = req.body;
if (!title || !author) {
return res.status(400).json({ error: 'Title and author are required' });
}
const book = { id: nextId++, title, author };
books.push(book);
res.status(201).json(book);
});
// PUT — Update a book
app.put('/api/books/: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' });
const { title, author } = req.body;
if (!title || !author) {
return res.status(400).json({ error: 'Title and author are required' });
}
book.title = title;
book.author = author;
res.json(book);
});
// DELETE — Remove a book
app.delete('/api/books/: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();
});
app.listen(3000, () => console.log('API running on port 3000')); - GET /api/books — Read all books
- GET /api/books/:id — Read a single book
- POST /api/books — Create a new book
- PUT /api/books/:id — Update an existing book
- DELETE /api/books/:id — Delete a book
- This uses in-memory storage — data is lost when the server restarts. In production, you would use a database like MongoDB or PostgreSQL.
Request Body Parsing and Validation
When clients send data in POST or PUT requests, you need to parse and validate it. Express's express.json() middleware parses JSON bodies, but you should always validate the data before using it.
Basic validation checks for required fields, correct data types, and reasonable values. For complex validation, libraries like Joi or express-validator are recommended.
const express = require('express');
const app = express();
app.use(express.json());
// Simple validation function
function validateBook(body) {
const errors = [];
if (!body.title || typeof body.title !== 'string') {
errors.push('Title is required and must be a string');
}
if (!body.author || typeof body.author !== 'string') {
errors.push('Author is required and must be a string');
}
if (body.year && (typeof body.year !== 'number' || body.year < 0)) {
errors.push('Year must be a positive number');
}
return errors;
}
app.post('/api/books', (req, res) => {
const errors = validateBook(req.body);
if (errors.length > 0) {
return res.status(400).json({ errors });
}
const book = {
id: Date.now(),
title: req.body.title.trim(),
author: req.body.author.trim(),
year: req.body.year || null
};
res.status(201).json(book);
});
// Test with curl:
// curl -X POST http://localhost:3000/api/books \
// -H "Content-Type: application/json" \
// -d '{"title": "Dune", "author": "Frank Herbert", "year": 1965}' - Always validate and sanitize user input. Never trust data from the client — trim strings, check types, and validate ranges.
