Published: March 31 2023

Next.js 11 + MongoDB - Connect to Mongo database with Mongoose

Tutorial built with Next.js 11, Mongoose 7 and MongoDB

This post shows goes through the steps to connect a Next.js application to MongoDB using the Mongoose ODM (Object Data Modeling) library, and automatically create/update the MongoDB database from code using Mongoose.

We'll start with an example Next.js auth app from a tutorial I posted recently, it saves data in JSON files by default for testing, we'll update it to connect to MongoDB and auto generate the database and collections with Mongoose. For full details about the Next.js auth app see Next.js 11 - User Registration and Login Tutorial with Example App.


Tutorial Contents


Tools required for this tutorial

To follow the steps in this tutorial you'll need the following:


Download & Run the example Next.js App

Follow these steps to download and run the Next.js 11 Auth App on your local machine with the default "JSON files" database:

  1. Download or clone the Next.js project source code from https://github.com/cornflourblue/next-js-11-registration-login-example
  2. Install all required npm packages by running npm install or npm i from the command line in the project root folder (where the package.json is located).
  3. Start the app by running npm run dev from the command line in the project root folder, this will compile the app and start the Next.js server.
  4. Open to the app at the URL http://localhost:3000.

NOTE: You can also start the app directly with the Next.js CLI command npx next dev. For more info on the Next.js CLI see https://nextjs.org/docs/api-reference/cli.

Before running in production

Before running in production make sure you update the secret property in the Next.js config file, it is used to sign and verify JWT tokens for authentication, change it to a random string to ensure nobody else can generate a JWT with the same secret and gain unauthorized access to your API. A quick and easy way is join a couple of GUIDs together to make a long random string (e.g. from https://www.guidgenerator.com/).


Update Next.js App to connect to MongoDB


Install MongoDB database driver from npm

Run the following command from the project root folder to install the MongoDB Node.js driver from npm:

npm install mongodb


Install Mongoose ODM from npm

Run the following command from the project root folder to install the Mongoose Object Data Modeling library from npm:

npm install mongoose


Add connection string to Next.js server config variables

Open the next.config.js file and add the entry connectionString as a child of serverRuntimeConfig with the MongoDB connection string (e.g. "mongodb://localhost/next-js-registration-login-example"), the connection string should be in the format "mongodb://[HOST]/[DATABASE];".

When Mongoose connects, the [DATABASE] value will be the name of the database created in MongoDB.

The updated next.config.js file with the connection string should look something like this:

module.exports = {
    reactStrictMode: true,
    serverRuntimeConfig: {
        connectionString: "mongodb://localhost/next-js-registration-login-example",
        secret: 'THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING'
    },
    publicRuntimeConfig: {
        apiUrl: process.env.NODE_ENV === 'development'
            ? 'http://localhost:3000/api' // development api
            : 'http://localhost:3000/api' // production api
    }
}


Create MongoDB Data Context

Create a new file /helpers/api/db.js for our MongoDB data context, it will be used to connect to MongoDB using Mongoose and exports an object containing all of the database model objects in the application (currently only User). It provides an easy way to access any part of the database from a single point.

Copy and paste the below code into the data context file (/helpers/api/db.js):

import getConfig from 'next/config';
import mongoose from 'mongoose';

const { serverRuntimeConfig } = getConfig();
const Schema = mongoose.Schema;

mongoose.connect(process.env.MONGODB_URI || serverRuntimeConfig.connectionString);
mongoose.Promise = global.Promise;

export const db = {
    User: userModel()
};

// mongoose models with schema definitions

function userModel() {
    const schema = new Schema({
        username: { type: String, unique: true, required: true },
        hash: { type: String, required: true },
        firstName: { type: String, required: true },
        lastName: { type: String, required: true }
    }, {
        // add createdAt and updatedAt timestamps
        timestamps: true
    });

    schema.set('toJSON', {
        virtuals: true,
        versionKey: false,
        transform: function (doc, ret) {
            delete ret._id;
            delete ret.hash;
        }
    });

    return mongoose.models.User || mongoose.model('User', schema);
}


Update the helpers api index file

Open the file /helpers/api/index.js and add an export statement for the new MongoDB data context (export * from './db';).

The updated file should look something like this:

export * from './api-handler';
export * from './db';
export * from './error-handler';
export * from './jwt-middleware';
export * from './omit';
export * from './users-repo';


Update Users Repo and Next.js API Handlers


Update Users Repo to use MongoDB Data Context

Replace the contents of the Users Repo file (/helpers/api/users-repo.js) with the following code to access and save data in MongoDB using the data context created above.

import getConfig from 'next/config';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import { db } from 'helpers/api';

const { serverRuntimeConfig } = getConfig();
const User = db.User;

export const usersRepo = {
    authenticate,
    getAll,
    getById,
    create,
    update,
    delete: _delete
};

async function authenticate({ username, password }) {
    const user = await User.findOne({ username });
    
    if (!(user && bcrypt.compareSync(password, user.hash))) {
        throw 'Username or password is incorrect';
    }        
    
    // create a jwt token that is valid for 7 days
    const token = jwt.sign({ sub: user.id }, serverRuntimeConfig.secret, { expiresIn: '7d' });

    return {
        ...user.toJSON(),
        token
    };
}

async function getAll() {
    return await User.find();
}

async function getById(id) {
    return await User.findById(id);
}

async function create(params) {
    // validate
    if (await User.findOne({ username: params.username })) {
        throw 'Username "' + params.username + '" is already taken';
    }

    const user = new User(params);

    // hash password
    if (params.password) {
        user.hash = bcrypt.hashSync(params.password, 10);
    }

    // save user
    await user.save();
}

async function update(id, params) {
    const user = await User.findById(id);

    // validate
    if (!user) throw 'User not found';
    if (user.username !== params.username && await User.findOne({ username: params.username })) {
        throw 'Username "' + params.username + '" is already taken';
    }

    // hash password if it was entered
    if (params.password) {
        params.hash = bcrypt.hashSync(params.password, 10);
    }

    // copy params properties to user
    Object.assign(user, params);

    await user.save();
}

async function _delete(id) {
    await User.findByIdAndRemove(id);
}


Update Next.js API Handlers to be compatible with the updated Users Repo

There are four files in the /pages/api/users folder that handle API requests for users in the Next.js app. Update each them with the below code to be compatible with the updated users repo.

/pages/api/users/[id].js - Next.js API handler for GET, PUT and DELETE requests to the route /api/users/[id]:

import { apiHandler, usersRepo } from 'helpers/api';

export default apiHandler({
    get: getById,
    put: update,
    delete: _delete
});

async function getById(req, res) {
    const user = await usersRepo.getById(req.query.id);

    if (!user) throw 'User Not Found';

    return res.status(200).json(user);
}

async function update(req, res) {
    await usersRepo.update(req.query.id, req.body);
    return res.status(200).json({});
}

async function _delete(req, res) {
    await usersRepo.delete(req.query.id);
    return res.status(200).json({});
}


/pages/api/users/authenticate.js - Next.js API handler for POST requests to the route /api/users/authenticate:

import { apiHandler, usersRepo } from 'helpers/api';

export default apiHandler({
    post: authenticate
});

async function authenticate(req, res) {
    const user = await usersRepo.authenticate(req.body);
    return res.status(200).json(user);
}


/pages/api/users/index.js - Next.js API handler for GET requests to the route /api/users:

import { apiHandler, usersRepo } from 'helpers/api';

export default apiHandler({
    get: getAll
});

async function getAll(req, res) {
    const users = await usersRepo.getAll();
    return res.status(200).json(users);
}


/pages/api/users/register.js - Next.js API handler for POST requests to the route /api/users/register:

import { apiHandler, usersRepo } from 'helpers/api';

export default apiHandler({
    post: register
});

async function register(req, res) {
    await usersRepo.create(req.body);
    return res.status(200).json({});
}


Restart Next.js App

Stop and restart the Next.js app with the command npm run dev from the project root folder, you should see the message ready - started server on 0.0.0.0:3000, url: http://localhost:3000 and the app should now be connected to MongoDB.

 


Need Some NextJS Help?

Search fiverr for freelance NextJS developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by