Skip to content

Commit 37e8114

Browse files
committed
chore: review and improve MongoDB connection handling for serverless runtimes
1 parent 0e4f492 commit 37e8114

2 files changed

Lines changed: 70 additions & 3 deletions

File tree

lib/mongodb.test.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,27 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
22
import mongoose from 'mongoose';
33
import dbConnect from './mongodb';
44

5+
const { mockMongooseConnection } = vi.hoisted(() => ({
6+
mockMongooseConnection: {
7+
readyState: 0,
8+
},
9+
}));
10+
511
vi.mock('mongoose', () => ({
612
default: {
713
connect: vi.fn(),
14+
disconnect: vi.fn(),
15+
connection: mockMongooseConnection,
816
},
917
}));
1018

19+
const setConnectedMongoose = (resolvedValue: typeof mongoose) => {
20+
vi.mocked(mongoose.connect).mockImplementation(async () => {
21+
mockMongooseConnection.readyState = 1;
22+
return resolvedValue;
23+
});
24+
};
25+
1126
describe('dbConnect', () => {
1227
beforeEach(() => {
1328
vi.clearAllMocks();
@@ -17,10 +32,15 @@ describe('dbConnect', () => {
1732
global.mongoose.conn = null;
1833
global.mongoose.promise = null;
1934
}
35+
36+
delete process.env.NEXT_RUNTIME;
37+
delete process.env.MONGODB_URI;
38+
mockMongooseConnection.readyState = 0;
2039
});
2140

2241
afterEach(() => {
2342
delete process.env.MONGODB_URI;
43+
delete process.env.NEXT_RUNTIME;
2444
});
2545

2646
it('throws an error if MONGODB_URI is not defined', async () => {
@@ -34,12 +54,16 @@ describe('dbConnect', () => {
3454
it('connects to mongoose and caches the connection', async () => {
3555
process.env.MONGODB_URI = 'mongodb://localhost:27017/test';
3656
const mockMongoose = { connection: 'mock' };
37-
vi.mocked(mongoose.connect).mockResolvedValue(mockMongoose as unknown as typeof mongoose);
57+
setConnectedMongoose(mockMongoose as unknown as typeof mongoose);
3858

3959
const conn1 = await dbConnect();
4060
expect(mongoose.connect).toHaveBeenCalledTimes(1);
4161
expect(mongoose.connect).toHaveBeenCalledWith('mongodb://localhost:27017/test', {
4262
bufferCommands: false,
63+
maxPoolSize: 10,
64+
minPoolSize: 0,
65+
maxIdleTimeMS: 30000,
66+
serverSelectionTimeoutMS: 5000,
4367
});
4468
expect(conn1).toBe(mockMongoose);
4569

@@ -64,12 +88,16 @@ describe('dbConnect', () => {
6488
process.env.MONGODB_URI = specificUri;
6589

6690
const mockMongoose = { connection: 'mock' };
67-
vi.mocked(mongoose.connect).mockResolvedValue(mockMongoose as unknown as typeof mongoose);
91+
setConnectedMongoose(mockMongoose as unknown as typeof mongoose);
6892

6993
await dbConnect();
7094

7195
expect(mongoose.connect).toHaveBeenCalledWith(specificUri, {
7296
bufferCommands: false,
97+
maxPoolSize: 10,
98+
minPoolSize: 0,
99+
maxIdleTimeMS: 30000,
100+
serverSelectionTimeoutMS: 5000,
73101
});
74102
});
75103

@@ -85,4 +113,29 @@ describe('dbConnect', () => {
85113
// The promise should be cleared so it can try again
86114
expect(global.mongoose.promise).toBeNull();
87115
});
116+
117+
it('throws when called from the Edge runtime', async () => {
118+
vi.stubEnv('NEXT_RUNTIME', 'edge');
119+
process.env.MONGODB_URI = 'mongodb://localhost:27017/test';
120+
121+
await expect(dbConnect()).rejects.toThrow(
122+
'MongoDB is not supported in the Edge runtime. Use the Node.js runtime.'
123+
);
124+
125+
expect(mongoose.connect).not.toHaveBeenCalled();
126+
});
127+
128+
it('clears a stale cached connection before reconnecting', async () => {
129+
process.env.MONGODB_URI = 'mongodb://localhost:27017/test';
130+
global.mongoose.conn = {} as typeof mongoose;
131+
132+
const mockMongoose = { connection: 'mock' };
133+
setConnectedMongoose(mockMongoose as unknown as typeof mongoose);
134+
135+
const conn = await dbConnect();
136+
137+
expect(mongoose.connect).toHaveBeenCalledTimes(1);
138+
expect(global.mongoose.conn).toBe(mockMongoose);
139+
expect(conn).toBe(mockMongoose);
140+
});
88141
});

lib/mongodb.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import mongoose from 'mongoose';
22

33
declare global {
4+
// Cached across hot reloads and repeated serverless invocations in the same process.
45
var mongoose: {
56
conn: typeof import('mongoose') | null;
67
promise: Promise<typeof import('mongoose')> | null;
@@ -14,10 +15,19 @@ if (!cached) {
1415
}
1516

1617
async function dbConnect() {
17-
if (cached.conn) {
18+
if (process.env.NEXT_RUNTIME === 'edge') {
19+
throw new Error('MongoDB is not supported in the Edge runtime. Use the Node.js runtime.');
20+
}
21+
22+
if (cached.conn && mongoose.connection.readyState === 1) {
1823
return cached.conn;
1924
}
2025

26+
if (cached.conn && mongoose.connection.readyState !== 1) {
27+
cached.conn = null;
28+
cached.promise = null;
29+
}
30+
2131
if (!cached.promise) {
2232
const MONGODB_URI = process.env.MONGODB_URI;
2333

@@ -27,6 +37,10 @@ async function dbConnect() {
2737

2838
const opts = {
2939
bufferCommands: false,
40+
maxPoolSize: 10,
41+
minPoolSize: 0,
42+
maxIdleTimeMS: 30000,
43+
serverSelectionTimeoutMS: 5000,
3044
};
3145

3246
cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {

0 commit comments

Comments
 (0)