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 fullMongoClientOptions
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();
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 }
or404
.
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: "a@example.com" });
await repo.findById("64f7a8d...");
// Create
await repo.create({ email: "alice@example.com" });
// Update
await repo.updateOne({ email: "alice@example.com" }, { active: false });
await repo.updateById("64f7a8d...", { name: "Alice Updated" });
// Delete
await repo.deleteOne({ email: "a@example.com" });
await repo.deleteById("64f7a8d...");
// --- createMany ---
const created = await users.createMany([
{ name: "Ada Lovelace", email: "ada@example.com", role: "admin" },
{ name: "Grace Hopper", email: "grace@example.com" }, // 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