AWS Lambda
MongoDB
Cloud Computing
Serverless Architecture
Database Connections

MongoDB connections from AWS Lambda

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Connecting AWS Lambda to MongoDB is straightforward in small demos, but production behavior is shaped by cold starts, connection pooling, and network setup. The main goal is simple: avoid opening a brand-new database connection on every invocation, because that adds latency and can exhaust database limits under load.

Why Connection Reuse Matters

Each Lambda invocation runs inside an execution environment that may be reused for later requests. That means objects created outside the handler can survive between invocations. For MongoDB, this is valuable because the driver can keep a connection pool warm instead of paying the cost of authentication, TLS negotiation, and server selection every time.

The pattern is:

  1. Create the MongoClient outside the handler.
  2. Cache the promise returned by connect().
  3. Reuse that client when the same Lambda container handles the next event.

This does not guarantee one global connection for every invocation across all containers. A burst of traffic can still create multiple Lambda environments, and each one may hold its own small pool. That is why pool sizing matters.

A Practical Node.js Pattern

The following example uses the official MongoDB driver in a Lambda function. It opens the client lazily and reuses it when the environment is warm.

javascript
1import { MongoClient } from "mongodb";
2
3const uri = process.env.MONGODB_URI;
4
5if (!uri) {
6  throw new Error("MONGODB_URI is required");
7}
8
9const client = new MongoClient(uri, {
10  maxPoolSize: 5,
11  minPoolSize: 0,
12  serverSelectionTimeoutMS: 5000,
13});
14
15let clientPromise;
16
17function getClient() {
18  if (!clientPromise) {
19    clientPromise = client.connect();
20  }
21
22  return clientPromise;
23}
24
25export const handler = async (event) => {
26  const mongo = await getClient();
27  const db = mongo.db("app");
28  const path = event.rawPath ?? "/";
29
30  const result = await db.collection("visits").findOneAndUpdate(
31    { path },
32    { $inc: { count: 1 } },
33    { upsert: true, returnDocument: "after" }
34  );
35
36  return {
37    statusCode: 200,
38    headers: { "content-type": "application/json" },
39    body: JSON.stringify({
40      path,
41      count: result.value?.count ?? 1,
42    }),
43  };
44};

This is usually better than calling await client.connect() inside the handler and then closing the client before returning. Repeated open-close cycles increase latency and create unnecessary pressure on MongoDB.

Network Layout and Timeouts

Connection code is only half of the story. Lambda must also be able to reach the database.

If MongoDB is hosted on Atlas with public access, the Lambda function needs outbound internet connectivity. If the function runs inside a VPC private subnet, that often means a NAT gateway or another approved route. If MongoDB is self-hosted inside a VPC, the Lambda security group, subnet routing, and database security group must allow the traffic.

Timeout settings also matter. A Lambda timeout of three seconds paired with slow DNS, TLS, or VPC startup can make transient failures look like database bugs. Reasonable driver settings such as serverSelectionTimeoutMS help the function fail quickly and log a useful error instead of hanging until the Lambda runtime kills it.

Pool Size, Concurrency, and Cost

A common mistake is assuming serverless means "no connection planning needed." In reality, Lambda concurrency multiplies connection usage. If 100 warm containers each keep a pool of 20 sockets, the database may see far more open connections than expected.

That is why small pools are usually the right default for Lambda workloads. Many handlers execute one or two database operations and return. They do not need a large pool inside each execution environment. Start with a conservative maxPoolSize, observe connection counts, and scale from measurements instead of guesswork.

For spiky workloads, provisioned concurrency can reduce cold starts, but it can also keep more execution environments alive, which may increase the number of idle MongoDB connections. The right tradeoff depends on whether latency or connection budget is the tighter constraint.

Common Pitfalls

Opening a new client in every handler call is the most common problem. It works in tests, then becomes slow and expensive under real concurrency.

Closing the client after every invocation is another mistake. In Lambda, you usually want reuse, not aggressive teardown.

Large pool sizes are also risky. A serverless function can scale horizontally very quickly, so a modest pool per container is safer than a large pool multiplied by many containers.

Networking issues are easy to misdiagnose. If the function sits in a VPC without proper routing, the MongoDB driver may appear to "hang" even though the real issue is connectivity. Check subnets, security groups, Atlas IP rules, and DNS resolution before changing application logic.

Finally, avoid storing secrets directly in code. Put the MongoDB URI in environment variables or a secrets manager and keep permissions narrow.

Summary

  • Reuse a MongoClient across warm Lambda invocations by defining it outside the handler.
  • Cache the connect() promise so concurrent calls in the same environment share setup work.
  • Keep maxPoolSize small because Lambda concurrency multiplies connection counts.
  • Treat VPC routing, security groups, and internet access as first-class parts of the design.
  • Use short, intentional timeout settings so failures surface quickly and predictably.

Course illustration
Course illustration

All Rights Reserved.