Setup Nextjs 14 with MongoDB and Mongoose

In this blog, we will learn how to set up a Next.js 14 project with MongoDB and Mongoose. Setting up Nextjs with MongoDB can be tricky. But I will show you how to do that.

Setup database

I will use MongoDB Atlas for this tutorial. You can use any other MongoDB service or install it locally.

  1. Go to MongoDB Atlas and create an account.

  2. Create a organization if you haven't created any and a project.

  3. Create a cluster and choose the free tier.

  4. Create a database user and add the IP address to the IP access list.

  5. Create a database and a collection. Let's call both of them posts Now we have a database and a collection.

  6. Get the connection string from the cluster and save it for later.

    • Go to overview button on sidebar. And then use connection method.
    • Choose correct driver(nodejs latest)
    • Copy the connection string

You can also checkout the video tutorial to get a better understanding.

Create a Next.js project

Use the command below to create a new Next.js project. Make sure to use app router.

1npx create-next-app nextjs-mongodb

Install dependencies

Install the mongoose package.

1npm install mongoose

Connect to database

Create a new .env.local file and add the connection string to it.

1MONGO_URI=your_connection_string
2
3# Example
4MONGO_URI=mongodb+srv://anjan:anjan@cluster0.est6lzg.mongodb.net/posts?retryWrites=true&w=majority

Replace <password> with database password. And add db name after mongodb.net/ and before ?. Check the example above.

Create a new file db.js and add the code below.

1// /src/app/lib/db.js
2
3import mongoose from 'mongoose'
4
5const MONGODB_URI = process.env.MONGO_URI
6
7if (!MONGODB_URI) {
8 throw new Error(
9 'Please define the MONGODB_URI environment variable inside .env.local',
10 )
11}
12
13let cached = global.mongoose
14
15if (!cached) {
16 cached = global.mongoose = { conn: null, promise: null }
17}
18
19async function dbConnect() {
20 if (cached.conn) {
21 return cached.conn
22 }
23 if (!cached.promise) {
24 const opts = {
25 bufferCommands: false,
26 }
27 cached.promise = mongoose.connect(MONGODB_URI, opts).then(mongoose => {
28 console.log('Db connected')
29 return mongoose
30 })
31 }
32 try {
33 cached.conn = await cached.promise
34 } catch (e) {
35 cached.promise = null
36 throw e
37 }
38
39 return cached.conn
40}
41
42export default dbConnect

Explanation:

  1. With this dbConnect function, we are connecting to the database and caching the connection.
  2. We are only connecting to the database once and reusing the connection. You don't want to connect to the database multiple times.

Where to use dbConnect function?

This is a tricky question. In basic Node.js projects, you can connect to the database in the entry file index.js and you don't call that again. But in Nextjs, there isn't any entry file. Because all the pages and route handlers are specific to the pages or API routes.

But now you can use the experimental feature called instrumentation. It will allow you to execute any startup script and this runs only once when the nextjs server starts.

Enable the instrumentation feature by adding the code below to the next.config.js file.

1module.exports = {
2 experimental: {
3 instrumentationHook: true,
4 },
5}

Create a new file instrumentation.js at the root of project or inside src directory if it exist. And add the code below.

1import connect from '@/lib/db'
2
3export async function register() {
4 await connect()
5}

You just need to export the register function and call the dbConnect function inside it.

If the connection is successful, you will see the message Db connected in the console even before you access any page or api route.

What if you don't want to use instrumentation?

In that case, you have to call the dbConnect function on every page or api route where you want to access the database.

Because if you try to access the page and you don't call the connection function, you will get an error. For example, you only call the function in the /home/page.js file but the first user visiting the /about/page.js file will get an error. Because the connection is not established yet.

Once you have a connection, you can create model to interact with the database.

Create a model

Create a new file post.js and add the code below.

1import mongoose from 'mongoose'
2
3const postSchema = new mongoose.Schema({
4 title: {
5 type: String,
6 required: true,
7 },
8 description: {
9 type: String,
10 required: true,
11 },
12})
13
14export default mongoose.models.Post || mongoose.model('Post', postSchema)

Explanation:

  1. We create a new schema with title and description fields.
  2. We export the model. If the model already exists, we use that. Otherwise, we create a new model.

Add new post

Let's create a form and a server action

1export default async function Home() {
2 return (
3 <form action={addPost}>
4 <div>
5 <label>Title</label>
6 <input name='title' type='text' />
7 </div>
8 <div>
9 <label>Description</label>
10 <textarea name='description' />
11 </div>
12 <button>Submit</button>
13 </form>
14 )
15}

Create a server action in a separate file.

1'use server'
2
3import Post from '@/models/Post'
4
5const addPost = async post => {
6 const title = post.get('title')
7 const description = post.get('description')
8
9 const newPost = new Post({ title, description })
10 return newPost.save()
11}
12
13export { addPost }

Explanation:

  1. We have a server action that will add a new post to the database.
  2. It will be called when the form is submitted.

Get all post

Let's create another function:

1const getPosts = async () => {
2 return Post.find()
3}

Render the posts.

1import { getPosts } from '@/actions/action'
2
3export default async function Home() {
4 const posts = await getPosts()
5
6 return (
7 <div>
8 {posts.map(post => (
9 <div key={post._id}>
10 <h1>{post.title}</h1>
11 <p>{post.description}</p>
12 </div>
13 }
14 <form action={addPost}>
15 <div>
16 <label>Title</label>
17 <input name='title' type='text' />
18 </div>
19 <div>
20 <label>Description</label>
21 <textarea name='description' />
22 </div>
23 <button>Submit</button>
24 </form>
25 </div>
26 )
27}

Now you should get the list of posts from the database.

That's it. You have successfully set up a Next.js 14 project with MongoDB and Mongoose.

Shameless Plug

I have made an Xbox landing page clone with React and Styled components. I hope you will enjoy it. Please consider like this video and subscribe to my channel.

That's it for this blog. I have tried to explain things simply. If you get stuck, you can ask me questions.

Contacts

Blogs you might want to read:

Videos might you might want to watch:

Next PostuseFormStatus basics tutorial | Everything you need to know