MongoDB Node.js Driver
Introduction
The MongoDB Node.js Driver is the official driver that allows Node.js applications to interact with MongoDB databases. It provides a high-level API to MongoDB, enabling developers to connect to a MongoDB database and perform various database operations like creating, reading, updating, and deleting documents (CRUD operations).
Whether you're building a simple web application or a complex distributed system, understanding how to use the MongoDB Node.js Driver effectively is essential for working with MongoDB in a Node.js environment.
In this guide, we'll explore:
- How to install and set up the MongoDB Node.js Driver
- Connecting to a MongoDB database
- Performing basic CRUD operations
- Working with advanced features like aggregation and transactions
- Best practices for using the driver in production applications
Prerequisites
Before we get started, make sure you have:
- Node.js installed (v12.x or higher recommended)
- A MongoDB instance (local or remote) that you can connect to
- Basic understanding of JavaScript and Node.js concepts
- Familiarity with MongoDB concepts (collections, documents, queries)
Installation
Let's start by installing the MongoDB Node.js Driver in your project:
npm install mongodb
This command installs the latest stable version of the MongoDB Node.js Driver.
Connecting to MongoDB
The first step in using the MongoDB driver is establishing a connection to your database. Here's how you can do it:
const { MongoClient } = require('mongodb');
// Connection URI
const uri = 'mongodb://localhost:27017';
// Database Name
const dbName = 'myProject';
// Create a new MongoClient
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// Connect to the MongoDB server
async function connectToDatabase() {
try {
// Connect the client to the server
await client.connect();
console.log('Connected successfully to MongoDB server');
// Get reference to the database
const db = client.db(dbName);
return db;
} catch (err) {
console.error('Error connecting to MongoDB:', err);
throw err;
}
}
// Usage example
async function main() {
let db;
try {
db = await connectToDatabase();
// Now you can use the db object to interact with your database
console.log('Database connection established and ready to use');
} catch (err) {
console.error('Failed to connect to database:', err);
} finally {
// Ensures that the client will close when you finish/error
if (client) {
await client.close();
console.log('Database connection closed');
}
}
}
main();
Connection String Options
The connection string (URI) can include various options to customize your connection:
// Connection with authentication and options
const uri = 'mongodb://username:password@localhost:27017/?maxPoolSize=20&w=majority';
Common connection string options include:
maxPoolSize
: Maximum size of the connection poolw
: Write concern levelretryWrites
: Enable or disable retryable writesconnectTimeoutMS
: How long to wait for a connection before timing out
Basic CRUD Operations
Now that we have connected to MongoDB, let's explore the basic CRUD operations.
Creating Documents
To insert documents into a collection, you can use the insertOne()
or insertMany()
methods:
async function createDocuments(db) {
const collection = db.collection('users');
// Insert a single document
const resultOne = await collection.insertOne({
name: 'John Doe',
email: '[email protected]',
age: 30,
createdAt: new Date()
});
console.log(`Inserted document with ID: ${resultOne.insertedId}`);
// Insert multiple documents
const resultMany = await collection.insertMany([
{
name: 'Jane Smith',
email: '[email protected]',
age: 25,
createdAt: new Date()
},
{
name: 'Bob Johnson',
email: '[email protected]',
age: 35,
createdAt: new Date()
}
]);
console.log(`${resultMany.insertedCount} documents were inserted`);
console.log('Inserted document IDs:', resultMany.insertedIds);
return { resultOne, resultMany };
}
Output:
Inserted document with ID: 60f8a5b9c2f84d001f3a9c42
2 documents were inserted
Inserted document IDs: {
'0': 60f8a5b9c2f84d001f3a9c43,
'1': 60f8a5b9c2f84d001f3a9c44
}
Reading Documents
To retrieve documents from a collection, you can use the find()
or findOne()
methods:
async function readDocuments(db) {
const collection = db.collection('users');
// Find a single document
const user = await collection.findOne({ name: 'John Doe' });
console.log('Found user:', user);
// Find multiple documents
const cursor = collection.find({ age: { $gt: 25 } });
// Print each document
const users = await cursor.toArray();
console.log('Users older than 25:', users);
// Count documents
const count = await collection.countDocuments({ age: { $gt: 25 } });
console.log(`Number of users older than 25: ${count}`);
return { user, users, count };
}
Output:
Found user: {
_id: 60f8a5b9c2f84d001f3a9c42,
name: 'John Doe',
email: '[email protected]',
age: 30,
createdAt: 2023-09-23T10:30:45.123Z
}
Users older than 25: [
{
_id: 60f8a5b9c2f84d001f3a9c42,
name: 'John Doe',
email: '[email protected]',
age: 30,
createdAt: 2023-09-23T10:30:45.123Z
},
{
_id: 60f8a5b9c2f84d001f3a9c44,
name: 'Bob Johnson',
email: '[email protected]',
age: 35,
createdAt: 2023-09-23T10:30:45.123Z
}
]
Number of users older than 25: 2
Updating Documents
To update documents in a collection, you can use updateOne()
, updateMany()
, or replaceOne()
:
async function updateDocuments(db) {
const collection = db.collection('users');
// Update a single document
const updateOneResult = await collection.updateOne(
{ name: 'John Doe' },
{ $set: { age: 31, updatedAt: new Date() } }
);
console.log(`Updated ${updateOneResult.modifiedCount} document(s)`);
// Update multiple documents
const updateManyResult = await collection.updateMany(
{ age: { $lt: 30 } },
{ $inc: { age: 1 }, $set: { updatedAt: new Date() } }
);
console.log(`Updated ${updateManyResult.modifiedCount} document(s)`);
return { updateOneResult, updateManyResult };
}
Output:
Updated 1 document(s)
Updated 1 document(s)
Deleting Documents
To remove documents from a collection, you can use the deleteOne()
or deleteMany()
methods:
async function deleteDocuments(db) {
const collection = db.collection('users');
// Delete a single document
const deleteOneResult = await collection.deleteOne({ name: 'John Doe' });
console.log(`Deleted ${deleteOneResult.deletedCount} document(s)`);
// Delete multiple documents
const deleteManyResult = await collection.deleteMany({ age: { $gt: 30 } });
console.log(`Deleted ${deleteManyResult.deletedCount} document(s)`);
return { deleteOneResult, deleteManyResult };
}
Output:
Deleted 1 document(s)
Deleted 1 document(s)
Advanced Features
Let's explore some of the more advanced features of the MongoDB Node.js Driver.
Working with Aggregation Framework
MongoDB's Aggregation Framework is a powerful tool for data analysis and transformation. Here's a simple example:
async function performAggregation(db) {
const collection = db.collection('users');
const pipeline = [
// Stage 1: Match users above 25
{ $match: { age: { $gt: 25 } } },
// Stage 2: Group by age and count
{ $group: {
_id: "$age",
count: { $sum: 1 },
users: { $push: "$name" }
}
},
// Stage 3: Sort by age
{ $sort: { _id: 1 } }
];
const results = await collection.aggregate(pipeline).toArray();
console.log('Aggregation results:', results);
return results;
}
Output:
Aggregation results: [
{ _id: 26, count: 1, users: ['Jane Smith'] },
{ _id: 31, count: 1, users: ['John Doe'] },
{ _id: 35, count: 1, users: ['Bob Johnson'] }
]
Using Transactions
For operations that require atomicity, you can use transactions:
async function performTransaction(client) {
const session = client.startSession();
try {
// Start transaction
session.startTransaction();
const db = client.db('myProject');
const usersCollection = db.collection('users');
const accountsCollection = db.collection('accounts');
// Perform operations within the transaction
await usersCollection.insertOne({
name: 'Alice Brown',
email: '[email protected]',
age: 28
}, { session });
await accountsCollection.insertOne({
userId: '[email protected]',
balance: 1000,
type: 'savings'
}, { session });
// Commit the transaction
await session.commitTransaction();
console.log('Transaction committed successfully');
} catch (error) {
// Abort transaction on error
await session.abortTransaction();
console.error('Transaction aborted. Error:', error);
throw error;
} finally {
// End the session
await session.endSession();
}
}
Change Streams
Change streams allow you to watch for changes in your MongoDB collections:
async function watchCollection(db) {
const collection = db.collection('users');
// Create a change stream
const changeStream = collection.watch();
// Set up change stream handler
changeStream.on('change', (change) => {
console.log('Detected change:', change);
if (change.operationType === 'insert') {
console.log('New document inserted:', change.fullDocument);
} else if (change.operationType === 'update') {
console.log('Document updated:', change.documentKey);
} else if (change.operationType === 'delete') {
console.log('Document deleted:', change.documentKey);
}
});
console.log('Change stream established. Watching for changes...');
// Return the change stream so it can be closed later
return changeStream;
}
Best Practices
When working with the MongoDB Node.js Driver, consider these best practices:
Connection Management
// Create a singleton connection manager
class MongoConnectionManager {
constructor() {
this.client = null;
this.db = null;
this.uri = 'mongodb://localhost:27017';
this.dbName = 'myProject';
}
async connect() {
if (this.client) return this.db;
this.client = new MongoClient(this.uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
maxPoolSize: 10
});
await this.client.connect();
this.db = this.client.db(this.dbName);
console.log('Connected to MongoDB');
return this.db;
}
async close() {
if (this.client) {
await this.client.close();
this.client = null;
this.db = null;
console.log('MongoDB connection closed');
}
}
}
// Usage
const connectionManager = new MongoConnectionManager();
// In your application code
async function doSomething() {
const db = await connectionManager.connect();
// Use db for operations
}
Error Handling
Always implement proper error handling for database operations:
async function safeOperation(callback) {
try {
const db = await connectionManager.connect();
return await callback(db);
} catch (error) {
console.error('Database operation failed:', error);
// Log the error, send alerts, etc.
throw error;
}
}
// Usage
await safeOperation(async (db) => {
const users = await db.collection('users').find({}).toArray();
return users;
});
Schema Validation
You can use MongoDB's schema validation to ensure data integrity:
async function createValidatedCollection(db) {
await db.createCollection('products', {
validator: {
$jsonSchema: {
bsonType: 'object',
required: ['name', 'price', 'category'],
properties: {
name: {
bsonType: 'string',
description: 'must be a string and is required'
},
price: {
bsonType: 'decimal',
minimum: 0,
description: 'must be a positive decimal and is required'
},
category: {
bsonType: 'string',
description: 'must be a string and is required'
},
tags: {
bsonType: 'array',
items: {
bsonType: 'string'
}
}
}
}
},
validationLevel: 'strict',
validationAction: 'error'
});
console.log('Created validated collection: products');
}
Real-World Example: Building a RESTful API
Let's use everything we've learned to build a simple RESTful API for a product catalog:
const express = require('express');
const { MongoClient, ObjectId } = require('mongodb');
const bodyParser = require('body-parser');
// Express app setup
const app = express();
app.use(bodyParser.json());
// MongoDB connection
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true
});
let db;
// Connect to MongoDB
async function connectToMongo() {
try {
await client.connect();
db = client.db('productCatalog');
console.log('Connected to MongoDB');
} catch (err) {
console.error('Failed to connect to MongoDB', err);
process.exit(1);
}
}
// API Routes
// Get all products
app.get('/api/products', async (req, res) => {
try {
const collection = db.collection('products');
const products = await collection.find({}).toArray();
res.json(products);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Get a single product
app.get('/api/products/:id', async (req, res) => {
try {
const collection = db.collection('products');
const product = await collection.findOne({ _id: new ObjectId(req.params.id) });
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json(product);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Create a new product
app.post('/api/products', async (req, res) => {
try {
const collection = db.collection('products');
const product = {
name: req.body.name,
price: req.body.price,
category: req.body.category,
createdAt: new Date()
};
const result = await collection.insertOne(product);
res.status(201).json({
_id: result.insertedId,
...product
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Update a product
app.put('/api/products/:id', async (req, res) => {
try {
const collection = db.collection('products');
const result = await collection.updateOne(
{ _id: new ObjectId(req.params.id) },
{
$set: {
name: req.body.name,
price: req.body.price,
category: req.body.category,
updatedAt: new Date()
}
}
);
if (result.matchedCount === 0) {
return res.status(404).json({ error: 'Product not found' });
}
const updatedProduct = await collection.findOne({ _id: new ObjectId(req.params.id) });
res.json(updatedProduct);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Delete a product
app.delete('/api/products/:id', async (req, res) => {
try {
const collection = db.collection('products');
const result = await collection.deleteOne({ _id: new ObjectId(req.params.id) });
if (result.deletedCount === 0) {
return res.status(404).json({ error: 'Product not found' });
}
res.status(204).send();
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Start the server
const PORT = process.env.PORT || 3000;
connectToMongo().then(() => {
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
});
// Handle graceful shutdown
process.on('SIGINT', async () => {
if (client) {
await client.close();
console.log('MongoDB connection closed');
}
process.exit(0);
});
Summary
In this guide, we've covered the MongoDB Node.js Driver from basics to advanced usage:
- Installation and Connection: How to install the driver and connect to a MongoDB database
- CRUD Operations: Creating, reading, updating, and deleting documents
- Advanced Features: Working with aggregation, transactions, and change streams
- Best Practices: Connection management, error handling, and schema validation
- Real-World Example: Building a RESTful API using MongoDB and Express
The MongoDB Node.js Driver is a powerful tool for Node.js developers working with MongoDB. It provides a robust set of features for interacting with MongoDB databases efficiently and reliably.
Additional Resources
- Official MongoDB Node.js Driver Documentation
- MongoDB Node.js Driver API Reference
- MongoDB University - Free Online Courses
Practice Exercises
-
Basic CRUD Application: Build a simple to-do application that allows users to create, read, update, and delete tasks.
-
Data Analysis: Use the aggregation framework to generate reports from a dataset of your choice (e.g., sales data, user activity).
-
Real-time Features: Implement real-time notifications using change streams to alert users when data changes.
-
Schema Migration: Create a script to perform a schema migration on a collection (e.g., adding a new field to all documents or restructuring nested data).
-
Connection Resilience: Enhance the connection management code to include automatic reconnection with exponential backoff after connection failures.
Remember that the best way to learn is by doing. Start with small projects and gradually increase their complexity as you become more comfortable with the MongoDB Node.js Driver.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)