How to Perform CRUD Operations in Express.js with Mongoose: A Step-by-Step Guide
CRUD—Create, Read, Update, and Delete—are the four fundamental operations that form the backbone of almost every dynamic web application. Mastering them is a critical skill for any backend developer. When building with Node.js, the combination of the Express.js framework for routing and the Mongoose ODM for MongoDB interaction provides a powerful and developer-friendly stack to build robust APIs.
This technical guide will walk you through building a complete RESTful API that performs all four CRUD operations. We will create a simple application to manage blog posts, demonstrating each step with clear code examples and explanations.
Prerequisites
Before we begin, ensure you have the following installed and set up:
- Node.js and npm: You can download them from the official Node.js website.
- MongoDB: You need a running MongoDB instance, either locally on your machine or through a cloud service like MongoDB Atlas.
- A code editor: Such as Visual Studio Code.
- Basic knowledge: A foundational understanding of JavaScript, Node.js, and how APIs work is recommended.
Step 1: Setting Up the Project Environment
First, let's create our project directory and initialize a new Node.js application.
Initialize the Node.js Project
Open your terminal, create a new folder for your project, and navigate into it.
mkdir express-crud-api
cd express-crud-api
Now, initialize a package.json file using npm.
npm init -y
Install Dependencies
We need a few key packages to build our application:
- express: The web framework for Node.js that will handle our routing and server logic.
- mongoose: An Object Data Modeling (ODM) library for MongoDB and Node.js. It simplifies interactions with the database.
- dotenv: A module to load environment variables from a
.envfile intoprocess.env. - nodemon (dev dependency): A tool that automatically restarts the server during development whenever file changes are detected.
Install the required dependencies:
npm install express mongoose dotenv
Install nodemon as a development dependency:
npm install --save-dev nodemon
Next, open your package.json and add a start script to run the server with nodemon.
"scripts": {
"start": "nodemon server.js"
}
Step 2: Creating the Express Server and Connecting to MongoDB
Now it's time to write some code. Create a file named server.js in your project's root directory. This file will be the entry point for our application.
Basic Server Setup
Add the following code to server.js to create a minimal Express server.
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware to parse JSON bodies
app.use(express.json());
app.get('/', (req, res) => {
res.send('CRUD API is running!');
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Database Connection
To keep our database connection string secure, create a .env file in the root directory and add your MongoDB URI.
# .env
MONGO_URI=mongodb://localhost:27017/blogDB
Now, let's update server.js to connect to our MongoDB database using Mongoose. We will also require the dotenv package to load the environment variable.
// server.js
require('dotenv').config(); // Load environment variables
const express = require('express');
const mongoose = require('mongoose');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(express.json());
// Connect to MongoDB
mongoose.connect(process.env.MONGO_URI)
.then(() => {
console.log('Successfully connected to MongoDB');
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
})
.catch((err) => {
console.error('Database connection error:', err);
});
app.get('/', (req, res) => {
res.send('CRUD API is running!');
});
Now, if you run npm start in your terminal, your server should start and connect to the database.
Step 3: Defining the Mongoose Schema and Model
Mongoose works with Schemas and Models. A Schema defines the structure of the documents within a collection, and a Model provides an interface to the database for creating, querying, updating, and deleting records.
Create a new folder named models and inside it, a file named post.model.js.
// models/post.model.js
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
content: {
type: String,
required: true
},
author: {
type: String,
required: false,
default: 'Anonymous'
}
}, {
timestamps: true // Automatically adds createdAt and updatedAt fields
});
const Post = mongoose.model('Post', postSchema);
module.exports = Post;
This schema defines a Post with a title, content, and author. The timestamps option will automatically manage createdAt and updatedAt fields for us.
Step 4: Implementing the CRUD Operations
With our model in place, we can now create the API endpoints for each CRUD operation in server.js.
First, import the Post model at the top of your server.js file.
const Post = require('./models/post.model');
1. Create (POST)
To create a new post, we'll define a POST route. This route will expect a JSON object in the request body that matches our postSchema.
// CREATE a new post
app.post('/posts', async (req, res) => {
try {
const post = await Post.create(req.body);
res.status(201).json(post);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
- Route:
POST /posts - Logic: We use an
async/awaitblock to handle the asynchronous database operation.Post.create(req.body)creates a new document in thepostscollection using the data from the request body. - Response: On success, we send a
201 Createdstatus with the newly created post. If an error occurs, we send a500 Internal Server Error.
2. Read (GET)
Reading data can be done in two ways: fetching a list of all items or fetching a single item by its unique identifier.
Get All Posts
This endpoint will retrieve all posts from the database.
// READ all posts
app.get('/posts', async (req, res) => {
try {
const posts = await Post.find({});
res.status(200).json(posts);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
- Route:
GET /posts - Logic:
Post.find({})queries the collection and returns all documents. An empty object{}as the filter means "match all." - Response: We return a
200 OKstatus with an array of posts.
Get a Single Post by ID
This endpoint will retrieve a specific post using its _id.
// READ a single post by ID
app.get('/posts/:id', async (req, res) => {
try {
const { id } = req.params;
const post = await Post.findById(id);
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
res.status(200).json(post);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
- Route:
GET /posts/:id(e.g.,/posts/60d21b4667d0d8992e610c85) - Logic: We extract the
idfrom the URL parameters (req.params).Post.findById(id)finds the document with the matching_id. We also handle the case where no post is found, returning a404 Not Found. - Response: On success, we return the found post.
3. Update (PUT)
To update an existing post, we'll use a PUT request. This route will identify the post by its ID and update it with the data provided in the request body.
// UPDATE a post by ID
app.put('/posts/:id', async (req, res) => {
try {
const { id } = req.params;
const post = await Post.findByIdAndUpdate(id, req.body, { new: true });
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
res.status(200).json(post);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
- Route:
PUT /posts/:id - Logic:
Post.findByIdAndUpdate()finds a document by its ID and updates it. The first argument is theid, the second is the update data (req.body), and the third is an options object.{ new: true }ensures that the method returns the modified document, not the original one. - Response: We return the updated post.
4. Delete (DELETE)
Finally, to delete a post, we'll use the DELETE HTTP method.
// DELETE a post by ID
app.delete('/posts/:id', async (req, res) => {
try {
const { id } = req.params;
const post = await Post.findByIdAndDelete(id);
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
res.status(200).json({ message: 'Post deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
});
- Route:
DELETE /posts/:id - Logic:
Post.findByIdAndDelete()finds a document by its ID and removes it from the collection. - Response: On successful deletion, we return a confirmation message.
Conclusion
Congratulations! You have successfully built a complete RESTful API with Express.js and Mongoose, implementing all four essential CRUD operations. You now have a solid foundation for creating, reading, updating, and deleting data from a MongoDB database.
These fundamental patterns are the building blocks for more complex applications. From here, you can expand on this project by adding features like:
- Input Validation: Use a library like
Joiorexpress-validatorto validate incoming data. - Authentication and Authorization: Secure your endpoints so only authorized users can perform certain actions.
- Refactoring: Organize your code by separating routes and controller logic into different files for better maintainability (e.g., using the MVC pattern).
By mastering CRUD operations in Express, you've unlocked the ability to build powerful and scalable backend services for any web or mobile application.
Top comments (0)