Introduction
CRUD stands for Create, Read, Update, and Delete — the four fundamental operations that most APIs need to perform against a data store. In Express, CRUD is typically implemented as a set of routes that map HTTP methods (POST, GET, PUT/PATCH, DELETE) to database operations, following RESTful conventions. Combining Express routing with a database model (Mongoose or a SQL client) lets you build a fully functional resource API in a small, predictable set of handlers.
Cricket analogy: Think of a scorer's ledger: adding a new delivery (Create), checking the scorecard (Read), correcting a wrong run tally (Update), and striking off a no-ball entry (Delete) — Virat Kohli's innings gets logged the same predictable way every time.
Syntax
const express = require('express');
const router = express.Router();
const { User } = require('./models/user');
// Create
router.post('/users', async (req, res, next) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
next(err);
}
});
// Read all
router.get('/users', async (req, res, next) => {
try {
const users = await User.find();
res.json(users);
} catch (err) {
next(err);
}
});
// Read one
router.get('/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
} catch (err) {
next(err);
}
});
// Update
router.put('/users/:id', async (req, res, next) => {
try {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
} catch (err) {
next(err);
}
});
// Delete
router.delete('/users/:id', async (req, res, next) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.status(204).end();
} catch (err) {
next(err);
}
});
module.exports = router;Explanation
Each route maps to one CRUD operation: POST creates a new document and responds with 201 Created; GET reads either the full collection or a single resource by id, responding with 404 when nothing matches; PUT updates a resource, using { new: true } so the response reflects the updated document and runValidators: true so schema validation still applies on updates; DELETE removes a resource and responds with 204 No Content, which conventionally carries no response body. Every handler wraps its database call in try/catch and forwards errors to next(err), letting a centralized Express error-handling middleware format consistent error responses instead of duplicating error logic in each route.
Cricket analogy: POST is like the umpire signaling a new boundary is logged (201 Created); GET is checking the scoreboard, returning 'no such batsman' if you ask about someone not playing (404); PUT is correcting a run total and re-validating against the scoring rules; DELETE wipes a wrongly credited run with no further commentary (204).
Example
// Simulated client requests against the router above
// 1. Create a user
// POST /users body: { "name": "Ada", "email": "ada@example.com" }
// 2. List all users
// GET /users
// 3. Fetch one user
// GET /users/64f1a2b3c4d5e6f7a8b9c0d1
// 4. Update a user's name
// PUT /users/64f1a2b3c4d5e6f7a8b9c0d1 body: { "name": "Ada Lovelace" }
// 5. Delete a user
// DELETE /users/64f1a2b3c4d5e6f7a8b9c0d1Output
The Create request returns status 201 with the new user JSON including a generated _id. The list request returns status 200 with a JSON array of all users. Fetching a single valid id returns 200 with that user's JSON, while an unknown id returns 404 with {"error":"Not found"}. The update request returns 200 with the modified document reflecting the new name. The delete request returns 204 with an empty body, confirming the resource was removed.
Cricket analogy: Adding Rohit Sharma to the squad list returns 201 with his new player ID; listing the full squad returns 200 with every player's card; looking up a retired player returns 404 'Not found'; updating his role to vice-captain returns 200 with the revised card; dropping a player from the squad returns 204 with nothing further.
Key Takeaways
- CRUD maps to HTTP verbs: POST for Create, GET for Read, PUT/PATCH for Update, DELETE for Delete.
- Use appropriate status codes: 201 for created, 200 for success, 404 for not found, 204 for successful deletion with no body.
- Pass { new: true, runValidators: true } to findByIdAndUpdate so responses reflect updates and validation still runs.
- Always check for a null/missing result after findById-style calls and respond with 404 before assuming success.
- Centralize error handling by calling next(err) instead of duplicating try/catch logic across every route.
Practice what you learned
1. Which HTTP method conventionally maps to the 'Create' operation in a RESTful CRUD API?
2. What status code should a successful DELETE request typically return when there is no response body?
3. Why is { new: true } passed to Mongoose's findByIdAndUpdate?
4. What should a GET /users/:id route do if no matching user is found?
Was this page helpful?
You May Also Like
Connecting to MongoDB with Mongoose
Learn how to connect an Express app to MongoDB using Mongoose schemas, models, and connection management.
RESTful API Design
Learn the core principles of REST: resource-based URLs, proper HTTP methods, and meaningful status codes.
Data Validation in Express
Validate incoming request data in Express using schema-based validation before it reaches your database layer.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics