Overview
Most applications use a single MongoDB connection. However, some applications benefit from multiple connections for data separation, performance optimization, or architectural requirements.
In this tutorial, you will learn how to establish multiple MongoDB connections within a single Node.js application.
Use Cases for Multiple MongoDB Connections
Multiple MongoDB connections are useful in applications that require data separation, improved performance, or architectural flexibility.
Common use cases for multiple MongoDB connections include the following:
Multi-tenant applications: Each tenant uses a separate database with its own connection for data isolation and security
Microservices architecture: Each service maintains its own dedicated database connection
Load distribution: Distribute read and write operations across multiple database instances
Data processing: Retrieve data from multiple MongoDB servers simultaneously for analytics or reporting
Multi-Tenant Applications
Consider a multi-tenant application where different tenants or customers share the same web application but require separate, isolated databases. In this scenario, each tenant can have their own dedicated MongoDB connection. This ensures data separation, security, and customization for each tenant while all operating within the same application. This approach simplifies management and provides a way to scale as new tenants join the platform without affecting existing ones.
Consider the following MongoDB concepts as you explore how to implement multiple connections in your application:
These concepts play a role in scenarios where multiple MongoDB connections are required for efficient data management and performance optimization.
Tutorial
In this tutorial, you will perform the following actions:
Set up your environment and install the required dependencies
Connect to MongoDB
Set up the primary MongoDB connection
Set up secondary MongoDB connections
Use an existing schema
Set schema flexibility
Switch databases within the same connection
Prerequisites
Before you begin this tutorial, you must have a MongoDB Atlas cluster. To learn how to create a free MongoDB Atlas cluster, see the Get Started with Atlas tutorial.
Note
This tutorial uses MongoDB Server 8.0.12 and Node.js version 20.15.1.
Install Dependencies
Install the required dependencies for this tutorial:
npm install express mongoose
This tutorial uses Express.js for the web framework and Mongoose for MongoDB object modeling.
Note
This tutorial uses Mongoose, a popular Object Data Modeling (ODM) library for MongoDB. For more information on getting started with Mongoose, see the Get Started with Mongoose tutorial.
Set Up Environment Variables
Create a .env
file in your project root directory to store your MongoDB
connection strings:
touch .env
Add the following environment variables to your .env
file:
PRIMARY_CONN_STR=<your-primary-connection-string> SECONDARY_CONN_STR=<your-secondary-connection-string>
Replace the placeholder values with your actual MongoDB Atlas connection strings. To learn how to find your MongoDB Atlas connection string, see the Connect to Your Cluster tutorial.
Install the dotenv
package to load your environment variables:
npm install dotenv
You now have a project that is ready to set up multiple MongoDB connections in your application.
Project Structure
When you complete this tutorial, your project directory for this application will have the following structure:
mongodb-multiple-connections/ ├── .env # Environment variables ├── package.json # Project dependencies ├── package-lock.json # Dependency lock file ├── node_modules/ # Installed packages ├── index.js # Main application file ├── db.primary.js # Primary connection configuration ├── db.secondary.js # Secondary connection configuration └── product.schema.js # Product schema definition
The key files you'll create in this tutorial serve the following purposes:
index.js: Your main application file that imports and uses both connections
db.primary.js: Configures the primary MongoDB connection using
mongoose.connect()
db.secondary.js: Configures secondary MongoDB connections using
mongoose.createConnection()
product.schema.js: Defines the reusable product schema for both connections
.env: Stores your MongoDB connection strings securely
Set Up the Primary MongoDB Connection
In this section, learn how to set up the primary MongoDB connection in your application by using Mongoose. Use the mongoose.connect() method to establish the primary MongoDB database connection for your application. This method manages a single connection pool for the entire application.
Create the Primary Connection File
In a new file named db.primary.js
, define a connection method to
use in your main application file, index.js
. This method configures
the MongoDB connection and handles events. Copy and paste the following
code into the db.primary.js
file:
require('dotenv').config(); const mongoose = require("mongoose"); module.exports = async (uri, options = {}) => { // By default, Mongoose skips properties not defined in the schema (strictQuery). // You can adjust it based on your configuration. mongoose.set('strictQuery', true); // Connect to MongoDB try { await mongoose.connect(uri, options); console.info("MongoDB primary connection initiated"); } catch (err) { console.error("MongoDB primary connection failed, " + err); } // Event handling mongoose.connection.once('open', () => console.info("MongoDB primary connection opened!")); mongoose.connection.on('connected', () => console.info("MongoDB primary connection succeeded!")); mongoose.connection.on('error', (err) => { console.error("MongoDB primary connection failed, " + err); mongoose.disconnect(); }); mongoose.connection.on('disconnected', () => console.info("MongoDB primary connection disconnected!")); // Graceful exit process.on('SIGINT', async () => { try { await mongoose.connection.close(); console.info("Mongoose primary connection disconnected through app termination!"); process.exit(0); } catch (err) { console.error("Error during graceful shutdown:", err); process.exit(1); } }); }
Create the Product Schema
Create schemas for performing operations in your application. Write the
schema in a separate file named product.schema.js
and export it, as
shown in the following code:
const mongoose = require("mongoose"); module.exports = (options = {}) => { // Schema for Product return new mongoose.Schema( { store: { _id: mongoose.Types.ObjectId, // Reference-id to the store collection name: String }, name: String, price: Number, category: String, description: String // add required properties }, options ); }
Use the Primary Connection
Import the db.primary.js
file in your main index.js
file and use
the method defined there to establish the primary MongoDB connection. You
can also pass an optional connection options object if needed.
Then, import the product.schema.js
file to access the product schema.
This lets you create a model and perform operations related to products
in your application. Copy and paste the following code into your
index.js
file to set up the primary connection and perform
operations on sample product data:
// Load environment variables require('dotenv').config(); const mongoose = require("mongoose"); // Async function to establish the primary MongoDB connection async function establishPrimaryConnection() { try { await require("./db.primary.js")(process.env.PRIMARY_CONN_STR, { // (optional) connection options }); } catch (error) { console.error('Failed to establish primary connection:', error); process.exit(1); } } // Initialize connection establishPrimaryConnection(); // Import Product Schema const productSchema = require("./product.schema.js")({ collection: "products", // Pass configuration options if needed }); // Create Model const ProductModel = mongoose.model("Product", productSchema); // Sample products data const sampleProducts = [ { name: "Laptop Pro", price: 1299.99, category: "Electronics", description: "High-performance laptop for professionals" }, { name: "Wireless Headphones", price: 199.99, category: "Electronics", description: "Premium noise-cancelling headphones" }, { name: "Coffee Maker", price: 89.99, category: "Kitchen", description: "Automatic drip coffee maker" } ]; // Wait for connection to be ready before executing operations mongoose.connection.once('open', async () => { console.log('Primary database connected, executing operations...'); try { // Check if products already exist const existingCount = await ProductModel.countDocuments(); console.log(`Existing products in primary DB: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into primary database...'); await ProductModel.insertMany(sampleProducts); console.log('Sample products inserted into primary database!'); } // Find and display a product let product = await ProductModel.findOne(); console.log('Product found in primary DB:', product); // Display all products const allProducts = await ProductModel.find(); console.log(`Total products in primary DB: ${allProducts.length}`); } catch (error) { console.error('Error with primary database operations:', error); } });
Primary database connected, executing operations... MongoDB primary connection initiated Existing products in primary DB: 3 Product found in primary DB: { _id: new ObjectId('...'), name: 'Laptop Pro', __v: 0 } Total products in primary DB: 3
where your application requires multiple MongoDB connections.
Set Up Secondary MongoDB Connections
You can configure secondary MongoDB connections for various use cases. In this section, learn how to set up and use the secondary connection.
Create the Secondary Connection File
Create a connection code in a db.secondary.js
file by using the
mongoose.createConnection()
method. This method allows you to establish separate connection pools each
tailored to a specific use case or data access pattern, unlike the
mongoose.connect()
method that you used previously for the primary
MongoDB connection:
const mongoose = require("mongoose"); module.exports = (uri, options = {}) => { // Connect to MongoDB const db = mongoose.createConnection(uri, options); // By default, Mongoose skips properties not defined in the schema (strictQuery). // Adjust it based on your configuration. db.set('strictQuery', true); // Event handling db.once('open', () => console.info("MongoDB secondary connection opened!")); db.on('connected', () => console.info(`MongoDB secondary connection succeeded!`)); db.on('error', (err) => { console.error(`MongoDB secondary connection failed, ` + err); db.close(); }); db.on('disconnected', () => console.info(`MongoDB secondary connection disconnected!`)); // Graceful exit process.on('SIGINT', async () => { try { await db.close(); console.info(`Mongoose secondary connection disconnected through app termination!`); process.exit(0); } catch (err) { console.error("Error during graceful shutdown:", err); process.exit(1); } }); // Export db object return db; }
Use the Secondary Connection
Import the db.secondary.js
file in your main index.js
file,
create the connection object with a variable named db
, and use the
method defined there to establish the secondary MongoDB connection. You
can also pass an optional connection options object if needed. Add the
following code to the end of the index.js
file:
// Load environment variables require('dotenv').config(); // Establish the secondary MongoDB connection const db = require("./db.secondary.js")(process.env.SECONDARY_CONN_STR, { // (optional) connection options }); // Import Product Schema const SecondaryProductSchema = require("./product.schema.js")({ collection: "products", // Pass configuration options if needed }); // Create Model using the secondary connection const SecondaryProductModel = db.model("Product", SecondaryProductSchema); // Sample products data for secondary database const SecondarySampleProducts = [ { name: "Smart Watch", price: 199.99, category: "Electronics", description: "Advanced fitness tracking smartwatch" }, { name: "Bluetooth Speaker", price: 79.99, category: "Electronics", description: "Portable wireless speaker with premium sound" }, { name: "Desk Lamp", price: 49.99, category: "Home", description: "LED desk lamp with adjustable brightness" } ]; // Wait for secondary connection to be ready before executing operations db.once('open', async () => { console.log('Secondary database connected, executing operations...'); try { // Check if products already exist const existingCount = await SecondaryProductModel.countDocuments(); console.log(`Existing products in secondary DB: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into secondary database...'); await SecondaryProductModel.insertMany(SecondarySampleProducts); console.log('Sample products inserted into secondary database!'); } // Find and display a product let product = await SecondaryProductModel.findOne(); console.log('Product found in secondary DB:', product); // Display all products const allProducts = await SecondaryProductModel.find(); console.log(`Total products in secondary DB: ${allProducts.length}`); } catch (error) { console.error('Error with secondary database operations:', error); } });
Primary database connected, executing operations... MongoDB primary connection initiated MongoDB secondary connection succeeded! MongoDB secondary connection opened! Secondary database connected, executing operations... Existing products in primary DB: 3 Existing products in secondary DB: 6 Product found in primary DB: { _id: new ObjectId('...'), name: 'Laptop Pro', __v: 0 } Product found in secondary DB: { _id: new ObjectId('...'), name: 'Smart Watch', __v: 0 } Total products in primary DB: 3 Total products in secondary DB: 3
Now that you've set up the connection, you can use the new db
object to create a model. The following sections explore different
scenarios and examples to help you choose the setup that best aligns with
your specific data access and management needs.
Use an Existing Schema
If both connections operate on the same data model, use the
same product.schema.js
file that was employed in the primary
connection.
Import the product.schema.js
file to access the product schema. This
enables you to create a model by using the db
object and perform operations
related to products in your application:
// Import Product Schema const secondaryProductSchema = require("./product.schema.js")({ collection: "products", // Pass configuration options if needed }); // Create Model const SecondaryProductModel = db.model("Product", secondaryProductSchema); // Wait for secondary connection to be ready before executing operations db.once('open', async () => { console.log('Secondary database connected, executing operations...'); try { // Check if products already exist in secondary database const existingCount = await SecondaryProductModel.countDocuments(); console.log(`Existing products in secondary DB: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into secondary database...'); await SecondaryProductModel.insertMany(sampleProducts); console.log('Sample products inserted into secondary database!'); } // Find and display a product let product = await SecondaryProductModel.findOne(); console.log('Product found in secondary DB:', product); // Display all products const allProducts = await SecondaryProductModel.find(); console.log(`Total products in secondary DB: ${allProducts.length}`); } catch (error) { console.error('Error with secondary database operations:', error); } });
To see a practical code example and available resources for using the existing schema of a primary database connection into a secondary MongoDB connection in your project, visit the Using the Existing Schema GitHub repository.
Set Schema Flexibility
When working with multiple MongoDB connections, it's essential to have the flexibility to adapt your schema based on specific use cases. Although the primary connection might demand a strict schema with validation to ensure data integrity, there are scenarios where a secondary connection serves a different purpose. For instance, a secondary connection might store data for analytics on an archive server, with varying schema requirements driven by past use cases. In this section, you'll explore how to configure schema flexibility for your secondary connection, allowing you to meet the distinct needs of your application.
If you prefer to have schema flexibility in Mongoose, pass the
strict: false
property in the schema options when configuring your schema for
the secondary connection. This allows you to work with data that doesn't
adhere strictly to the schema.
Import the product.schema.js
file to access the product schema. This
enables you to create a model by using the db
object and perform operations
related to products in your application:
// Import Product Schema const secondaryProductSchema = require("./product.schema.js")({ collection: "products", strict: false // Pass configuration options if needed }); // Create Model const SecondaryProductModel = db.model("Product", secondaryProductSchema); // Wait for secondary connection to be ready before executing operations db.once('open', async () => { console.log('Secondary database (flexible schema) connected, executing operations...'); try { // Check if products already exist in secondary database const existingCount = await SecondaryProductModel.countDocuments(); console.log(`Existing products in secondary DB: ${existingCount}`); if (existingCount === 0) { // Add extra fields to demonstrate schema flexibility const flexibleSampleProducts = sampleProducts.map(product => ({ ...product, extraField: "This field is not in the schema but will be saved due to strict: false", timestamp: new Date() })); console.log('Inserting sample products with extra fields into secondary database...'); await SecondaryProductModel.insertMany(flexibleSampleProducts); console.log('Sample products with extra fields inserted into secondary database!'); } // Find and display a product let product = await SecondaryProductModel.findOne(); console.log('Product found in secondary DB (flexible schema):', product); // Display all products const allProducts = await SecondaryProductModel.find(); console.log(`Total products in secondary DB: ${allProducts.length}`); } catch (error) { console.error('Error with secondary database operations:', error); } });
To view a practical code example and available resources for setting schema flexibility in a secondary MongoDB connection in your project, see the Setting Schema Flexibility GitHub repository.
Switch Databases Within the Same Connection
Within your application's database setup, you can switch between different databases by using the db.useDb() method. This method lets you to create a new connection object associated with a specific database while sharing the same connection pool.
This approach allows you to manage multiple databases within your application, by using a single connection while maintaining distinct data contexts for each database.
Import the product.schema.js
file to access the product schema. This
lets you to create a model by using the db
object and perform operations
related to products in your application.
Example: Store with Separate Database
Consider an e-commerce platform where multiple stores operate independently,
with each store maintaining its own database for product management. Use the
db.useDb()
method to switch between different store databases while
maintaining a shared connection pool, as shown in the following example:
// Load environment variables require('dotenv').config(); // Establish the secondary MongoDB connection const db = require("./db.secondary.js")(process.env.SECONDARY_CONN_STR, { // (optional) connection options }); // Import Product Schema const secondaryProductSchema = require("./product.schema.js")({ collection: "products", // strict: false // that doesn't adhere strictly to the schema! // Pass configuration options if needed }); // Base sample products data const sampleProducts = [ { name: "Laptop Pro", price: 1299.99, category: "Electronics", description: "High-performance laptop for professionals" }, { name: "Wireless Headphones", price: 199.99, category: "Electronics", description: "Premium noise-cancelling headphones" }, { name: "Coffee Maker", price: 89.99, category: "Kitchen", description: "Automatic drip coffee maker" } ]; // Sample store-specific products const storeAProducts = sampleProducts.map(product => ({ ...product, store: { name: "Store A" }, storeId: "A" })); const storeBProducts = [ { name: "Gaming Chair", price: 299.99, category: "Furniture", description: "Ergonomic gaming chair with RGB lighting", store: { name: "Store B" }, storeId: "B" }, { name: "Mechanical Keyboard", price: 149.99, category: "Electronics", description: "RGB mechanical gaming keyboard", store: { name: "Store B" }, storeId: "B" } ]; // Create a connection for 'Store A' const storeA = db.useDb('StoreA'); // Create Model const SecondaryStoreAProductModel = storeA.model("Product", secondaryProductSchema); // Wait for Store A connection to be ready storeA.once('open', async () => { console.log('Store A database connected, executing operations...'); try { // Check if products already exist in Store A const existingCount = await SecondaryStoreAProductModel.countDocuments(); console.log(`Existing products in Store A: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into Store A database...'); await SecondaryStoreAProductModel.insertMany(storeAProducts); console.log('Sample products inserted into Store A database!'); } // Find and display a product let product = await SecondaryStoreAProductModel.findOne(); console.log('Product found in Store A:', product); // Display all products const allProducts = await SecondaryStoreAProductModel.find(); console.log(`Total products in Store A: ${allProducts.length}`); } catch (error) { console.error('Error with Store A operations:', error); } }); // Create a connection for 'Store B' const storeB = db.useDb('StoreB'); // Create Model const SecondaryStoreBProductModel = storeB.model("Product", secondaryProductSchema); // Wait for Store B connection to be ready storeB.once('open', async () => { console.log('Store B database connected, executing operations...'); try { // Check if products already exist in Store B const existingCount = await SecondaryStoreBProductModel.countDocuments(); console.log(`Existing products in Store B: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into Store B database...'); await SecondaryStoreBProductModel.insertMany(storeBProducts); console.log('Sample products inserted into Store B database!'); } // Find and display a product let product = await SecondaryStoreBProductModel.findOne(); console.log('Product found in Store B:', product); // Display all products const allProducts = await SecondaryStoreBProductModel.find(); console.log(`Total products in Store B: ${allProducts.length}`); } catch (error) { console.error('Error with Store B operations:', error); } });
The preceding example establishes separate database connections for
Store A
and Store B
, with each store containing its own product data.
This approach provides data separation while utilizing a single shared
connection pool for efficient resource management.
The preceding static approach creates explicit connections for each store
with predefined names (StoreA
, StoreB
).
For dynamic store management, create a function that accepts a store identifier as a parameter and returns a connection object. This function enables store switching by identifier and reuses existing connections for improved efficiency.
// Function to get connection object for particular store's database function getStoreConnection(storeId) { return db.useDb("Store"+storeId, { useCache: true }); } // Create a connection for 'Store A' const store = getStoreConnection("A"); // Create Model const SecondaryStoreProductModel = store.model("Product", secondaryProductSchema); // Wait for store connection to be ready store.once('open', async () => { console.log('Store A (dynamic) database connected, executing operations...'); try { // Check if products already exist in the store const existingCount = await SecondaryStoreProductModel.countDocuments(); console.log(`Existing products in Store A (dynamic): ${existingCount}`); if (existingCount === 0) { // Use the same store A products from the previous example console.log('Inserting sample products into Store A (dynamic) database...'); await SecondaryStoreProductModel.insertMany(storeAProducts); console.log('Sample products inserted into Store A (dynamic) database!'); } // Find and display a product let product = await SecondaryStoreProductModel.findOne(); console.log('Product found in Store A (dynamic):', product); // Display all products const allProducts = await SecondaryStoreProductModel.find(); console.log(`Total products in Store A (dynamic): ${allProducts.length}`); } catch (error) { console.error('Error with Store A (dynamic) operations:', error); } });
In the dynamic approach, connection instances are created and cached as needed, eliminating the need for manually managing separate connections for each store. This approach enhances flexibility and resource efficiency in scenarios where you need to work with multiple stores in your application.
To see a practical code example and available resources for switching databases within the same connection into a secondary MongoDB connection in your project, visit the GitHub repository.
Next Steps
This tutorial demonstrates how to implement multiple MongoDB connections in a Node.js application by using Mongoose. You learned to establish primary and secondary connections, implement schema flexibility, and manage multiple databases within a single connection pool.
These techniques enable data separation, improved performance, and architectural flexibility for applications requiring multiple data contexts. You can now implement connection strategies that meet your specific application requirements. Consider the following best practices and additional resources as you continue to work with multiple MongoDB connections.
Best Practices
When implementing multiple MongoDB connections in your Node.js application, follow these best practices:
Connection pooling: Use connection pooling to manage MongoDB connections efficiently. Connection pooling enables connection reuse and reduces overhead. To learn more, see Connection Pooling in the Server manual and Manage Connections with Connection Pools in the MongoDB Node.js driver documentation.
Error handling: Implement proper error handling, logging, and recovery mechanisms to ensure connection reliability.
Security: Implement authentication, authorization, and secure communication practices when handling sensitive data. For more information, see Secure Your Data.
Scalability: Design your connection strategy to support both horizontal and vertical scaling requirements.
Testing: Test your multiple connection setup under various conditions, including failover scenarios, high load, and resource constraints.
Additional Resources
To learn more about getting started with Mongoose, see the Get Started with Mongoose tutorial in the Third-Party Integrations section.
To learn more about using Mongoose with MongoDB, see the Mongoose documentation.
To learn more about the complete implementation of multiple MongoDB connections in a Node.js application, see the GitHub repository.