Using MongoDB in a Serverless app

Using MongoDB in a Serverless app

Posted on 30th December 2019

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.

shell
npm install mongoose
npm install @types/mongoose -D

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

src/database/index.ts

typescript
import { Connection, createConnection } from "mongoose";

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.

src/database/index.ts

typescript
let conn: Connection = null;

Next get the MongoDB connection URI.

src/database/index.ts

typescript
const uri: string = process.env.DB_PATH;

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.

src/database/index.ts

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

Finally the file should look like this.

src/database/index.ts

typescript
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;
};

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.

src/database/models/note.ts

typescript
import {
Document,
SchemaDefinition,
SchemaTypes,
Schema,
Connection,
Model,
} from "mongoose";

Then declare an interface for Note type.

src/database/models/note.ts

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

After that define the schema for the Note model.

src/database/models/note.ts

typescript
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);

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.

src/database/models/note.ts

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

Finally the file should look like this.

src/database/models/note.ts

typescript
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;

Using the Model

To create the connection just call the getConnection function.

typescript
const dbConn = await getConnection();

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

typescript
const Note: mongoose.Model<INote> = NoteModel(dbConn);

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

typescript
const note = await Note.findById(id).exec();

Wrapping Up

I hope you found this post useful. Please be sure to share if you did!

0 views

Sign up for Coil to support this site!