100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Node.js

CRUD Operations in Express

Build complete Create, Read, Update, and Delete route handlers in Express using a database model.

Databases & DataIntermediate12 min readJul 8, 2026
Analogies

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

javascript
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

javascript
// 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/64f1a2b3c4d5e6f7a8b9c0d1

Output

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

Was this page helpful?

Topics covered

#NodeJs#NodeJsExpressStudyNotes#WebDevelopment#CRUDOperationsInExpress#CRUD#Operations#Express#Syntax#StudyNotes#SkillVeris