MongoDB
MongoDB adapter setup, collection naming, migrations, and when to choose the document path.
The MongoDB adapter maps Better Media's five tables to five collections in a single database.
Installation
pnpm add @better-media/adapter-databases/mongodb-adapter mongodbConfiguration
Pass a connected MongoClient and the target database name:
import { MongoClient } from "mongodb";
import { MongoDbAdapter } from "@better-media/adapter-databases/mongodb-adapter";
import { schema } from "@better-media/core";
const client = new MongoClient(process.env.MONGODB_URI!);
await client.connect();
const database = new MongoDbAdapter(client, {
config: { databaseName: "my-app" },
schema,
});
const media = createBetterMedia({ storage, database, plugins });The adapter uses client.db(databaseName) internally. No other connection configuration is needed — pass the options you need directly to MongoClient.
Connection String Formats
# Local development
MONGODB_URI=mongodb://localhost:27017
# MongoDB Atlas
MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/?retryWrites=true&w=majority
# With auth and database
MONGODB_URI=mongodb://user:password@host:27017/my-app?authSource=adminCollections
Each Better Media table maps to a MongoDB collection with the same name:
| Collection | Contents |
|---|---|
media | Core media records |
media_versions | Derivative files (thumbnails, compressed variants) |
media_jobs | Background job records |
media_validation_results | Per-file validation outcomes |
media_virus_scan_results | Per-file scan results |
Migrations
The MongoDB adapter does not use SQL migrations. Instead it creates collections and indexes on demand via __initCollection. Run schema setup on application startup:
import { runMigrations } from "@better-media/core";
await runMigrations(database, schema);This is idempotent — it only creates collections and indexes that do not already exist. Existing data is never modified.
Indexes
The adapter automatically creates indexes based on the schema definition:
- Unique fields (e.g.
idempotencyKey) — unique single-field index - Foreign key fields (e.g.
mediaId) — non-unique index for fast lookups - Compound indexes from
schema[model].indexes— e.g.{ mediaId, versionNumber }unique
Transactions
MongoDB multi-document transactions require a replica set or Atlas cluster (standalone instances do not support sessions).
await database.transaction(async (trx) => {
await trx.update({
model: "media",
where: [{ field: "id", value: recordId }],
update: { status: "quarantined" },
});
await trx.create({
model: "media_virus_scan_results",
data: { id: randomUUID(), mediaId: recordId, status: "infected", threats: [...] },
});
});If transactions are not available (standalone instance), wrap operations in your own error-handling logic instead.
Raw Commands
database.raw() accepts a JSON command string and forwards it to db.command():
const result = await database.raw('{"ping": 1}');For aggregation pipelines or complex queries, access the MongoDB collection directly through your MongoClient instance.
When to Choose MongoDB
MongoDB is a good fit when:
- Your application already runs MongoDB and adding a second database is undesirable
- You store large, variable-shape metadata in the
media.metadataJSON field and want to query it directly - You need horizontal sharding at scale
Choose a SQL adapter if:
- You need strong referential integrity and JOIN queries across your application tables
- Your team is more comfortable with relational tooling
- You want the CLI migration workflow (
generate/migratecommands)