Photo by Eric Er on Unsplash

On this page

This chapter follows Authentication in MongoDB. It uses the official mongodb driver and assumes you already have a connected Db from Installing and connecting to MongoDB.

Documents, BSON, and ObjectId

A document is a JSON-like object stored as BSON (Binary JSON). BSON adds types JSON lacks: ObjectId, Date, Decimal128, Binary, and more. Every inserted document gets an _id field; if you omit it, the server generates an ObjectId.

Collections and databases are created implicitly on first write—there is no separate CREATE DATABASE step.

src/types/product.ts
import { ObjectId } from 'mongodb';
export interface Product {
name: string;
price: number;
tags: string[];
createdAt: Date;
}
export type ProductDoc = Product & { _id: ObjectId };

Use Collection<Product> for inserts and WithId<Product> when reading:

src/repos/products.ts
import { Collection, Db, ObjectId, OptionalId, WithId } from 'mongodb';
import type { Product } from '../types/product.js';
export function products(db: Db): Collection<Product> {
return db.collection<Product>('products');
}

Create: insertOne and insertMany

src/repos/products.ts
export async function createProduct(
col: Collection<Product>,
data: OptionalId<Product>,
) {
const result = await col.insertOne(data);
return result.insertedId;
}
export async function createMany(
col: Collection<Product>,
items: OptionalId<Product>[],
) {
const result = await col.insertMany(items);
return result.insertedIds;
}

insertedId and insertedIds tell you what the server assigned.

Read: find, findOne, projection, sort

find() returns a cursor; materialize with toArray(), iteration, or for await.

const cursor = col.find({ price: { $gte: 10 } })
.project({ name: 1, price: 1 })
.sort({ price: -1 })
.limit(20);
const page: WithId<Product>[] = await cursor.toArray();
const one = await col.findOne({ name: 'Apple' });

Common query operators:

Operator Meaning
$eq, $ne Equal / not equal
$gt, $gte, $lt, $lte Comparisons
$in, $nin Membership
$regex Pattern match (use index carefully)
$and, $or Boolean composition

Count with countDocuments(filter)—avoid deprecated cursor .count().

Update: operators and upsert

Updates use update operators; passing a plain document without operators replaces fields incorrectly in some APIs.

await col.updateOne(
{ name: 'Apple' },
{ $set: { price: 1.2 }, $inc: { version: 1 } },
);
await col.updateOne(
{ sku: 'NEW-001' },
{ $setOnInsert: { name: 'New item', createdAt: new Date() } },
{ upsert: true },
);

Array updates:

await col.updateOne(
{ _id: id },
{ $push: { tags: 'sale' }, $addToSet: { tags: 'organic' } },
);
await col.updateOne(
{ _id: id, 'items.sku': 'A1' },
{ $set: { 'items.$.qty': 3 } },
);
await col.updateOne(
{ _id: id },
{ $pull: { tags: 'sale' } },
);

findOneAndUpdate returns the document before or after the change—useful for atomic counters:

const updated = await col.findOneAndUpdate(
{ _id: id },
{ $inc: { views: 1 } },
{ returnDocument: 'after' },
);

Delete and replace

await col.deleteOne({ _id: id });
await col.deleteMany({ archived: true });
await col.replaceOne({ _id: id }, { name: 'Only these fields remain', price: 0 });

replaceOne replaces the entire document except _id; prefer $set for partial updates.

bulkWrite

Batch mixed operations in one round trip:

await col.bulkWrite([
{ insertOne: { document: { name: 'Bulk', price: 1, tags: [], createdAt: new Date() } } },
{ updateOne: { filter: { name: 'Bulk' }, update: { $set: { price: 2 } } } },
{ deleteOne: { filter: { name: 'Bulk' } } },
]);

ObjectId versus string at API boundaries

JSON has no ObjectId type. HTTP clients send _id as a 24-character hex string. Validate before querying:

src/lib/objectId.ts
import { ObjectId } from 'mongodb';
export function parseObjectId(value: string): ObjectId {
if (!ObjectId.isValid(value)) {
throw new Error('Invalid id');
}
return new ObjectId(value);
}

Expose string ids in REST responses (id.toHexString()) and parse on input. Using strings everywhere in your domain layer is fine if you convert at the repository boundary.

Next: Schema design in MongoDB.

Check your understanding

Question 1 of 7

0 of 7 checked

  1. 1. What happens to `_id` when you insert a document without one?

Search articles

Type a word to search published articles.

Open the search page