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.