Tutorials Logic, IN info@tutorialslogic.com

Node.js MongoDB Connect CRUD

MongoDB Driver Workflow

Use the official MongoDB driver with one reusable MongoClient, an environment-provided connection string, and awaited CRUD operations whose result objects are checked explicitly.

MongoDB Driver Setup

The official MongoDB Node.js driver connects a Node application to Atlas or a self-managed deployment and exposes promise-based APIs for databases, collections, queries, writes, indexes, and transactions. Keep the connection string outside source control and reuse a MongoClient instead of opening a client for every request.

  • Install the mongodb package and import MongoClient from mongodb.
  • Create one MongoClient from the environment-provided connection URI and verify connectivity before serving traffic.
  • Select a database with client.db(name) and a typed collection with db.collection(name).
  • Use insertOne or insertMany for writes and inspect acknowledged and insertedId results.
  • Use findOne for one document and find for a cursor that can filter, project, sort, skip, and limit results.
  • Use updateOne or updateMany with update operators such as $set; inspect matchedCount and modifiedCount.
  • Use deleteOne or deleteMany with a deliberate filter and inspect deletedCount before reporting success.
  • Close short-lived script clients in finally; long-running servers should close the shared client during graceful shutdown.

Install the MongoDB Driver

Install the current 7.x driver used by this lesson. Keep the exact version in package-lock.json so deployments use the tested dependency.

Install the MongoDB Driver
npm install mongodb@7.4

Complete Promise-based CRUD Script

Complete Promise-based CRUD Script
import { MongoClient } from "mongodb";

const uri = process.env.MONGODB_URI;

if (!uri) {
  throw new Error("Set MONGODB_URI before running this script.");
}

const client = new MongoClient(uri);

try {
  await client.connect();

  const users = client.db("tutorials").collection("users");
  await users.createIndex({ email: 1 }, { unique: true });

  const insertResult = await users.insertOne({
    name: "Asha",
    email: "asha@example.test",
    active: true
  });

  const savedUser = await users.findOne(
    { _id: insertResult.insertedId },
    { projection: { name: 1, email: 1, active: 1 } }
  );

  const updateResult = await users.updateOne(
    { _id: insertResult.insertedId },
    { $set: { active: false } }
  );

  const deleteResult = await users.deleteOne({
    _id: insertResult.insertedId
  });

  console.log({
    savedUser,
    updated: updateResult.modifiedCount,
    deleted: deleteResult.deletedCount
  });
} finally {
  await client.close();
}

// Run as an ES module:
// MONGODB_URI="mongodb://127.0.0.1:27017" node mongo-crud.mjs

The script connects once, selects a database and collection, performs create-read-update-delete operations, checks operation counts, and closes the client even when an operation throws.

Reuse One Client in an Application

Reuse One Client in an Application
import { MongoClient } from "mongodb";

const uri = process.env.MONGODB_URI;
if (!uri) throw new Error("MONGODB_URI is required.");

const client = new MongoClient(uri);
let connectionPromise;

export async function getDatabase() {
  connectionPromise ??= client.connect();
  await connectionPromise;
  return client.db("tutorials");
}

export async function closeDatabase() {
  await client.close();
}

Application modules can call getDatabase without creating a new socket pool for every request. Call closeDatabase during graceful shutdown, not after every query.

Filter Project Sort and Limit Results

Filter Project Sort and Limit Results
const recentUsers = await users
  .find(
    { active: true, createdAt: { $gte: new Date("2026-01-01") } },
    { projection: { name: 1, email: 1, createdAt: 1 } }
  )
  .sort({ createdAt: -1 })
  .limit(20)
  .toArray();

console.log(recentUsers);

find returns a cursor. Add filters before materializing it with toArray, project only required fields, and cap result size for predictable memory and response time.

MongoClient Lifecycle

Create one `MongoClient` for the Node.js process and reuse its built-in connection pool. Connect during startup or lazily through one guarded initialization path, then share database and collection handles through repositories. Creating a client per request adds connection overhead and can overload both the application and MongoDB.

Load the connection string and database name from validated configuration. Use TLS and an application identity with the minimum roles required. Keep test, staging, and production databases distinct, and never log the URI because it may contain credentials and topology information.

Expose readiness only after the application can perform the required database operation, and close the client during graceful shutdown. Monitor checkout wait, pool size, server selection, command duration, and topology changes so a network problem is not misdiagnosed as a slow route.

  • Reuse one MongoClient and its pool per process.
  • Validate configuration and protect connection credentials.
  • Separate environment databases and least-privilege identities.
  • Close cleanly and monitor pool and topology health.

Document Contract

Design documents around the reads and updates the application performs. Embed bounded data that belongs to one aggregate and is usually read together; reference entities that grow independently, are shared broadly, or need separate authorization. Unbounded embedded arrays make documents expensive to update and eventually approach size limits.

Validate incoming documents in Node.js and add MongoDB collection validation for durable invariants. Normalize optional fields and dates, reject unknown writable properties, and construct a new document instead of spreading the request body directly into an insert or update.

Treat `_id` conversion as a boundary. Validate the string before constructing an ObjectId and return a client error for malformed input. Query by tenant or owner together with the object identifier so retrieving a valid ID cannot cross an authorization boundary.

  • Choose embedding or references from ownership and growth.
  • Validate application input and collection documents.
  • Allowlist writable fields instead of persisting request bodies.
  • Include tenant and ownership scope in each sensitive query.

CRUD and Atomicity

Use `findOne`, bounded `find` queries, `insertOne`, `updateOne`, `findOneAndUpdate`, and `deleteOne` according to the response needed. Inspect `matchedCount`, `modifiedCount`, `upsertedId`, or `deletedCount`; an acknowledged command can still match no document.

Single-document writes are atomic. Keep related invariant data in one document when that boundary fits the domain. For multi-document consistency, use a transaction only on a deployment that supports it, pass one session to every operation, keep the transaction short, and retry according to official driver labels rather than retrying every error.

Use update operators such as `$set`, `$inc`, and `$push` with allowlisted paths instead of replacing an entire document accidentally. Protect stale edits with a version field or expected current value. Avoid accepting raw operators from the client, which can turn an update endpoint into an unintended query language.

  • Check result counts before reporting success.
  • Prefer one-document atomicity when the model fits.
  • Keep transaction operations on one session.
  • Allowlist update operators and field paths.

Indexes and Pagination

Build indexes for actual filters and sort order, including tenant scope where required. Use unique indexes for durable uniqueness and partial or TTL indexes only when their semantics match the product. An index that supports one query can increase memory, storage, and every write, so inspect query plans and production metrics.

Use a stable indexed cursor for large result sets, such as `(createdAt, _id)`, rather than increasingly expensive `skip` values. Encode the cursor opaquely, validate it, preserve filter scope, and request one extra result to determine whether another page exists. Always cap page size.

Projection is both a performance and disclosure control. Return only fields required by the API and avoid pulling large arrays or private values into Node.js merely to discard them. Aggregate pipelines also need bounded stages, early filtering, and memory review before production use.

  • Match compound index order to equality, range, and sort use.
  • Enforce uniqueness in the database.
  • Use stable cursor pagination for growing collections.
  • Project only fields the caller may receive.

MongoDB Verification

Run integration tests against MongoDB for ObjectId handling, collection validation, indexes, update operators, transaction behavior, and driver result counts. Cover missing documents, duplicate keys, stale versions, malformed filters, timeout, network interruption, and a restart during an idempotent write.

Monitor command latency, server selection, connection checkout, slow queries, replication health, storage, and index usage. Correlate operations with request IDs while redacting documents and credentials. A useful log names the repository operation and outcome, not every private field.

Back up and restore test data, rehearse index builds and schema migrations, and define retention for expired documents. A flexible schema reduces migration ceremony but does not remove the need to version application assumptions and handle older documents safely.

  • Use real-engine integration tests for driver and index behavior.
  • Version document assumptions and migrate old shapes deliberately.
  • Observe pool, query, replication, and storage signals.
  • Rehearse backup, restore, and outage recovery.

Consistency and Transaction Boundaries

Design a document so one common business change is usually one atomic document update. MongoDB updates to a single document are atomic, which often removes the need for a multi-document transaction. Use operators such as `$set`, `$inc`, `$push`, and conditional filters to express the change on the server instead of reading, modifying, and replacing from application memory.

When an invariant genuinely spans documents or collections, run the work in a session transaction supported by the deployment topology. Pass the session to every operation, keep the transaction short, avoid external API calls inside it, and handle transient transaction labels according to the driver guidance. A transaction is not a substitute for a document model aligned with access and update patterns.

Select read concern, write concern, and read preference from the durability and freshness contract. Stronger guarantees can cost latency or availability, while secondary reads may be stale. State those choices at the repository boundary so callers know whether a successful response means acknowledged, majority-durable, or merely locally observed work.

Test concurrent conditional updates, duplicate-key races, transient transaction failures, primary changes, retry behavior, and ambiguous network outcomes. Use stable operation IDs or unique constraints when a retry must not create duplicate effects, and inspect persisted documents after the test rather than trusting only driver return values.

  • Prefer atomic single-document updates when the model allows them.
  • Use sessions consistently for true multi-document invariants.
  • Document durability and freshness choices at the data boundary.
  • Make retried writes safe against duplicate effects.
Before you move on

MongoDB CRUD Review

6 checks
  • Load the connection URI from the environment and keep credentials out of source control.
  • Reuse one MongoClient in a long-running application instead of reconnecting per request.
  • Await each operation and inspect insertedId, matchedCount, modifiedCount, or deletedCount as appropriate.
  • Limit and project query results instead of loading unbounded documents and fields.
  • Validate request data and authorize the intended operation before building a database filter.
  • Close the client in finally for scripts or during graceful shutdown for servers.

Node JS Questions Learners Ask

Creating a new MongoDB connection for every request adds latency, burns sockets, and can overwhelm the database under traffic.

Validate allowed fields, types, length, and value ranges, then authorize what the caller may read or change. Build the filter from validated fields instead of spreading an untrusted request object into the query.

MongoDB write methods return operation metadata, not always the complete document you want to send back. For example, insertOne returns an insertedId and acknowledgement details.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.