Skip to main content

Mongo

Feature powered by MongoDB Driver | Zod

Intro

The CrumbJS Mongo Plugin brings first-class MongoDB support into your CrumbJS applications.
It provides everything you need to go from a plain collection to a fully validated REST API in minutes:

  • Connection Manager – simple MONGO_URI setup or multiple named connections with full MongoClientOptions support.
  • Schema helpers – thin wrappers around Zod (document, field, timestamps, softDelete) that enforce consistent schema shapes without hiding Zod’s flexibility.
  • Repository Layer – type-safe repository abstraction with helpers for pagination, filtering, soft deletes, and raw access to the underlying MongoDB collection when you need full control.
  • Auto-CRUD Resources – generate REST endpoints (GET, POST, PUT, PATCH, DELETE) automatically from a Zod schema, complete with validation, soft deletes, hooks, and OpenAPI docs.

In short: define a schema once, and instantly get a secure, documented, production-ready API on top of MongoDB, while still having the flexibility to drop down into raw repositories whenever you need custom logic.

Install

Install the plugin in your API project:

bun install @crumbjs/mongo

Mount

Simple one-connection use case (just URI in env)

MONGO_URI=mongodb://127.0.0.1:27017/?directConnection=true
import { App } from "@crumbjs/core";
import { mongoPlugin, mongo } from "@crumbjs/mongo";

// With MONGO_URI env variable set
const app = new App();
app.prefix("api");
app.use(mongoPlugin());
app.get("/", async () => {
// Raw mongo client query, no repository here
return mongo.db("mydb").collection("my_collection").find().toArray();
});
app.serve();

Multiple named connections

import { App } from "@crumbjs/core";
import { mongoPlugin, mongo } from "@crumbjs/mongo";

const app = new App();
app.prefix("api");
app.use(
mongoPlugin([
{
name: "default",
uri: "mongodb://127.0.0.1:27017/?directConnection=true" /** opts: MongoClientOptions */,
},
{
name: "secondary",
uri: "mongodb://192.168.0.10:27017/?directConnection=true" /** opts: MongoClientOptions */,
},
])
);
app.get("/", async () => {
return mongo.db("mydb").collection("my_collection").find().toArray();
});

app.serve();

Define Schema (Zod only, no magic)

We provide a small set of helpers (document and field) on top of Zod.
They are thin wrappers, designed to enforce standard schema shapes and handle optional/nullable safely.

⚡ Helpers always return Zod schemas.
⚠️ To keep consistency, avoid .optional() — prefer .nullable().default(null).

import { document, field, softDelete, timestamps } from "@crumbjs/mongo";

export const employeeSchema = document({
// System fields
...softDelete(), // deletedAt: Date|null
...timestamps(), // createdAt/updatedAt
// Custom fields
uuid: field.uuid({ auto: true }),
name: field.string({ min: 3 }),
lastName: field.string({ min: 3 }),
email: field.string({ format: "email" }),
birthDate: field.dateString({ nullable: true }),
gender: field.enum(["male", "female", "none"]),
active: field.boolean(true),
userId: field.objectId(),
companyId: field.objectId({ nullable: true }),
});

Auto-CRUD Resources (create, replace, patch, delete, paginate)

When to use AUTO-CRUD

The auto-CRUD feature is designed for straightforward REST exposure of MongoDB collections.
It works best when:

  • Collections contain data that can safely be exposed through a REST API.
  • The main business logic revolves around CRUD operations, with some pre/post hooks (e.g. mutating fields before insert, triggering side-effects after update).
  • You want auto-validated endpoints with OpenAPI documentation out of the box, without writing boilerplate handlers.

When not to use AUTO-CRUD

This approach is not always ideal. Consider avoiding the auto-CRUD resource generator if:

  • You need fine-tuned performance and reduce db round-trips using complex queries.
  • Your responses must exclude or transform fields: auto-CRUD responses always include the entire collection document.
  • Collections hold sensitive data that must be filtered, reshaped, or aggregated before being returned. In these cases, it is safer to use the Repository and implement custom handlers.

In short: use auto-CRUD for quick, schema-driven resources with light business logic. Use the repository for complex, sensitive, or performance-critical scenarios.

Define a resource in your CrumbJS app

Schemas must include _id, createdAt, updatedAt, deletedAt if you want soft deletes.
All endpoints are autovalidated and OpenAPI documented by CrumbJS core.

import { App, Unauthorized } from "@crumbjs/core";
import { createResource, mongoPlugin } from "@crumbjs/mongo";
import { employeeSchema } from "./schemas/employee";

const app = new App();

app.use(mongoPlugin());

app.use(
createResource({
/** Zod schema of documents */
schema: employeeSchema,
/** (optional) Connection name (default: "default") */
connection: "default",
/** Database name */
db: "tuples_hr",
/** Collection name */
collection: "employees",
/**
* (optional) Define wich endpoints you want to include, (default: all enabled)
* @example to create All except "DELETE"
* endpoints: {
* delete: false,
* },
*/
/** (optional) Route prefix (default: collection name) */
prefix: "employee",
/** (optional) OpenAPI tag (default: collection name) */
tag: "Employees",
/** (optional) Middlewares applied to all routes */
use: [authMiddleware],

/**
* (optional) Filter Hook.
* Adds Mongo filters automatically to every operation
* except POST.
*/
prefilter: async (c, triggeredBy) => {
const user = c.get<User>("user");
return { userId: user.id };
},

/**
* (optional) Hook before creating documents (must throw to block).
*/
beforeCreate: async (c, doc) => {
const user = c.get<User>("user");
if (!user.roles.includes("create:employees")) {
throw new Unauthorized("You don't have access to create employees");
}
},

/** ...other hooks */
})
);

app.serve();

Example resource documentation

Generated REST Endpoints

When you define a resource with createResource, the following endpoints are created (prefix defaults to collection name):

GET /{prefix}

  • Returns a paginated list of documents.
  • Supports query filters (field=value), pagination (page, pageSize), sort (sortField,sortDirection), and soft-deleted docs (withTrash=yes).

GET /{prefix}/:id

  • Returns a single document by ObjectId.
  • Responds 404 if not found.

POST /{prefix}

  • Creates a new document.
  • Validates body against schema.
  • Runs beforeCreate hook (must throw to block).
  • Runs afterCreate hook if defined.

PUT /{prefix}/:id

  • Replace an entire document.
  • Requires a full valid body (minus system fields).
  • Runs beforeUpdate / afterUpdate hooks.
  • Returns updated doc or 404.

PATCH /{prefix}/:id

  • Partial update by ID.
  • At least one field required, otherwise 422.
  • Runs beforePatch / afterPatch hooks.
  • Returns updated doc or 404.

DELETE /{prefix}/:id

  • Soft deletes by default (deletedAt timestamp).
  • Runs beforeDelete / afterDelete hooks.
  • Returns { success: true|false } or 404.

Repository

Basic usage

import { useRepository } from "@crumbjs/mongo";
import { employeeSchema } from "./schemas/employee";

const employeeRepository = useRepository(
"mydb", // Database name
"employees", // Collection name
employeeSchema, // Zod schema
"deletedAt", // Soft delete field (false disables soft deletes)
"default" // Connection name
);

Advanced repository

import { Repository, db } from "@crumbjs/mongo";
import { employeeSchema } from "./schemas/employee";

export class EmployeeRepository extends Repository<typeof employeeSchema> {
constructor() {
super(db("mydb"), "employees", employeeSchema);
}

async complexThings() {
return this.collection
.aggregate([
/* ... */
])
.toArray();
}
}

Repository methods

// Count
await repo.count();

// Get (with or without soft deletes)
await repo.get();
await repo.get({ active: true }, { _id: -1 }, true);

// Paginate
await repo.getPaiginated(); // equivalent to: await repo.getPaginated({}, {}, 1, 10);
// some filters, order by _id desc, page 2, limit 20
await repo.getPaginated({ active: true }, { _id: -1 }, 2, 20);

// Find
await repo.findOne({ email: "[email protected]" });
await repo.findById("64f7a8d...");

// Create
await repo.create({ email: "[email protected]" });

// Update
await repo.updateOne({ email: "[email protected]" }, { active: false });
await repo.updateById("64f7a8d...", { name: "Alice Updated" });

// Delete
await repo.deleteOne({ email: "[email protected]" });
await repo.deleteById("64f7a8d...");

// --- createMany ---
const created = await users.createMany([
{ name: "Ada Lovelace", email: "[email protected]", role: "admin" },
{ name: "Grace Hopper", email: "[email protected]" }, // role -> "user" by default
]);

// --- updateMany ---
// Promote all regular users to admin
const result = await users.updateMany(
{ role: "user" }, // MongoDB filter
{ role: "admin" } // partial payload
);

Mongo Connection Manager

Can be used standalone (e.g. seeders, scripts):

import { mongo } from "@crumbjs/mongo";

mongo.add({
uri: "...",
name: "example",
opts: {
/* MongoClientOptions */
},
});

await mongo.connect(); // Connects all registered connections
mongo.get("example"); // MongoClient
mongo.db("users", "example"); // Db instance