How to Build a CRUD API Using Express and MongoDB: Step-by-Step Guide

14/07/2025

How to Build a CRUD API Using Express and MongoDB: Step-by-Step Guide

Learn how to build a fully functional CRUD API using Express.js and MongoDB. This step-by-step guide covers setup, routing, database operations, and testing for beginners and intermediate developers.

Build a CRUD API Using Express and MongoDB

Master the fundamentals of backend development by creating a full-featured REST API.

At the heart of almost every dynamic web application lies a backend API that handles data persistence. A common pattern for interacting with data is CRUD: Create, Read, Update, and Delete. In this comprehensive guide, we'll walk through building a complete CRUD REST API using Node.js, the popular web framework Express.js, and MongoDB as our NoSQL database, with Mongoose for object data modeling. By the end, you'll have a solid understanding of how to manage data flow in a modern backend application.

Prerequisites

  • Node.js and npm (or yarn) installed on your machine.
  • MongoDB installed locally or access to a MongoDB Atlas cluster.
  • Basic understanding of JavaScript and Node.js.

1. Project Setup

Let's start by initializing our Node.js project and installing the necessary packages.

# Create a new project directory
mkdir my-crud-api
cd my-crud-api

# Initialize npm project
npm init -y

# Install dependencies
npm install express mongoose dotenv cors
  • `express`: Fast, unopinionated, minimalist web framework for Node.js.
  • `mongoose`: MongoDB object modeling for Node.js.
  • `dotenv`: Loads environment variables from a `.env` file.
  • `cors`: Provides a Connect/Express middleware that can be used to enable CORS with various options.

2. Connect to MongoDB

Create a `.env` file in your project root to store your MongoDB connection string.

# .env
MONGO_URI=mongodb://localhost:27017/my_database_name

Now, let's set up our `server.js` (or `app.js`) file.

// server.js
require('dotenv').config(); // Load environment variables
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 5000;

// Middleware
app.use(express.json()); // For parsing application/json
app.use(cors()); // Enable CORS

// Connect to MongoDB
mongoose.connect(process.env.MONGO_URI)
  .then(() => console.log('MongoDB connected successfully'))
  .catch(err => console.error('MongoDB connection error:', err));

// Basic route
app.get('/', (req, res) => {
  res.send('API is running...');
});

// Start the server
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

3. Define a Mongoose Schema and Model

Let's create a simple `Product` schema and model. Create a `models` directory and `Product.js` inside it.

// models/Product.js
const mongoose = require('mongoose');

const productSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true,
  },
  description: {
    type: String,
    required: true,
  },
  price: {
    type: Number,
    required: true,
    min: 0,
  },
  inStock: {
    type: Boolean,
    default: true,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('Product', productSchema);

4. Implement CRUD Operations (Routes)

Now, let's create our API routes for CRUD operations. Create a `routes` directory and `productRoutes.js` inside it.

// routes/productRoutes.js
const express = require('express');
const router = express.Router();
const Product = require('../models/Product'); // Import the Product model

// @route   GET /api/products
// @desc    Get all products
// @access  Public
router.get('/', async (req, res) => {
  try {
    const products = await Product.find();
    res.status(200).json(products);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// @route   GET /api/products/:id
// @desc    Get single product by ID
// @access  Public
router.get('/:id', async (req, res) => {
  try {
    const product = await Product.findById(req.params.id);
    if (!product) return res.status(404).json({ message: 'Product not found' });
    res.status(200).json(product);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// @route   POST /api/products
// @desc    Create a new product
// @access  Public
router.post('/', async (req, res) => {
  const { name, description, price, inStock } = req.body;
  const newProduct = new Product({
    name,
    description,
    price,
    inStock,
  });

  try {
    const savedProduct = await newProduct.save();
    res.status(201).json(savedProduct);
  } catch (err) {
    res.status(400).json({ message: err.message }); // 400 for validation errors
  }
});

// @route   PUT /api/products/:id
// @desc    Update a product by ID
// @access  Public
router.put('/:id', async (req, res) => {
  try {
    const updatedProduct = await Product.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true, runValidators: true } // Return the new doc, run schema validators
    );
    if (!updatedProduct) return res.status(404).json({ message: 'Product not found' });
    res.status(200).json(updatedProduct);
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

// @route   DELETE /api/products/:id
// @desc    Delete a product by ID
// @access  Public
router.delete('/:id', async (req, res) => {
  try {
    const deletedProduct = await Product.findByIdAndDelete(req.params.id);
    if (!deletedProduct) return res.status(404).json({ message: 'Product not found' });
    res.status(204).send(); // No content to send back
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

Finally, integrate these routes into your `server.js` file:

// server.js (updated part)
// ... (previous code) ...

// Import product routes
const productRoutes = require('./routes/productRoutes');

// Use product routes
app.use('/api/products', productRoutes); // All routes starting with /api/products will use productRoutes

// ... (rest of the code) ...

5. Running Your API

To start your API server, run:

node server.js

You should see "MongoDB connected successfully" and "Server running on port 5000" in your console.

Testing Your API (Example with cURL)

  • Create Product (POST):
    curl -X POST -H "Content-Type: application/json" -d '{
      "name": "Laptop",
      "description": "Powerful gaming laptop",
      "price": 1200,
      "inStock": true
    }' http://localhost:5000/api/products
  • Get All Products (GET):
    curl http://localhost:5000/api/products
  • Get Single Product (GET): (Replace `<PRODUCT_ID>` with an actual ID from a POST response)
    curl http://localhost:5000/api/products/<PRODUCT_ID>
  • Update Product (PUT):
    curl -X PUT -H "Content-Type: application/json" -d '{
      "name": "Gaming Laptop Pro",
      "description": "Ultra powerful gaming laptop",
      "price": 1500,
      "inStock": true
    }' http://localhost:5000/api/products/<PRODUCT_ID>
  • Delete Product (DELETE):
    curl -X DELETE http://localhost:5000/api/products/<PRODUCT_ID>

Congratulations! You've just built a fully functional CRUD REST API using Express.js and MongoDB. This foundational knowledge is essential for any backend developer. From here, you can expand your API with more complex features like authentication, authorization, advanced querying, and error handling. Remember to always validate incoming data and handle errors gracefully to build robust and secure applications.

Building a CRUD (Create, Read, Update, Delete) API is a fundamental skill for any backend developer. In this tutorial, you'll learn how to create a RESTful CRUD API using Express.js, a minimalist Node.js framework, and MongoDB, a powerful NoSQL database.

We’ll guide you through the full development process—from setting up the development environment and connecting to MongoDB, to defining routes and handling database operations. You’ll understand how to structure your project, implement each CRUD operation cleanly, and test your API endpoints effectively.

Whether you're just getting started with the MERN stack or looking to solidify your backend development skills, this tutorial will give you practical experience and a solid foundation for real-world projects.