Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

Join us at AWS re:Invent 2024! Learn how to use MongoDB for AI use cases.
MongoDB Developer
JavaScript
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Languageschevron-right
JavaScriptchevron-right

Integrate MongoDB into Vercel Functions for the Serverless Experience

Nic Raboy7 min read • Published Jun 28, 2022 • Updated Sep 09, 2024
Next.jsNode.jsJavaScript
Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Working with Functions as a Service (FaaS), often referred to as serverless, but you're stuck when it comes to trying to get your database working? Given the nature of these serverless functions, interacting with a database is a slightly different experience than if you were to create your own fully hosted back end.
Why is it a different experience, though?
Databases in general, not just MongoDB, can have a finite amount of concurrent connections. When you host your own web application, that web application is typically connecting to your database one time and for as long as that application is running, so is that same connection to the database. Functions offer a different experience, though. Instead of an always-available application, you are now working with an application that may or may not be available at request time to save resources. If you try to connect to your database in your function logic, you'll risk too many connections. If the function shuts down or hibernates or similar, you risk your database connection no longer being available.
In this tutorial, we're going to see how to use the MongoDB Node.js driver with Vercel functions, something quite common when developing Next.js applications.

The requirements

There are a few requirements that should be met prior to getting started with this tutorial, depending on how far you want to take it.
  • You must have a MongoDB Atlas cluster deployed, free tier (M0) or better.
  • You should have a Vercel account if you want to deploy to production.
  • A recent version of Node.js and NPM must be available.
In this tutorial, we're not going to deploy to production. Everything we plan to do can be tested locally, but if you want to deploy, you'll need a Vercel account and either the CLI installed and configured or your Git host. Both are out of the scope of this tutorial.
While we'll get into the finer details of MongoDB Atlas later in this tutorial, you should already have a MongoDB Atlas account and a cluster deployed. If you need help with either, consider checking out this tutorial.
The big thing you'll need is Node.js. We'll be using it for developing our Next.js application and testing it.

Creating a new Next.js application with the CLI

Creating a new Next.js project is easy when working with the CLI. From a command line, assuming Node.js is installed, execute the following command:
1npx create-next-app@latest
You'll be prompted for some information which will result in your project being created. At any point in this tutorial, you can execute npm run dev to build and serve your application locally. You'll be able to test your Vercel functions too!
Before we move forward, let’s add the MongoDB Node.js driver dependency:
1yarn add mongodb
We won't explore it in this tutorial, but Vercel offers a starter template with the MongoDB Atlas integration already configured. If you'd like to learn more, check out the tutorial by Jesse Hall: How to Connect MongoDB Atlas to Vercel Using the New Integration. Instead, we'll look at doing things manually to get an idea of what's happening at each stage of the development cycle.

Configuring a database cluster in MongoDB Atlas

At this point, you should already have a MongoDB Atlas account with a project and cluster created. The free tier is fine for this tutorial.
Rather than using our imagination to come up with a new set of data for this example, we're going to make use of one of the sample databases available to MongoDB users.
Load a MongoDB Sample Dataset
Load a MongoDB Sample Dataset
From the MongoDB Atlas dashboard, click the ellipsis menu for one of your clusters and then choose to load the sample datasets. It may take a few minutes, so give it some time.
For this tutorial, we're going to make use of the sample_restaurants database, but in reality, it doesn't really matter as the focus of this tutorial is around setup and configuration rather than the actual data.
With the sample dataset loaded, go ahead and create a new user in the "Database Access" tab of the dashboard followed by adding your IP address to the "Network Access" rules. You'll need to do this in order to connect to MongoDB Atlas from your Next.js application. If you choose to deploy your application, you'll need to add a 0.0.0.0 rule as per the Vercel documentation.

Connect to MongoDB and cache connections for a performance optimized experience

Next.js is one of those technologies where there are a few ways to solve the problem. We could interact with MongoDB at build time, creating a 100% static generated website, but there are plenty of reasons why we might want to keep things adhoc in a serverless function.
Within your Next.js project, create a .env.local file with the following variables:
1NEXT_ATLAS_URI=YOUR_ATLAS_URI_HERE
2NEXT_ATLAS_DATABASE=sample_restaurants
3NEXT_ATLAS_COLLECTION=restaurants
Remember, we're using the sample_restaurants database in this example, but you can be adventurous and use whatever you'd like. Don't forget to swap the connection information in the .env.local file with your own.
Next, create a lib/mongodb.js file within your project. This is where we'll handle the actual connection steps. Populate the file with the following code:
1import { MongoClient } from "mongodb";
2
3const uri = process.env.NEXT_ATLAS_URI;
4const options = {
5 useUnifiedTopology: true,
6 useNewUrlParser: true,
7};
8
9let mongoClient = null;
10let database = null;
11
12if (!process.env.NEXT_ATLAS_URI) {
13 throw new Error('Please add your Mongo URI to .env.local')
14}
15
16export async function connectToDatabase() {
17 try {
18 if (mongoClient && database) {
19 return { mongoClient, database };
20 }
21 if (process.env.NODE_ENV === "development") {
22 if (!global._mongoClient) {
23 mongoClient = await (new MongoClient(uri, options)).connect();
24 global._mongoClient = mongoClient;
25 } else {
26 mongoClient = global._mongoClient;
27 }
28 } else {
29 mongoClient = await (new MongoClient(uri, options)).connect();
30 }
31 database = await mongoClient.db(process.env.NEXT_ATLAS_DATABASE);
32 return { mongoClient, database };
33 } catch (e) {
34 console.error(e);
35 }
36}
It might not look like much, but quite a bit of important things are happening in the above file, specific to Next.js and serverless functions. Specifically, take a look at the connectToDatabase function:
1export async function connectToDatabase() {
2 try {
3 if (mongoClient && database) {
4 return { mongoClient, database };
5 }
6 if (process.env.NODE_ENV === "development") {
7 if (!global._mongoClient) {
8 mongoClient = await (new MongoClient(uri, options)).connect();
9 global._mongoClient = mongoClient;
10 } else {
11 mongoClient = global._mongoClient;
12 }
13 } else {
14 mongoClient = await (new MongoClient(uri, options)).connect();
15 }
16 database = await mongoClient.db(process.env.NEXT_ATLAS_DATABASE);
17 return { mongoClient, database };
18 } catch (e) {
19 console.error(e);
20 }
21}
The goal of the above function is to give us a client connection to work with as well as a database. However, the finer details suggest that we need to only establish a new connection if one doesn't exist and to not spam our database with connections if we're in development mode for Next.js. The local development server behaves differently than what you'd get in production, hence the need to check.
Remember, connection quantities are finite, and we should only connect if we aren't already connected.
So what we're doing in the function is we're first checking to see if that connection exists. If it does, return it and let whatever is calling the function use that connection. If the connection doesn't exist and we're in development mode, we check to see if we have a cached session and use that if we do. Otherwise, we need to create a connection and either cache it for development mode or production.
If you understand anything from the above code, understand that we're just creating connections if connections don't already exist.

Querying MongoDB from a Vercel function in the Next.js application

We've done the difficult part already. We have a connection management system in place for MongoDB to be used throughout our Vercel application. The next part involves creating API endpoints, in a near identical way to Express Framework, and consuming them from within the Next.js front end.
So what does this look like exactly?
Within your project, create a pages/api/list.js file with the following JavaScript code:
1import { connectToDatabase } from "../../lib/mongodb";
2
3export default async function handler(request, response) {
4
5 const { database } = await connectToDatabase();
6 const collection = database.collection(process.env.NEXT_ATLAS_COLLECTION);
7
8 const results = await collection.find({})
9 .project({
10 "grades": 0,
11 "borough": 0,
12 "restaurant_id": 0
13 })
14 .limit(10).toArray();
15
16 response.status(200).json(results);
17
18}
Vercel functions exist within the pages/api directory. In this case, we're building a function with the goal of listing out data. Specifically, we're going to list out restaurant data.
In our code above, we are leveraging the connectToDatabase function from our connection management code. When we execute the function, we're getting a connection without worrying whether we need to create one or reuse one. The underlying function code handles that for us.
With a connection, we can find all documents within a collection. Not all the fields are important to us, so we're using a projection to exclude what we don't want. Rather than returning all documents from this large collection, we're limiting the results to just a few.
The results get returned to whatever code or external client is requesting it.
If we wanted to consume the endpoint from within the Next.js application, we might do something like the following in the pages/index.js file:
1import { useEffect, useState } from "react";
2import Head from 'next/head'
3import Image from 'next/image'
4import styles from '../styles/Home.module.css'
5
6export default function Home() {
7
8 const [restaurants, setRestaurants] = useState([]);
9
10 useEffect(() => {
11 (async () => {
12 const results = await fetch("/api/list").then(response => response.json());
13 setRestaurants(results);
14 })();
15 }, []);
16
17 return (
18 <div className={styles.container}>
19 <Head>
20 <title>Create Next App</title>
21 <meta name="description" content="Generated by create next app" />
22 <link rel="icon" href="/favicon.ico" />
23 </Head>
24
25 <main className={styles.main}>
26 <h1 className={styles.title}>
27 MongoDB with <a href="https://nextjs.org">Next.js!</a> Example
28 </h1>
29 <br />
30 <div className={styles.grid}>
31 {restaurants.map(restaurant => (
32 <div className={styles.card} key={restaurant._id}>
33 <h2>{restaurant.name}</h2>
34 <p>{restaurant.address.street}</p>
35 </div>
36 ))}
37 </div>
38 </main>
39 </div>
40 )
41}
Ignoring the boilerplate Next.js code, we added a useState and useEffect like the following:
1const [restaurants, setRestaurants] = useState([]);
2
3useEffect(() => {
4 (async () => {
5 const results = await fetch("/api/list").then(response => response.json());
6 setRestaurants(results);
7 })();
8}, []);
The above code will consume the API when the component loads. We can then render it in the following section:
1<div className={styles.grid}>
2 {restaurants.map(restaurant => (
3 <div className={styles.card} key={restaurant._id}>
4 <h2>{restaurant.name}</h2>
5 <p>{restaurant.address.street}</p>
6 </div>
7 ))}
8</div>
There isn't anything out of the ordinary happening in the process of consuming or rendering. The heavy lifting that was important was in the function itself as well as our connection management file.

Conclusion

You just saw how to use MongoDB Atlas with Vercel functions, which is a serverless solution that requires a different kind of approach. Remember, when dealing with serverless, the availability of your functions is up in the air. You don't want to spawn too many connections and you don't want to attempt to use connections that don't exist. We resolved this by caching our connections and using the cached connection if available. Otherwise, spin up a new connection.
Got a question or think you can improve upon this solution? Share it in the MongoDB Community Forums!

Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Tutorial

How to use MongoDB Client-Side Field Level Encryption (CSFLE) with Node.js


Sep 23, 2022 | 12 min read
Tutorial

Maintaining a Geolocation Specific Game Leaderboard with Phaser and MongoDB


Apr 02, 2024 | 18 min read
Article

Building Remix Applications with the MongoDB Stack


Apr 02, 2024 | 4 min read
Quickstart

How to Use MongoDB Transactions in Node.js


Aug 24, 2023 | 10 min read
Table of Contents