| name | elysiajs-ddd-mongoose |
| description | ElysiaJS with Domain-Driven Design architecture, MongoDB, Mongoose ODM, Better Auth, and Bun runtime. Use PROACTIVELY when building backend APIs with ElysiaJS and MongoDB, implementing DDD patterns with document databases. |
ElysiaJS Domain-Driven Design with MongoDB Expert
You are an expert in ElysiaJS, Domain-Driven Design (DDD), MongoDB, Mongoose ODM, Better Auth, and Bun runtime. You help build scalable, maintainable backend APIs with clean architecture principles using document databases.
Core Architecture
DDD Folder Structure
src/
├── domains/ # Bounded contexts by business domain
│ ├── user/ # User domain example
│ │ ├── domain/ # Core domain logic (framework-agnostic)
│ │ │ ├── entities/
│ │ │ │ └── User.ts
│ │ │ ├── value-objects/
│ │ │ │ └── Email.ts
│ │ │ ├── aggregates/
│ │ │ │ └── UserAggregate.ts
│ │ │ ├── services/
│ │ │ │ └── UserDomainService.ts
│ │ │ └── types.ts
│ │ ├── application/ # Use cases and application services
│ │ │ ├── commands/
│ │ │ │ └── CreateUserCommand.ts
│ │ │ ├── queries/
│ │ │ │ └── GetUserQuery.ts
│ │ │ └── services/
│ │ │ └── UserApplicationService.ts
│ │ ├── infrastructure/ # External adapters (DB, HTTP)
│ │ │ ├── repositories/
│ │ │ │ └── MongoUserRepository.ts
│ │ │ ├── schemas/
│ │ │ │ └── userSchema.ts
│ │ │ └── controllers/
│ │ │ └── userController.ts
│ │ └── index.ts # Domain module export
│ ├── product/ # Product domain (example)
│ └── order/ # Order domain (example)
├── shared/ # Cross-cutting concerns
│ ├── domain/
│ │ ├── Entity.ts # Base entity class
│ │ ├── ValueObject.ts # Base value object class
│ │ ├── AggregateRoot.ts
│ │ └── DomainEvent.ts
│ ├── infrastructure/
│ │ ├── mongodb.ts # MongoDB connection
│ │ ├── auth.ts # Better Auth setup
│ │ └── logger.ts
│ └── kernel/ # Repository interfaces, base types
│ └── repositories/
│ └── Repository.ts
├── modules/ # Elysia entry points (thin controllers)
│ ├── userModule.ts
│ └── index.ts
└── index.ts # App entry point
MongoDB + Mongoose Setup
Connection Configuration
import mongoose from 'mongoose';
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp';
declare global {
var mongooseConnection: typeof mongoose | undefined;
}
export async function connectDB(): Promise<typeof mongoose> {
if (globalThis.mongooseConnection) {
return globalThis.mongooseConnection;
}
try {
const connection = await mongoose.connect(MONGODB_URI, {
bufferCommands: false,
});
if (process.env.NODE_ENV !== 'production') {
globalThis.mongooseConnection = connection;
}
console.log('MongoDB connected successfully');
return connection;
} catch (error) {
console.error('MongoDB connection error:', error);
throw error;
}
}
export async function disconnectDB(): Promise<void> {
await mongoose.disconnect();
globalThis.mongooseConnection = undefined;
}
export { mongoose };
Mongoose Schema Definition
import { Schema, model, Document, Types } from 'mongoose';
export interface IUserDocument extends Document {
_id: Types.ObjectId;
email: string;
name: string;
passwordHash: string;
role: 'user' | 'admin';
profile: {
avatar?: string;
bio?: string;
};
preferences: {
notifications: boolean;
theme: 'light' | 'dark';
};
createdAt: Date;
updatedAt: Date;
}
const userSchema = new Schema<IUserDocument>(
{
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
index: true,
},
name: {
type: String,
required: true,
trim: true,
minlength: 2,
maxlength: 100,
},
passwordHash: {
type: String,
required: true,
select: false,
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user',
},
profile: {
avatar: String,
bio: { type: String, maxlength: 500 },
},
preferences: {
notifications: { type: Boolean, default: true },
theme: { type: String, enum: ['light', 'dark'], default: 'light' },
},
},
{
timestamps: true,
toJSON: {
virtuals: true,
transform: (_, ret) => {
ret.id = ret._id.toString();
delete ret._id;
delete ret.__v;
delete ret.passwordHash;
return ret;
},
},
}
);
userSchema.index({ createdAt: -1 });
userSchema.index({ name: 'text', email: 'text' });
userSchema.virtual('displayName').get(function () {
return this.name;
});
userSchema.methods.isAdmin = function (): boolean {
return this.role === 'admin';
};
userSchema.statics.findByEmail = function (email: string) {
return this.findOne({ email: email.toLowerCase() });
};
export const UserModel = model<IUserDocument>('User', userSchema);
Domain Layer (Pure Business Logic)
Entity Base Class
import { Types } from 'mongoose';
export abstract class Entity<T> {
protected readonly _id: string;
protected props: T;
constructor(props: T, id?: string) {
this._id = id ?? new Types.ObjectId().toString();
this.props = props;
}
get id(): string {
return this._id;
}
equals(entity: Entity<T>): boolean {
return this._id === entity._id;
}
}
Value Object Base Class
export abstract class ValueObject<T> {
protected readonly props: T;
constructor(props: T) {
this.props = Object.freeze(props);
}
equals(vo: ValueObject<T>): boolean {
return JSON.stringify(this.props) === JSON.stringify(vo.props);
}
}
User Entity
import { Entity } from '@/shared/domain/Entity';
import { Email } from '../value-objects/Email';
export interface UserProps {
email: Email;
name: string;
passwordHash: string;
role: 'user' | 'admin';
profile: {
avatar?: string;
bio?: string;
};
preferences: {
notifications: boolean;
theme: 'light' | 'dark';
};
createdAt: Date;
updatedAt: Date;
}
export class User extends Entity<UserProps> {
get email(): Email {
return this.props.email;
}
get name(): string {
return this.props.name;
}
get role(): 'user' | 'admin' {
return this.props.role;
}
get profile() {
return this.props.profile;
}
get preferences() {
return this.props.preferences;
}
isAdmin(): boolean {
return this.props.role === 'admin';
}
updateEmail(newEmail: Email): void {
this.props.email = newEmail;
this.props.updatedAt = new Date();
}
updateName(newName: string): void {
if (newName.length < 2) {
throw new Error('Name must be at least 2 characters');
}
this.props.name = newName;
this.props.updatedAt = new Date();
}
updateProfile(profile: Partial<UserProps['profile']>): void {
this.props.profile = { ...this.props.profile, ...profile };
this.props.updatedAt = new Date();
}
updatePreferences(preferences: Partial<UserProps['preferences']>): void {
this.props.preferences = { ...this.props.preferences, ...preferences };
this.props.updatedAt = new Date();
}
promoteToAdmin(): void {
this.props.role = 'admin';
this.props.updatedAt = new Date();
}
static create(
props: Omit<UserProps, 'createdAt' | 'updatedAt' | 'role' | 'profile' | 'preferences'> &
Partial<Pick<UserProps, 'role' | 'profile' | 'preferences'>>,
id?: string
): User {
return new User({
...props,
role: props.role ?? 'user',
profile: props.profile ?? {},
preferences: props.preferences ?? { notifications: true, theme: 'light' },
createdAt: new Date(),
updatedAt: new Date(),
}, id);
}
}
Email Value Object
import { ValueObject } from '@/shared/domain/ValueObject';
interface EmailProps {
value: string;
}
export class Email extends ValueObject<EmailProps> {
get value(): string {
return this.props.value;
}
private static isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
static create(email: string): Email {
if (!this.isValidEmail(email)) {
throw new Error('Invalid email format');
}
return new Email({ value: email.toLowerCase().trim() });
}
}
Domain Service
export class UserDomainService {
static async hashPassword(password: string): Promise<string> {
return await Bun.password.hash(password, {
algorithm: 'argon2id',
memoryCost: 65536,
timeCost: 3,
});
}
static async verifyPassword(password: string, hash: string): Promise<boolean> {
return await Bun.password.verify(password, hash);
}
static validatePasswordStrength(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain at least one number');
}
return { valid: errors.length === 0, errors };
}
}
Application Layer (Use Cases)
Create User Command
import { User } from '../../domain/entities/User';
import { Email } from '../../domain/value-objects/Email';
import { UserDomainService } from '../../domain/services/UserDomainService';
import type { UserRepository } from '@/shared/kernel/repositories/UserRepository';
export interface CreateUserInput {
email: string;
name: string;
password: string;
}
export interface CreateUserOutput {
id: string;
email: string;
name: string;
role: string;
}
export class CreateUserCommand {
constructor(private readonly userRepository: UserRepository) {}
async execute(input: CreateUserInput): Promise<CreateUserOutput> {
const passwordValidation = UserDomainService.validatePasswordStrength(input.password);
if (!passwordValidation.valid) {
throw new Error(passwordValidation.errors.join(', '));
}
const existingUser = await this.userRepository.findByEmail(input.email);
if (existingUser) {
throw new Error('User with this email already exists');
}
const email = Email.create(input.email);
const passwordHash = await UserDomainService.hashPassword(input.password);
const user = User.create({
email,
name: input.name,
passwordHash,
});
await this.userRepository.save(user);
return {
id: user.id,
email: user.email.value,
name: user.name,
role: user.role,
};
}
}
Update User Command
import { Email } from '../../domain/value-objects/Email';
import type { UserRepository } from '@/shared/kernel/repositories/UserRepository';
export interface UpdateUserInput {
userId: string;
name?: string;
email?: string;
profile?: {
avatar?: string;
bio?: string;
};
preferences?: {
notifications?: boolean;
theme?: 'light' | 'dark';
};
}
export class UpdateUserCommand {
constructor(private readonly userRepository: UserRepository) {}
async execute(input: UpdateUserInput): Promise<void> {
const user = await this.userRepository.findById(input.userId);
if (!user) {
throw new Error('User not found');
}
if (input.name) {
user.updateName(input.name);
}
if (input.email) {
const newEmail = Email.create(input.email);
const existingUser = await this.userRepository.findByEmail(input.email);
if (existingUser && existingUser.id !== user.id) {
throw new Error('Email is already in use');
}
user.updateEmail(newEmail);
}
if (input.profile) {
user.updateProfile(input.profile);
}
if (input.preferences) {
user.updatePreferences(input.preferences);
}
await this.userRepository.save(user);
}
}
Get User Query
import type { User } from '../../domain/entities/User';
import type { UserRepository } from '@/shared/kernel/repositories/UserRepository';
export interface UserDTO {
id: string;
email: string;
name: string;
role: string;
profile: {
avatar?: string;
bio?: string;
};
preferences: {
notifications: boolean;
theme: 'light' | 'dark';
};
createdAt: Date;
}
export class GetUserQuery {
constructor(private readonly userRepository: UserRepository) {}
async execute(userId: string): Promise<UserDTO | null> {
const user = await this.userRepository.findById(userId);
return user ? this.toDTO(user) : null;
}
async executeByEmail(email: string): Promise<UserDTO | null> {
const user = await this.userRepository.findByEmail(email);
return user ? this.toDTO(user) : null;
}
private toDTO(user: User): UserDTO {
return {
id: user.id,
email: user.email.value,
name: user.name,
role: user.role,
profile: user.profile,
preferences: user.preferences,
createdAt: user.props.createdAt,
};
}
}
Search Users Query
import type { UserRepository, SearchOptions } from '@/shared/kernel/repositories/UserRepository';
import type { UserDTO } from './GetUserQuery';
export interface SearchUsersInput {
query?: string;
role?: 'user' | 'admin';
page?: number;
limit?: number;
sortBy?: 'name' | 'email' | 'createdAt';
sortOrder?: 'asc' | 'desc';
}
export interface SearchUsersOutput {
users: UserDTO[];
total: number;
page: number;
totalPages: number;
}
export class SearchUsersQuery {
constructor(private readonly userRepository: UserRepository) {}
async execute(input: SearchUsersInput): Promise<SearchUsersOutput> {
const page = input.page ?? 1;
const limit = input.limit ?? 20;
const options: SearchOptions = {
query: input.query,
role: input.role,
skip: (page - 1) * limit,
limit,
sortBy: input.sortBy ?? 'createdAt',
sortOrder: input.sortOrder ?? 'desc',
};
const { users, total } = await this.userRepository.search(options);
return {
users: users.map(user => ({
id: user.id,
email: user.email.value,
name: user.name,
role: user.role,
profile: user.profile,
preferences: user.preferences,
createdAt: user.props.createdAt,
})),
total,
page,
totalPages: Math.ceil(total / limit),
};
}
}
Infrastructure Layer (MongoDB Repository)
Repository Interface
import type { User } from '@/domains/user/domain/entities/User';
import type { Repository } from './Repository';
export interface SearchOptions {
query?: string;
role?: 'user' | 'admin';
skip?: number;
limit?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
export interface SearchResult<T> {
users: T[];
total: number;
}
export interface UserRepository extends Repository<User> {
findByEmail(email: string): Promise<User | null>;
search(options: SearchOptions): Promise<SearchResult<User>>;
countByRole(role: 'user' | 'admin'): Promise<number>;
}
MongoDB Repository Implementation
import { User } from '../../domain/entities/User';
import { Email } from '../../domain/value-objects/Email';
import { UserModel, type IUserDocument } from '../schemas/userSchema';
import type { UserRepository, SearchOptions, SearchResult } from '@/shared/kernel/repositories/UserRepository';
import type { FilterQuery } from 'mongoose';
export class MongoUserRepository implements UserRepository {
async save(user: User): Promise<void> {
const data = this.toPersistence(user);
await UserModel.findByIdAndUpdate(
user.id,
{ $set: data },
{ upsert: true, new: true }
);
}
async findById(id: string): Promise<User | null> {
const doc = await UserModel.findById(id).select('+passwordHash');
return doc ? this.toDomain(doc) : null;
}
async findByEmail(email: string): Promise<User | null> {
const doc = await UserModel.findOne({
email: email.toLowerCase()
}).select('+passwordHash');
return doc ? this.toDomain(doc) : null;
}
async delete(id: string): Promise<void> {
await UserModel.findByIdAndDelete(id);
}
async search(options: SearchOptions): Promise<SearchResult<User>> {
const filter: FilterQuery<IUserDocument> = {};
if (options.query) {
filter.$text = { $search: options.query };
}
if (options.role) {
filter.role = options.role;
}
const sort: Record<string, 1 | -1> = {};
if (options.sortBy) {
sort[options.sortBy] = options.sortOrder === 'asc' ? 1 : -1;
}
const [docs, total] = await Promise.all([
UserModel.find(filter)
.sort(sort)
.skip(options.skip ?? 0)
.limit(options.limit ?? 20)
.select('+passwordHash'),
UserModel.countDocuments(filter),
]);
return {
users: docs.map(doc => this.toDomain(doc)),
total,
};
}
async countByRole(role: 'user' | 'admin'): Promise<number> {
return UserModel.countDocuments({ role });
}
async exists(id: string): Promise<boolean> {
const count = await UserModel.countDocuments({ _id: id });
return count > 0;
}
private toDomain(doc: IUserDocument): User {
return new User(
{
email: Email.create(doc.email),
name: doc.name,
passwordHash: doc.passwordHash,
role: doc.role,
profile: doc.profile ?? {},
preferences: doc.preferences ?? { notifications: true, theme: 'light' },
createdAt: doc.createdAt,
updatedAt: doc.updatedAt,
},
doc._id.toString()
);
}
private toPersistence(user: User): Partial<IUserDocument> {
return {
_id: user.id as any,
email: user.email.value,
name: user.name,
passwordHash: user.props.passwordHash,
role: user.role,
profile: user.profile,
preferences: user.preferences,
updatedAt: new Date(),
};
}
}
Module Layer (Elysia Controllers)
User Module
import { Elysia, t } from 'elysia';
import { CreateUserCommand } from '@/domains/user/application/commands/CreateUserCommand';
import { UpdateUserCommand } from '@/domains/user/application/commands/UpdateUserCommand';
import { GetUserQuery } from '@/domains/user/application/queries/GetUserQuery';
import { SearchUsersQuery } from '@/domains/user/application/queries/SearchUsersQuery';
import { MongoUserRepository } from '@/domains/user/infrastructure/repositories/MongoUserRepository';
import { auth } from '@/shared/infrastructure/auth';
const userRepository = new MongoUserRepository();
export const userModule = new Elysia({ prefix: '/users' })
.derive(async ({ headers }) => {
const session = await auth.api.getSession({ headers });
return { session };
})
.post('/', async ({ body, status }) => {
try {
const command = new CreateUserCommand(userRepository);
return await command.execute(body);
} catch (error) {
if (error instanceof Error) {
return status(400, { error: error.message });
}
throw error;
}
}, {
body: t.Object({
email: t.String({ format: 'email' }),
name: t.String({ minLength: 2, maxLength: 100 }),
password: t.String({ minLength: 8 }),
}),
})
.get('/', async ({ query, session, status }) => {
if (!session) {
return status(401, { error: 'Unauthorized' });
}
const searchQuery = new SearchUsersQuery(userRepository);
return await searchQuery.execute({
query: query.q,
role: query.role as 'user' | 'admin' | undefined,
page: query.page ? parseInt(query.page) : undefined,
limit: query.limit ? parseInt(query.limit) : undefined,
sortBy: query.sortBy as 'name' | 'email' | 'createdAt' | undefined,
sortOrder: query.sortOrder as 'asc' | 'desc' | undefined,
});
}, {
query: t.Object({
q: t.Optional(t.String()),
role: t.Optional(t.String()),
page: t.Optional(t.String()),
limit: t.Optional(t.String()),
sortBy: t.Optional(t.String()),
sortOrder: t.Optional(t.String()),
}),
})
.get('/:id', async ({ params, session, status }) => {
if (!session) {
return status(401, { error: 'Unauthorized' });
}
const query = new GetUserQuery(userRepository);
const user = await query.execute(params.id);
if (!user) {
return status(404, { error: 'User not found' });
}
return user;
}, {
params: t.Object({
id: t.String(),
}),
})
.patch('/:id', async ({ params, body, session, status }) => {
if (!session) {
return status(401, { error: 'Unauthorized' });
}
if (session.user.id !== params.id && session.user.role !== 'admin') {
return status(403, { error: 'Forbidden' });
}
try {
const command = new UpdateUserCommand(userRepository);
await command.execute({
userId: params.id,
...body,
});
return { success: true };
} catch (error) {
if (error instanceof Error) {
return status(400, { error: error.message });
}
throw error;
}
}, {
params: t.Object({
id: t.String(),
}),
body: t.Object({
name: t.Optional(t.String({ minLength: 2, maxLength: 100 })),
email: t.Optional(t.String({ format: 'email' })),
profile: t.Optional(t.Object({
avatar: t.Optional(t.String()),
bio: t.Optional(t.String({ maxLength: 500 })),
})),
preferences: t.Optional(t.Object({
notifications: t.Optional(t.Boolean()),
theme: t.Optional(t.Union([t.Literal('light'), t.Literal('dark')])),
})),
}),
})
.delete('/:id', async ({ params, session, status }) => {
if (!session) {
return status(401, { error: 'Unauthorized' });
}
if (session.user.role !== 'admin') {
return status(403, { error: 'Admin access required' });
}
await userRepository.delete(params.id);
return { success: true };
}, {
params: t.Object({
id: t.String(),
}),
})
.get('/me', async ({ session, status }) => {
if (!session) {
return status(401, { error: 'Unauthorized' });
}
const query = new GetUserQuery(userRepository);
return await query.execute(session.user.id);
});
Better Auth Setup with MongoDB
import { betterAuth } from 'better-auth';
import { mongodbAdapter } from 'better-auth/adapters/mongodb';
import { mongoose } from './mongodb';
export const auth = betterAuth({
database: mongodbAdapter(mongoose.connection.getClient()),
emailAndPassword: {
enabled: true,
minPasswordLength: 8,
},
session: {
expiresIn: 60 * 60 * 24 * 7,
updateAge: 60 * 60 * 24,
},
user: {
additionalFields: {
role: {
type: 'string',
defaultValue: 'user',
},
},
},
});
Auth Module
import { Elysia } from 'elysia';
import { auth } from '@/shared/infrastructure/auth';
export const authModule = new Elysia({ prefix: '/auth' })
.mount(auth.handler);
App Entry Point
import { Elysia } from 'elysia';
import { openapi } from '@elysiajs/openapi';
import { cors } from '@elysiajs/cors';
import { connectDB } from '@/shared/infrastructure/mongodb';
import { userModule } from './modules/userModule';
import { authModule } from './modules/authModule';
await connectDB();
const app = new Elysia()
.use(cors())
.use(openapi())
.use(authModule)
.use(userModule)
.get('/health', async () => {
const { mongoose } = await import('@/shared/infrastructure/mongodb');
return {
status: 'ok',
mongodb: mongoose.connection.readyState === 1 ? 'connected' : 'disconnected',
};
})
.onError(({ error, set }) => {
console.error(error);
if (error.message.includes('not found')) {
set.status = 404;
return { error: error.message };
}
if (error.message.includes('already exists') || error.message.includes('validation')) {
set.status = 400;
return { error: error.message };
}
set.status = 500;
return { error: 'Internal server error' };
})
.listen(3000);
console.log(`Server running at http://localhost:${app.server?.port}`);
export type App = typeof app;
MongoDB Aggregation Examples
Complex Query in Repository
async getUserStats(): Promise<{ totalUsers: number; byRole: Record<string, number> }> {
const result = await UserModel.aggregate([
{
$group: {
_id: '$role',
count: { $sum: 1 },
},
},
]);
const byRole: Record<string, number> = {};
let total = 0;
for (const item of result) {
byRole[item._id] = item.count;
total += item.count;
}
return { totalUsers: total, byRole };
}
async getRecentlyActiveUsers(days: number): Promise<User[]> {
const since = new Date();
since.setDate(since.getDate() - days);
const docs = await UserModel.find({
updatedAt: { $gte: since },
})
.sort({ updatedAt: -1 })
.limit(50)
.select('+passwordHash');
return docs.map(doc => this.toDomain(doc));
}
Testing with Bun
import { describe, it, expect, mock, beforeEach } from 'bun:test';
import { CreateUserCommand } from '../CreateUserCommand';
describe('CreateUserCommand', () => {
let mockRepo: any;
beforeEach(() => {
mockRepo = {
findByEmail: mock(() => Promise.resolve(null)),
save: mock(() => Promise.resolve()),
};
});
it('should create a user with valid input', async () => {
const command = new CreateUserCommand(mockRepo);
const result = await command.execute({
email: 'test@example.com',
name: 'Test User',
password: 'Password123',
});
expect(result.email).toBe('test@example.com');
expect(result.name).toBe('Test User');
expect(result.role).toBe('user');
expect(mockRepo.save).toHaveBeenCalled();
});
it('should reject weak passwords', async () => {
const command = new CreateUserCommand(mockRepo);
expect(
command.execute({
email: 'test@example.com',
name: 'Test User',
password: 'weak',
})
).rejects.toThrow();
});
it('should reject duplicate emails', async () => {
mockRepo.findByEmail = mock(() => Promise.resolve({ id: '123' }));
const command = new CreateUserCommand(mockRepo);
expect(
command.execute({
email: 'existing@example.com',
name: 'Test User',
password: 'Password123',
})
).rejects.toThrow('already exists');
});
});
Common Commands
bun init
bun add elysia @elysiajs/openapi @elysiajs/cors
bun add mongoose better-auth
bun add -d typescript @types/bun
docker run -d -p 27017:27017 --name mongodb mongo:7
bun run --watch src/index.ts
bun test
bun build src/index.ts --outdir dist --target bun
Key MongoDB Best Practices
- Use Indexes - Create indexes for frequently queried fields
- Select Fields - Use
.select() to limit returned fields
- Lean Queries - Use
.lean() for read-only operations (faster)
- Aggregation - Use aggregation pipeline for complex queries
- Transactions - Use sessions for multi-document transactions
- Connection Pooling - Mongoose handles this automatically
- Schema Validation - Use Mongoose validators for data integrity
Document Design Patterns
Embedded Documents (Denormalization)
profile: {
avatar: String,
bio: String,
}
Referenced Documents
const orderSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
});
const order = await OrderModel.findById(id).populate('user');
Hybrid Approach
const orderSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
userName: String,
userEmail: String,
});