UXDL Docs

MongoDB

Mongoose (Node) and Motor/Beanie (Python) — models and document patterns.

Use MongoDB for document-oriented data — activity feeds, flexible schemas, high-volume writes, or services that don't fit a relational model.

Install

bash
pnpm add mongoose

Connection

typescript
// src/db/mongo.ts
import mongoose from "mongoose";
 
export async function connectMongo() {
  await mongoose.connect(process.env.MONGODB_URI!);
  console.log("MongoDB connected");
}
 
mongoose.connection.on("error", (err) => {
  console.error("MongoDB error:", err);
});

Call in server.ts before starting Express:

typescript
import { connectMongo } from "./db/mongo";
 
await connectMongo();
app.listen(PORT, ...);

Schema & model

typescript
// src/models/activity.model.ts
import { Schema, model, Document } from "mongoose";
 
export interface IActivity extends Document {
  userId: string;
  action: string;
  metadata: Record<string, unknown>;
  createdAt: Date;
}
 
const activitySchema = new Schema<IActivity>(
  {
    userId: { type: String, required: true, index: true },
    action: { type: String, required: true },
    metadata: { type: Schema.Types.Mixed, default: {} },
  },
  { timestamps: { createdAt: true, updatedAt: false } },
);
 
activitySchema.index({ userId: 1, createdAt: -1 });
 
export const Activity = model<IActivity>("Activity", activitySchema);

Usage in services

typescript
// src/services/activity.service.ts
import { Activity } from "../models/activity.model";
 
export async function logActivity(userId: string, action: string, metadata = {}) {
  return Activity.create({ userId, action, metadata });
}
 
export async function getRecentActivity(userId: string, limit = 20) {
  return Activity.find({ userId }).sort({ createdAt: -1 }).limit(limit).lean();
}

Python (FastAPI) — Beanie

Python services use Beanie (async ODM built on Motor + Pydantic). Documents are declared as Pydantic models, mirroring the Mongoose schema above.

bash
uv add beanie motor
python
# app/models/activity.py
from datetime import datetime, timezone
 
import pymongo
from beanie import Document
from pydantic import Field
 
 
class Activity(Document):
    user_id: str
    action: str
    metadata: dict = {}
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
 
    class Settings:
        name = "activities"
        indexes = [
            [("user_id", pymongo.ASCENDING), ("created_at", pymongo.DESCENDING)],
        ]
python
# app/db/mongo.py
from beanie import init_beanie
from motor.motor_asyncio import AsyncIOMotorClient
 
from app.core.config import settings
from app.models.activity import Activity
 
 
async def connect_mongo() -> None:
    client = AsyncIOMotorClient(settings.mongodb_uri)
    await init_beanie(database=client.app, document_models=[Activity])
python
# app/services/activity.py
from app.models.activity import Activity
 
 
async def log_activity(user_id: str, action: str, metadata: dict | None = None) -> Activity:
    activity = Activity(user_id=user_id, action=action, metadata=metadata or {})
    return await activity.insert()
 
 
async def get_recent_activity(user_id: str, limit: int = 20) -> list[Activity]:
    return (
        await Activity.find(Activity.user_id == user_id)
        .sort(-Activity.created_at)
        .limit(limit)
        .to_list()
    )

Call connect_mongo() from the app lifespan startup, the same way connectMongo() runs before app.listen() on the Node side.

Environment

dotenv
MONGODB_URI=mongodb://localhost:27017/app
# Production
MONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/app

Local with Docker

yaml
# docker-compose.yml
services:
  mongo:
    image: mongo:7
    ports: ["27017:27017"]
    volumes: ["mongo_data:/data/db"]

Best practices

  • Always define indexes in the schema — don't rely on full collection scans
  • Use .lean() for read-only queries (returns plain objects, faster)
  • Avoid deep nesting — prefer flat documents with references for large arrays
  • Use transactions for multi-document writes that must be atomic

Official documentation