Key Takeaways
- Connect with the official
mongodbpackage, select your database explicitly, and use async/await for database operations. - Reuse one
MongoClientper application process instead of opening a client for every request. - Use
insertOne()for an introductory write and inspect its acknowledgment and inserted identifier. - Keep
find({}).toArray()for small learning datasets; filter, project, and limit application queries. - Close a short-lived script's client in
finally; close a server application's client during shutdown.
To connect MongoDB to Node.js, install the official driver, construct a MongoClient with your connection URI, and call await client.connect(). Select a database with client.db('yourwebapp'), then use its collections to insert and query documents.
This MongoDB Node.js tutorial assumes you already have a Node.js project. If you need that foundation first, start with Getting Started With Node.js and Express.
Install the MongoDB Node.js Driver
Choose compatible Node.js and driver versions
Use a supported patch release of Node.js 24 LTS for this walkthrough. As of October 17, 2026, Node.js 22 and 24 are LTS release lines; Node.js 20 is end-of-life. The MongoDB compatibility table lists driver releases 7.0 through 7.7 as compatible with Node.js 22 and 24, with a minimum runtime requirement of Node.js 20.19.0. That minimum isn't a recommendation to deploy an unsupported runtime.
Before upgrading an existing application, check both its Node.js version and MongoDB Server version against the official driver compatibility tables. Runtime compatibility and runtime maintenance status are separate checks.
From your existing project's directory, install the driver:
npm install mongodbnpm saves the package in package.json dependencies by default; --save isn't necessary.
You also need a running, reachable MongoDB deployment—not just the npm package. Use an existing Atlas deployment or install a local server from the MongoDB Community download page.
Configure Your MongoDB Connection URI
Keep connection configuration outside source code. For a local development server listening on IPv4 port 27017, create a .env file in your project directory:
MONGODB_URI=mongodb://127.0.0.1:27017This example contains no credentials and is intended for local development. Add .env to .gitignore; in deployment, inject MONGODB_URI through your environment or secret-management system. Don't print connection strings containing passwords.
Atlas commonly provides an SRV connection string. Its mongodb+srv:// prefix tells the driver to discover servers through DNS. The following is a template, not a working connection string—replace every placeholder using your deployment's connection details:
MONGODB_URI=mongodb+srv://<db_user>:<encoded_password>@<atlas-host>/Percent-encode reserved characters in usernames and passwords when building a URI; don't encode the entire connection string. Keep the deployment-provided connection options when copying an Atlas URI. See MongoDB's connection string formats for the exact rules.
Connect, Insert a Document, and Read Posts
Save the following as mongodb-tutorial.mjs. The .mjs extension explicitly selects ECMAScript modules, so you can use import without changing your existing project's module configuration.
import { MongoClient } from 'mongodb';
async function main() {
const uri = process.env.MONGODB_URI;
if (!uri) {
throw new Error('Set MONGODB_URI before running this script');
}
const client = new MongoClient(uri, {
serverSelectionTimeoutMS: 5000,
});
try {
await client.connect();
console.log('Connected to MongoDB');
const db = client.db('yourwebapp');
const posts = db.collection('posts');
const result = await posts.insertOne({
title: 'First post',
status: 'published',
createdAt: new Date(),
});
console.log({
acknowledged: result.acknowledged,
insertedId: result.insertedId,
});
// Introductory read: use only with a small test collection.
const allPosts = await posts.find({}).toArray();
console.log(allPosts);
} finally {
await client.close();
}
}
main().catch((error) => {
// Log the error category without dumping a credential-bearing URI.
console.error('MongoDB tutorial failed:', error.name);
process.exitCode = 1;
});Run it with Node's environment-file option, which loads the file's variables into process.env:
node --env-file=.env mongodb-tutorial.mjsThe script keeps the client open while performing its operations and awaits cleanup in finally, including when a connection or query fails. Its outer error handler sets a nonzero exit status without forcibly terminating pending output.
Understand the write result and asynchronous flow
insertOne() inserts one document. Because this example doesn't supply _id, the driver generates an ObjectId. The result's acknowledged property reports whether the write was acknowledged, and insertedId identifies the inserted document for an acknowledged write in this example.
Async/await makes the sequence easier to read than nested callbacks: connect, insert, read, then clean up. The driver's database API is promise-based; rejected operations can be handled with try/catch or an outer promise rejection handler.
The example explicitly sets serverSelectionTimeoutMS to 5000 milliseconds. This controls waiting for a suitable server; it isn't an execution deadline for every database query.
Query MongoDB Collections Without Unbounded Reads
find({}) uses an empty filter, matching every document. It returns a cursor—an object that retrieves query results in batches. Calling toArray() materializes all remaining results in application memory, which makes the introductory example convenient but unsuitable for an arbitrarily large collection.
Replace the introductory read with a bounded query. This fragment belongs inside the script's try block, where posts is available:
// Fragment: replace the allPosts read in the connected script.
const latestPosts = await posts
.find({ status: 'published' })
.project({ _id: 0, title: 1, createdAt: 1 })
.sort({ createdAt: -1, _id: -1 })
.limit(20)
.toArray();
console.log(latestPosts);- Filter: Selects documents whose
statusispublished. - Projection: Returns only
titleandcreatedAt, excluding_id. - Sort: Orders by descending creation date, then descending identifier.
- Limit: Returns at most 20 matching documents.
Set cursor options before consuming the cursor. Projection reduces the fields returned, while a limit bounds the document count—not necessarily the work required to find and sort them. See MongoDB projections for more field-selection examples.
For large exports or background processing, iterate with for await...of rather than accumulating every document in an array. Close a cursor explicitly when you stop using it before exhausting its results.
Match indexes to your query patterns
An index is an additional data structure that helps MongoDB locate and order documents. For the preceding query, a candidate compound index puts the equality-filter field first, followed by the sort fields:
// Fragment: run with a connected posts collection in a migration/setup task.
await posts.createIndex({ status: 1, createdAt: -1, _id: -1 });This follows MongoDB's equality-then-sort indexing guideline. Inspect query plans with representative data before assuming an index solves your performance problem. Indexes consume storage and add maintenance work to writes, so create them deliberately rather than inside every request handler.
Continue with Introduction to MongoDB Indexing when your queries outgrow the sample collection.
Reuse MongoClient in a Long-Running Application
A connection pool is a reusable group of database connections maintained by the driver. Each MongoClient maintains a pool for each server in the deployment topology. Most applications should create one client per process and share it across requests, rather than constructing and closing a client for each query.
Application startup
-> create one MongoClient
-> connect
-> request handlers reuse its connection pools
-> shutdown: stop new requests, finish active work
-> await client.close()For an existing server application, put ownership in a database module such as db.mjs:
import { MongoClient } from 'mongodb';
const uri = process.env.MONGODB_URI;
if (!uri) {
throw new Error('Set MONGODB_URI before starting the application');
}
const client = new MongoClient(uri);
let posts;
export async function connectDatabase() {
try {
await client.connect();
posts = client.db('yourwebapp').collection('posts');
} catch (error) {
await client.close();
throw error;
}
}
export async function fetchPublishedPosts() {
if (!posts) {
throw new Error('Call connectDatabase() at application startup');
}
return posts
.find({ status: 'published' })
.project({ _id: 0, title: 1, createdAt: 1 })
.sort({ createdAt: -1, _id: -1 })
.limit(20)
.toArray();
}
export async function closeDatabase() {
await client.close();
}Await connectDatabase() once before accepting requests. Request handlers call fetchPublishedPosts() and handle operation failures without closing the shared client. During graceful shutdown, stop accepting work, await active handlers, then call closeDatabase().
Don't increase pool sizes as your first response to slow queries. Check query patterns and indexes first; each application process creates its own client and pools, so deployment-wide connection usage grows with the number of processes.
Handle Connection and Operation Errors Separately
A failed connection usually requires checking deployment reachability or configuration. A failed operation after connecting can instead involve document validation, permissions, or a uniqueness constraint. Diagnose the failure rather than treating every exception as a reason to reconnect.
| Symptom | What to check |
|---|---|
MongoServerSelectionError containing ECONNREFUSED | Confirm the local MongoDB server is running and listening at the URI's address and port. Check for an IPv4/IPv6 mismatch. |
MongoServerError reporting authentication failure | Check the database username, password encoding, and authentication database specified by authSource. |
| Atlas connection cannot reach a server | Check DNS resolution, outbound network access, and whether the application's address is permitted by the Atlas IP access list. |
MongoDB's connection troubleshooting guide explains these failure modes. The script above logs only the error category; for deeper diagnosis, inspect error details in a controlled environment and redact credentials before sharing logs.
Model Documents Deliberately
MongoDB's flexible schema allows documents in one collection to have different fields and field types. It doesn't remove the need for a data model: decide which fields are required, keep their types consistent where your application expects consistency, and choose relationships around how you read and update data.
For these posts, keep createdAt as a date and define the allowed status values. Consider database schema validation for important invariants. Decide whether related data should be embedded or referenced rather than assuming everything belongs inside one document. Performance and scaling depend on those choices, indexes, workload, and deployment—not simply on choosing MongoDB.
See MongoDB schema design: the two questions to ask yourself for a practical next step.
Use the Driver With TypeScript
The mongodb package includes its TypeScript declarations; don't install @types/mongodb. If your project lacks Node.js declarations, install @types/node as a development dependency. The driver's official repository documents that setup.
You can describe your document shape and pass it to db.collection<Post>('posts') for typed collection operations. These compile-time types don't validate documents arriving from a network request or already stored in the database; keep runtime validation as a separate responsibility.
FAQ
How do I connect to MongoDB from Node.js using the official driver?
Install mongodb, construct new MongoClient(uri), and await client.connect(). Select your database with client.db('yourwebapp') and a collection with db.collection('posts'). In a short-lived script, await client.close() in a finally block.
Should I create a new MongoClient for every request?
No. Most applications should reuse one MongoClient per process so requests share its connection pools. Close it during application shutdown, not after each request.
How do I handle connection errors?
Catch errors from the awaited connection attempt and check server reachability, credentials, and network access. For scripts, use finally to close the client even after failure; for servers, handle individual operation failures without closing the shared client.
Can I use MongoDB with TypeScript?
Yes. The mongodb package includes its own TypeScript declarations, so you don't need @types/mongodb. Install @types/node as a development dependency if your project doesn't already provide Node.js declarations.
What versions of Node.js are compatible with MongoDB?
For driver releases 7.0 through 7.7, MongoDB's compatibility table lists Node.js 22 and 24 as compatible, with a minimum runtime requirement of Node.js 20.19.0. Prefer a maintained LTS runtime and check the matrix for your exact driver and MongoDB Server versions before upgrading.
Further Reading
- Create a MongoClient — connection setup, explicit database selection, and client reuse.
- Manage connection pools — pool ownership, configuration, and closing behavior.
- Insert documents — insert methods and document identifiers.
- Access data from a cursor — iteration, arrays, and cursor cleanup.
- Specify which fields to return — projection syntax and rules.
- Indexes for query optimization — index creation and operational trade-offs.
- TypeScript features and limitations — typed collections and type-safety boundaries.
- Upgrade driver versions — removed options and breaking changes.
