Setting Up MongoDB with Mongoose and NextJS 13 (The Correct Way).

Aniruddh Dev Upadhyay
4 min readJun 3, 2023

NextJS 13 is a framework for full stack web development. It is basically React on steroids wit an attached express server for creating REST APIs. NextJS is managed by vercel and has now become a great one-stop solution for all web developers, while also setting up a complete paradigm for One Application, One Code. In this article, we’ll be using the latest version of NextJS, i.e. 13, and the recently released app router.

MongoDB is a document-oriented database that fall under the school of NoSQL Databases. It uses JSON-like representations for storing datafields an example mongoDB document looks like —

Here all the fields in our relation are specified as key-value pairs. Document oriented databases are easier to read and are more beginner friendly than traditional SQL databases.

Mongoose is a tool written in JavaScript that is built upon the original Mongo SDK for JS. It provides us a way to represent Mongoose Models as Objects and then query on them using different functions that are built-in to the Model objects. Mongoose is an easy and efficient way to connect a Javascript based applications (eg. NextJS) to MongoDB.

Connecting NextJs and MongoDB can seem very ttrivial with a basic setup defined below. But beware — THE FOLLOWING IMPLEMENTATION HAS A FAULT.

  1. Set up a folder structure

After we create a next app, we first define a connection function for our database in /src/utils/db.js:

// /src/utils/db.js
import mongoose from "mongoose";

const connect = async () => {
try {
await mongoose.connect(process.env.MONGO);
} catch (error) {
throw new Error("Connection failed!");
}
};

export default connect;

Then we create a basic Model that holds user_id and password credentials of a user. We also set {timestamps: true}, so that createdAt and updatedAt fields are maintained along with documents. To create /src/models/Users.js:

// /src/models/Users.js
import mongoose from "mongoose";
const Schema = mongoose.Schema;

const UserSchema = new Schema(
{
user_id: { type: String, required: true },
password: { type: String, required: true },
},
{ timestamps: true }
);

module.exports = mongoose.models.User || mongoose.model("User", UserSchema);

As the final step we simply create a route that uses these to fetch list of all users in /src/api/route.js.

import { NextResponse } from "next/server";
import { dbConnect } from "@/utils/db";
import Users from "@/models/Users";

export const GET = async (req, res) => {
try {
await dbConnect();
console.log("Connected to database");
const users = await Users.find({});
res.statusCode = 200;
return NextResponse.json(users);
} catch (error) {
console.log(error);
return NextResponse.error(error);
}
};

We can easily get the output using this implementations. But this has a very BIG fault. By using this approach, everytime we call the /api route using GET method, a new connection to the database will be initiated, because of the call to mongoose.connect() method everytime. This has 2 downsides —

  • Un-necessary connections to the databse will be made, which will not be closed.
  • API will have to wait for connection to be established before responding, which increases the API round-trip time.

To solve this, we optimise the connect function in utils/db.js

import mongoose from "mongoose";

const { MONGO_URL } = process.env;

if (!MONGO_URL) throw new Error("MONGO_URL is not defined.");

let cached = global.mongoose;

if (!cached) {
cached = global.mongoose = { conn: null };
}

export const dbConnect = async () => {
if (cached.conn) return cached.conn;

cached.conn = await mongoose.connect(MONGO_URL);

return cached.conn;
};

Here we cache the mongoose connection once the app is launched to the “global” variable. If there is a existing connection, the same is returned. This implies that a new connection is established only when no previous connections exist.

P.S. — Because objects are copied be reference in JS, any changes made to the cached variables also reflect in global.mongoose.

Using the new method, we significantly reduce API response times and make the application more efficient. Knowing this technique directly affect the entire application as this connect methods needs to be called in every route that needs to be connected to the database.

Hope you found this article interesting and NEXT-level. :)

--

--