Skip to main content

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:

bash
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:

javascript
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:

javascript
// 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 pool
  • w: Write concern level
  • retryWrites: Enable or disable retryable writes
  • connectTimeoutMS: 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:

javascript
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:

javascript
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():

javascript
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:

javascript
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:

javascript
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:

javascript
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:

javascript
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

javascript
// 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:

javascript
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:

javascript
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:

javascript
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:

  1. Installation and Connection: How to install the driver and connect to a MongoDB database
  2. CRUD Operations: Creating, reading, updating, and deleting documents
  3. Advanced Features: Working with aggregation, transactions, and change streams
  4. Best Practices: Connection management, error handling, and schema validation
  5. 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

Practice Exercises

  1. Basic CRUD Application: Build a simple to-do application that allows users to create, read, update, and delete tasks.

  2. Data Analysis: Use the aggregation framework to generate reports from a dataset of your choice (e.g., sales data, user activity).

  3. Real-time Features: Implement real-time notifications using change streams to alert users when data changes.

  4. 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).

  5. 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! :)