Using MongoDB in a Serverless app

Akhila Ariyachandra - Dec 31 '19 - - Dev Community

PS - This was originally posted in my blog. Check it out if you want learn more about React and JavaScript!

If you have been developing an API in Node setting up a connection to a MongoDB database is relatively straight forward. Check out my previous post if you need a refresher.

When considering serverless we can't think about a server running constantly which has a persistent connection to the database. Serverless apps run as functions which are destroyed at the end of the invocation. So we'll have to create a new connection (or reuse an old one) every time the serverless function is called.

Setting up the Database connection

Since we cannot use a persistent database connection, we'll have to create a function to create the connection when needed.

Start by installing mongoose.

npm install mongoose
npm install @types/mongoose -D
Enter fullscreen mode Exit fullscreen mode

Then let's start defining a function to create the database connection. First create index.ts in src/database and import the mongoose dependencies.

import { Connection, createConnection } from "mongoose";
Enter fullscreen mode Exit fullscreen mode

After that let's create a variable to cache the connection so that we can try to cut down on the number of connection we make to the database.

let conn: Connection = null;
Enter fullscreen mode Exit fullscreen mode

Next get the MongoDB connection URI.

const uri: string = process.env.DB_PATH;
Enter fullscreen mode Exit fullscreen mode

You can use packages like dotenv and dotenv-webpack to load environment variables from a .env file into process.env. If you use the Now CLI this is done automatically. Remember to not commit the .env file!

Then we'll declare and export the function to create the database connection.

export const getConnection = async (): Promise<Connection> => {
  if (conn == null) {
    conn = await createConnection(uri, {
      bufferCommands: false,
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useCreateIndex: true
    });
  }

  return conn;
};
Enter fullscreen mode Exit fullscreen mode

Finally the file should look like this.

// src/database/index.ts
import { Connection, createConnection } from "mongoose";

let conn: Connection = null;

const uri: string = process.env.DB_PATH;

export const getConnection = async (): Promise<Connection> => {
  if (conn == null) {
    conn = await createConnection(uri, {
      bufferCommands: false,
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useCreateIndex: true
    });
  }

  return conn;
};
Enter fullscreen mode Exit fullscreen mode

All we're doing in the function is checking if there is a cached connection. If there is a cached connection the function returns it. If there isn't one, a new connection is created, cached and returned.

Defining a Model

For this example, let's create a Note model.

First create the file note.ts in src/database/models and import the dependencies.

import {
  Document,
  SchemaDefinition,
  SchemaTypes,
  Schema,
  Connection,
  Model
} from "mongoose";
Enter fullscreen mode Exit fullscreen mode

Then declare an interface for Note type.

export interface INote extends Document {
  title: string;
  content: string;
  date: Date;
}
Enter fullscreen mode Exit fullscreen mode

After that define the schema for the Note model.

const schema: SchemaDefinition = {
  title: { type: SchemaTypes.String, required: true },
  content: { type: SchemaTypes.String, required: true },
  date: { type: SchemaTypes.Date, required: true }
};

const collectionName: string = "note";
const noteSchema: Schema = new Schema(schema);
Enter fullscreen mode Exit fullscreen mode

To define the model we need the database connection. Since the connection has to created for each serverless function invocation, we'll also need to create the model for each invocation as well. So just like we declared a function to create the database connection, we'll declare and export a function to create the model as well.

const Note = (conn: Connection): Model<INote> =>
  conn.model(collectionName, noteSchema);

export default Note;
Enter fullscreen mode Exit fullscreen mode

Finally the file should look like this.

// src/database/models/note.ts
import {
  Document,
  SchemaDefinition,
  SchemaTypes,
  Schema,
  Connection,
  Model
} from "mongoose";

export interface INote extends Document {
  title: string;
  content: string;
  date: Date;
}

const schema: SchemaDefinition = {
  title: { type: SchemaTypes.String, required: true },
  content: { type: SchemaTypes.String, required: true },
  date: { type: SchemaTypes.Date, required: true }
};

const collectionName: string = "note";
const noteSchema: Schema = new Schema(schema);

const Note = (conn: Connection): Model<INote> =>
  conn.model(collectionName, noteSchema);

export default Note;
Enter fullscreen mode Exit fullscreen mode

Using the Model

To create the connection just call the getConnection function.

const dbConn = await getConnection();
Enter fullscreen mode Exit fullscreen mode

Then to create the Note model just pass the connection to the function used to create the model.

const Note: mongoose.Model<INote> = NoteModel(dbConn);
Enter fullscreen mode Exit fullscreen mode

After that the model can be used as normal, for example to find a Note by its id.

const note = await Note.findById(id).exec();
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

I hope you found this post useful. Please be sure to share if you did! If you have any suggestions on how to improve the post please leave a comment below! 😊

. . . . . . . . . . . . . . . . . . . . .