Querying MongoDB

Querying MongoDB Directly from the Server

A Server Component runs in Node.js 2,131 , so it can skip HTTP and talk to MongoDB 1,815 through the Mongoose 243,355 models of Mongoose; nothing in the query reaches the browser. What you must get right is the connection. next dev reloads modules on every edit, so a naive mongoose.connect() at module level opens a new pool per reload and eventually hits too many connections. Cache the connection promise on globalThis, which survives the reload, and guard the model registration — a second mongoose.model("Book", ...) throws OverwriteModelError.

A reload-safe connection and model (app/lib/db.js)JavaScript
import "server-only";
import mongoose from "mongoose";
import { cache } from "react";
const store = (globalThis._mongoose ??= { conn: null, promise: null });
export async function connect() {
  if (store.conn) return store.conn;
  store.promise ??= mongoose.connect(process.env.MONGODB_URI,
    { bufferCommands: false, maxPoolSize: 10 });
  return (store.conn = await store.promise);
}
export const Book = mongoose.models.Book ?? mongoose.model("Book", bookSchema);
export const booksByGenre = cache(async (genre) => {     // one query per request
  await connect();
  return Book.find({ genre }).sort({ year: 1 }).lean();
});

Two components on one page both call booksByGenre("sci-fi"). With mongoose.set("debug", true), Mongoose 9.10.1 shows what reached MongoDB 8.2.6 — one connection, one query:

Output of 32
[db] opening a new connection
Mongoose: books.find({ genre: 'sci-fi' }, { sort: { year: 1 } })
 GET /shelf 200 in 949ms (next.js: 287ms, application-code: 662ms)
Mongoose: books.find({ genre: 'sci-fi' }, { sort: { year: 1 } })   <- second request
 GET /shelf 200 in 42ms (next.js: 3ms, application-code: 39ms)

The second request never reopens the pool, and 949 ms becomes 42 ms. Without cache() the second component would have issued its own find: Mongoose queries are not memoized the way fetch is.

Three details matter as much as the connection. .lean() returns plain objects instead of Mongoose documents: faster, and also necessary, since a hydrated document is not serializable across the client boundary (Serializing Props) and _id is an ObjectId you must stringify yourself. bufferCommands: false makes a query fail fast when the database is unreachable instead of hanging until the request times out. And a Server Component has no idea who is asking, so every authorization rule your Express 24,430 routes enforce belongs here too.