- Express.js Basics
- Express.js HOME
- Express.js Introduction
- Express.js Installation
- Express.js Basic App
- Express.js Routing
- Basics Routing
- Route Parameters
- Handling Query Strings
- Router Middleware
- Middleware
- What is Middleware?
- Application-Level Middleware
- Router-Level Middleware
- Built-In Middleware
- Error-Handling Middleware
- Third-Party Middleware
- Express.js HTTP
- Handling GET Requests
- Handling POST Requests
- Handling PUT Requests
- Handling DELETE Requests
- Templating Engines
- Using Templating Engines
- Setting Up EJS
- Setting Up Handlebars
- Setting Up Pug
- Request/Response
- Request Object
- Response Object
- Handling JSON Data
- Handling Form Data
- Static Files
- Serving Static Files
- Setting Up Static Folders
- Managing Assets
- Express.js Advanced
- Middleware Stack
- CORS in Express.js
- JWT Authentication
- Session Handling
- File Uploads
- Error Handling
- Databases
- Express.js with MongoDB
- MongoDB CRUD Operations
- Express.js with MySQL
- MySQL CRUD Operations
- Deployment
- Deploying Express.js Apps to Heroku
- Deploying Express.js Apps to AWS
- Deploying Express.js Apps to Vercel
Express.js with MongoDB
Integrating MongoDB with Express.js is a fundamental skill for modern web developers. This combination is the backbone of the popular MERN stack (MongoDB, Express, React, Node.js). While Express handles the routing and server-side logic, MongoDB acts as a flexible, NoSQL database that stores your data in a JSON-like format called BSON. This synergy makes it incredibly easy to pass data from the database through your API directly to a JavaScript-based frontend.
Key Features of Express.js with MongoDB
- Scalable Database Integration: Unlike traditional SQL databases with rigid tables, MongoDB uses collections and documents. This allows you to evolve your data structure without complex migrations.
- RESTful APIs: Since MongoDB stores data in JSON-like documents, it maps perfectly to RESTful API responses, reducing the need for heavy data transformation.
- Asynchronous Operations: Node.js and MongoDB both rely heavily on non-blocking I/O. Using
async/awaitmakes your code look synchronous while maintaining high performance. - Full-Stack Capabilities: Using JavaScript for both the backend (Express) and the database queries makes context switching easier for developers.
.env) to store your MongoDB connection string. Never hardcode passwords or sensitive URI strings directly into your source code.
Setting Up MongoDB with Express.js
Install Required Packages
To get started, you need the mongoose library. While you can use the native MongoDB driver, Mongoose provides built-in validation, middle-ware, and a much cleaner syntax for defining your data structures.
Example:
npm install mongoose
Connect to MongoDB
Before performing any database operations, you must establish a persistent connection. It is best practice to handle connection errors gracefully so your app doesn't crash silently.
Example:
const mongoose = require('mongoose');
// Replace with your actual connection string
const dbURI = 'mongodb://localhost:27017/mydatabase';
mongoose.connect(dbURI)
.then(() => console.log('Successfully connected to MongoDB'))
.catch(err => console.error('Connection failed:', err.message));
useNewUrlParser: true. In Mongoose 6 and 7+, these are handled automatically, and including them can sometimes throw warnings.
Define a Schema and Model
A Schema defines the shape of the documents within a collection. A Model provides the interface to the database for creating, querying, updating, and deleting records.
Example:
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: { type: Number, min: 18 },
createdAt: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
mongoose.model() pluralizes your collection name automatically. If you name your model 'User', Mongoose will look for a collection named 'users' in MongoDB.
Create API Endpoints
Now, let's build the routes. We use async/await to handle the database promises, ensuring our server waits for the database to respond before sending a result to the client.
Insert Data (Create):
app.post('/add-user', async (req, res) => {
// req.body contains the JSON sent from the frontend
const user = new User(req.body);
try {
await user.save();
res.status(201).send('User added successfully');
} catch (err) {
res.status(400).send('Error saving user: ' + err.message);
}
});
Fetch Data (Read):
app.get('/users', async (req, res) => {
try {
const users = await User.find(); // Fetches all documents from the collection
res.json(users);
} catch (err) {
res.status(500).send('Server Error: ' + err.message);
}
});
Update Data (Update):
app.put('/update-user/:id', async (req, res) => {
try {
// The { new: true } option returns the document AFTER the update is applied
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!user) return res.status(404).send('User not found');
res.json(user);
} catch (err) {
res.status(400).send('Update failed: ' + err.message);
}
});
Delete Data (Delete):
app.delete('/delete-user/:id', async (req, res) => {
try {
const deletedUser = await User.findByIdAndDelete(req.params.id);
if (!deletedUser) return res.status(404).send('User not found');
res.send('User deleted successfully');
} catch (err) {
res.status(500).send('Delete failed: ' + err.message);
}
});
Complete Example
In a real-world scenario, you would separate your routes and models into different files. However, here is how everything looks when combined into a single entry point (e.g., app.js):
const express = require('express');
const mongoose = require('mongoose');
const app = express();
app.use(express.json()); // Built-in middleware to parse JSON
// MongoDB connection setup
mongoose.connect('mongodb://localhost:27017/mydatabase')
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('Database connection error:', err));
// Schema definition
const userSchema = new mongoose.Schema({
name: String,
email: String,
age: Number
});
const User = mongoose.model('User', userSchema);
// API Routes
app.post('/add-user', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json({ message: 'User created!', user });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
app.get('/users', async (req, res) => {
try {
const users = await User.find();
res.status(200).json(users);
} catch (err) {
res.status(500).json({ error: 'Failed to fetch users' });
}
});
// Start the server
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
201 Created for successful POST requests and 200 OK for successful GET requests. This helps frontend developers and API consumers understand the outcome immediately.
Summary
Express.js and MongoDB work seamlessly to handle database-driven applications. By using Mongoose as your ODM, you gain structure and validation while keeping the speed and flexibility of NoSQL. Remember to always handle your asynchronous code with try/catch blocks to prevent your server from hanging on database errors. From here, you can explore advanced topics like population (joins), indexing for performance, and building authentication systems.