| name | setup-mongodb |
| description | Configure MongoDB connection and test infrastructure. Use when adding MongoDB support to the project. Triggers on "setup mongodb", "add mongodb", "configure database", "mongodb connection". |
Setup MongoDB
Configures MongoDB connection with the native driver (not Mongoose), including graceful shutdown and test infrastructure.
Quick Reference
Files created:
src/config/mongodb.setup.ts- Connection managementtests/config/mongodb.global.ts- Test server lifecycletests/config/mongodb.setup.ts- Test mocks and helpers
When to use: Before creating MongoDB repository implementations
Prerequisites
- Project bootstrapped with base structure
- Node.js 18+ with pnpm
Instructions
Phase 1: Install Dependencies
Step 1: Install MongoDB Driver
pnpm add mongodb
Step 2: Install Test Dependencies
pnpm add -D mongodb-memory-server
Phase 2: Environment Configuration
Step 3: Add Environment Variables
Update src/env.ts:
const envSchema = z.object({
// ... existing variables ...
// MongoDB Configuration
MONGODB_URI: z.string().url().optional(),
MONGODB_DATABASE: z.string().default("my-app-db"),
});
const mappedEnv = {
// ... existing mappings ...
MONGODB_URI: process.env.MONGODB_URI,
MONGODB_DATABASE: process.env.MONGODB_DATABASE,
};
Update .env.example:
# MongoDB Configuration
MONGODB_URI=mongodb://localhost:27017
MONGODB_DATABASE=my-app-db
Update .env:
MONGODB_URI=mongodb://localhost:27017
MONGODB_DATABASE=my-app-db
Phase 3: Connection Setup
Step 4: Create Connection Manager
Create src/config/mongodb.setup.ts:
import { MongoClient, type Db } from "mongodb";
import { env } from "@/env";
let client: MongoClient | null = null;
let db: Db | null = null;
export async function connectToDatabase(): Promise<Db> {
if (db) {
return db;
}
const uri = env.MONGODB_URI;
if (!uri) {
throw new Error("MONGODB_URI environment variable is not set");
}
try {
client = new MongoClient(uri);
await client.connect();
db = client.db(env.MONGODB_DATABASE);
console.log(`Connected to MongoDB database: ${env.MONGODB_DATABASE}`);
return db;
} catch (error) {
console.error("Failed to connect to MongoDB:", error);
throw error;
}
}
export async function disconnectFromDatabase(): Promise<void> {
if (client) {
await client.close();
client = null;
db = null;
console.log("Disconnected from MongoDB");
}
}
export async function getDatabase(): Promise<Db> {
if (!db) {
return connectToDatabase();
}
return db;
}
// Graceful shutdown handlers
process.on("SIGINT", async () => {
await disconnectFromDatabase();
process.exit(0);
});
process.on("SIGTERM", async () => {
await disconnectFromDatabase();
process.exit(0);
});
Phase 4: Test Infrastructure
Step 5: Update Vitest Config
Update vitest.config.ts:
import { defineConfig } from "vitest/config";
import path from "path";
export default defineConfig({
test: {
include: ["tests/**/*.test.ts"],
globals: false,
environment: "node",
// MongoDB test configuration
globalSetup: ["./tests/config/mongodb.global.ts"],
setupFiles: ["./tests/config/mongodb.setup.ts"],
pool: "forks", // Prevents database conflicts between tests
coverage: {
provider: "v8",
reporter: ["text", "html"],
exclude: ["node_modules/", "tests/", "dist/"],
},
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
Step 6: Create Global Setup
Create tests/config/mongodb.global.ts:
import { MongoMemoryServer } from "mongodb-memory-server";
let mongoServer: MongoMemoryServer | null = null;
export async function setup() {
console.log("\nš Starting MongoDB Memory Server...");
mongoServer = await MongoMemoryServer.create({
instance: {
dbName: "test-db",
},
});
const uri = mongoServer.getUri();
process.env.MONGODB_URI = uri;
process.env.MONGODB_DATABASE = "test-db";
console.log(`ā
MongoDB Memory Server started at: ${uri}`);
}
export async function teardown() {
console.log("\nš Stopping MongoDB Memory Server...");
if (mongoServer) {
await mongoServer.stop();
mongoServer = null;
}
console.log("ā
MongoDB Memory Server stopped");
}
Step 7: Create Test Setup
Create tests/config/mongodb.setup.ts:
import { vi, beforeAll, afterAll } from "vitest";
import { MongoClient, type Db } from "mongodb";
// Store test database connection
let testClient: MongoClient | null = null;
let testDb: Db | null = null;
// Mock the mongodb.setup module
vi.mock("@/config/mongodb.setup", () => ({
getDatabase: vi.fn(async () => {
if (!testDb) {
const uri = process.env.MONGODB_URI;
if (!uri) {
throw new Error("MONGODB_URI not set by global setup");
}
testClient = new MongoClient(uri);
await testClient.connect();
testDb = testClient.db(process.env.MONGODB_DATABASE || "test-db");
}
return testDb;
}),
connectToDatabase: vi.fn(async () => {
const { getDatabase } = await import("@/config/mongodb.setup");
return getDatabase();
}),
disconnectFromDatabase: vi.fn(async () => {
if (testClient) {
await testClient.close();
testClient = null;
testDb = null;
}
}),
}));
// Global test helpers
declare global {
function setupTestDatabase(): Promise<{ db: Db; client: MongoClient }>;
function cleanupTestDatabase(client: MongoClient): Promise<void>;
}
globalThis.setupTestDatabase = async () => {
const uri = process.env.MONGODB_URI;
if (!uri) {
throw new Error("MONGODB_URI not set");
}
const client = new MongoClient(uri);
await client.connect();
const db = client.db(process.env.MONGODB_DATABASE || "test-db");
return { db, client };
};
globalThis.cleanupTestDatabase = async (client: MongoClient) => {
if (client) {
await client.close();
}
};
// Cleanup after all tests
afterAll(async () => {
if (testClient) {
await testClient.close();
testClient = null;
testDb = null;
}
});
Phase 5: Server Integration (Optional)
Step 8: Connect on Server Start
If you want to connect to MongoDB when the server starts, update src/server.ts:
import { serve } from "@hono/node-server";
import { app } from "./app";
import { env } from "./env";
import { connectToDatabase } from "./config/mongodb.setup";
const port = env.PORT;
async function startServer() {
// Connect to MongoDB if URI is configured
if (env.MONGODB_URI) {
try {
await connectToDatabase();
console.log("MongoDB connection established");
} catch (error) {
console.error("Failed to connect to MongoDB:", error);
// Decide: exit or continue with MockDB fallback
// process.exit(1);
}
}
console.log(`Server starting on port ${port}...`);
serve({
fetch: app.fetch,
port,
});
console.log(`Server running at http://localhost:${port}`);
}
startServer();
Phase 6: Verification
Step 9: Test MongoDB Setup
Create a simple test to verify the setup works:
Create tests/config/mongodb.connection.test.ts:
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import type { MongoClient, Db } from "mongodb";
describe("MongoDB Connection", () => {
let testClient: MongoClient;
let testDb: Db;
beforeAll(async () => {
const { db, client } = await setupTestDatabase();
testDb = db;
testClient = client;
});
afterAll(async () => {
await cleanupTestDatabase(testClient);
});
it("connects to test database", async () => {
expect(testDb).toBeDefined();
expect(testDb.databaseName).toBe("test-db");
});
it("can perform basic operations", async () => {
const collection = testDb.collection("test-collection");
// Insert
const insertResult = await collection.insertOne({ test: "data" });
expect(insertResult.insertedId).toBeDefined();
// Find
const doc = await collection.findOne({ test: "data" });
expect(doc).not.toBeNull();
expect(doc?.test).toBe("data");
// Cleanup
await collection.deleteMany({});
});
});
Run the test:
pnpm test -- tests/config/mongodb.connection.test.ts
Connection Patterns
Lazy Connection (Default)
Connection is established on first database access:
// In repository
private async getCollection() {
if (!this.collection) {
const db = await getDatabase(); // Connects if needed
this.collection = db.collection("entities");
}
return this.collection;
}
Eager Connection
Connect explicitly at startup:
// In server.ts
await connectToDatabase();
Connection Pooling
The MongoDB driver handles connection pooling automatically. Default pool size is 100 connections.
To customize:
const client = new MongoClient(uri, {
maxPoolSize: 50,
minPoolSize: 5,
});
Files Created Summary
src/
āāā config/
āāā mongodb.setup.ts # Connection manager
tests/
āāā config/
āāā mongodb.global.ts # Memory server lifecycle
āāā mongodb.setup.ts # Test mocks and helpers
āāā mongodb.connection.test.ts # Verification test
Troubleshooting
"MONGODB_URI not set" Error
- Check
.envfile has the variable - Verify
env.tsincludes the mapping - For tests, ensure
globalSetupruns before tests
Memory Server Fails to Start
- Check available memory (needs ~500MB)
- Try specifying a version:
MongoMemoryServer.create({ binary: { version: '6.0.0' } }) - On M1 Macs, ensure Rosetta is installed
Tests Hang
- Ensure
pool: "forks"is set in vitest.config.ts - Check for unclosed connections in afterAll hooks
- Verify global teardown runs
What NOT to Do
- Do NOT use Mongoose (use native driver for transparency)
- Do NOT connect in module scope (use async initialization)
- Do NOT forget graceful shutdown handlers
- Do NOT skip the
pool: "forks"setting for tests - Do NOT hardcode connection strings (use env variables)
See Also
create-mongodb-repository- Create MongoDB repository implementationtest-mongodb-repository- Test MongoDB repositoryadd-env-variable- Add more environment variables