# Getting Started With MongoDB Atlas Serverless, AWS CDK, and AWS Serverless Computing

Serverless development is a cloud computing execution model where cloud and SaaS providers dynamically manage the allocation and provisioning of servers on your behalf, dropping all the way to $0 cost when not in use. This approach allows developers to build and run applications and services without worrying about the underlying infrastructure, focusing primarily on writing code for their core product and associated business logic. Developers opt for serverless architectures to benefit from reduced operational overhead, cost efficiency through pay-per-use billing, and the ability to easily scale applications in response to real-time demand without manual intervention. 

MongoDB Atlas serverless instances eliminate the cognitive load of sizing infrastructure and allow you to get started with minimal configuration, so you can focus on building your app. Simply choose a cloud region and then start building with documents that map directly to objects in your code. Your serverless database will automatically scale with your app's growth, charging only for the resources utilized. Whether you’re just getting started or already have users all over the world, Atlas provides the capabilities to power today's most innovative applications while meeting the most demanding requirements for resilience, scale, and data privacy.

In this tutorial, we will walk you through getting started to build and deploy a simple serverless app that aggregates sales data stored in a MongoDB Atlas serverless instance using AWS Lambda as our compute engine and Amazon API Gateway as our fully managed service to create a RESTful API interface. Lastly, we will show you how easy this is using our recently published AWS CDK Level 3 constructs to better incorporate infrastructure as code (IaC) and DevOps best practices into your software development life cycle (SDLC). 

In this step-by-step guide, we will walk you through the entire process. We will be starting from an empty directory in an Ubuntu 20.04 LTS environment, but feel free to follow along in any supported OS that you prefer.

Let's get started!

## Setup

1. Create a MongoDB Atlas account. Already have an AWS account? Atlas supports paying for usage via the AWS Marketplace (AWS MP) without any upfront commitment — simply sign up for MongoDB Atlas via the AWS Marketplace.
2. Create a MongoDB Atlas programmatic API key (PAK)
3. Install and configure the AWS CLI and Atlas CLI in your terminal if you don’t have them already. 
4. Install the latest versions of Node.js and npm. 
5. Lastly, for the playground code running on Lambda function, we will be using Python so will also require Python3 and pip installed on your terminal. 

## Step 1: install AWS CDK, Bootstrap, and Initialize 

The AWS CDK is an open-source framework that lets you define and provision cloud infrastructure using code via AWS CloudFormation. It offers preconfigured components for easy cloud application development without the need for expertise. For more details, see the AWS CDK Getting Started guide. 

You can install CDK using npm:

```
sudo npm install -g aws-cdk
```

Next, we need to “bootstrap” our AWS environment to create the necessary resources to manage the CDK apps (see AWS docs for full details). Bootstrapping is the process of preparing an environment for deployment. Bootstrapping is a one-time action that you must perform for every environment that you deploy resources into.

The `cdk bootstrap` command creates an Amazon S3 bucket for storing files, AWS IAM roles, and a CloudFormation stack to manage these scaffolding resources: 

```
cdk bootstrap aws://ACCOUNT_NUMBER/REGION
```

Now, we can initialize a new CDK app using TypeScript. This is done using the cdk init command:

```
cdk init -l typescript 
```

This command initializes a new CDK app in TypeScript language. It creates a new directory with the necessary files and directories for a CDK app. When you initialize a new AWS CDK app, the CDK CLI sets up a project structure that organizes your application's code into a conventional layout. This layout includes bin and lib directories, among others, each serving a specific purpose in the context of a CDK app. Here's what each of these directories is for:

- The **bin directory** contains the entry point of your CDK application. It's where you define which stacks from your application should be synthesized and deployed. Typically, this directory will have a .ts file (with the same name as your project or another meaningful name you choose) that imports stacks from the lib directory and initializes them.

    The bin directory's script is the starting point that the CDK CLI executes to synthesize CloudFormation templates from your definitions. It acts as the orchestrator, telling the CDK which stacks to include in the synthesis process.

- The **lib directory** is where the core of your application's cloud infrastructure code lives. It's intended for defining CDK stacks and constructs, which are the building blocks of your AWS infrastructure. Typically, this directory will have a .ts file (with the same name as your project or another meaningful name you choose). 

    The lib directory contains the actual definitions of those stacks — what resources they include, how those resources are configured, and how they interact. You can define multiple stacks in the lib directory and selectively instantiate them in the bin directory as needed.

## Step 2: create and deploy the MongoDB Atlas Bootstrap Stack 

The `atlas-cdk-bootstrap` CDK construct was designed to facilitate the smooth configuration and setup of the MongoDB Atlas CDK framework. This construct simplifies the process of preparing your environment to run the Atlas CDK by automating essential configurations and resource provisioning.

Key features:

- User provisioning: The atlas-cdk-bootstrap construct creates a dedicated execution role within AWS Identity and Access Management (IAM) for executing CloudFormation Extension resources. This helps maintain security and isolation for Atlas CDK operations.

- Programmatic API key management: It sets up an AWS Secrets Manager to securely store and manage programmatic API Keys required for interacting with the Atlas services. This ensures sensitive credentials are protected and can be easily rotated.

- CloudFormation Extensions activation: This construct streamlines the activation of CloudFormation public extensions essential for the MongoDB Atlas CDK. It provides a seamless interface for users to specify the specific CloudFormation resources that need to be deployed and configured.

With `atlas-cdk-bootstrap`, you can accelerate the onboarding process for Atlas CDK and reduce the complexity of environment setup. By automating user provisioning, credential management, and resource activation, this CDK construct empowers developers to focus on building and deploying applications using the MongoDB Atlas CDK without getting bogged down by manual configuration tasks.

To use the atlas-cdk-bootstrap, we will first need a specific CDK package called `awscdk-resources-mongodbatlas` (see more details on this package on our 

Construct Hub page). Let's install it:

```
npm install awscdk-resources-mongodbatlas
```

To confirm that this package was installed correctly and to find its version number, see the package.json file. 

Next, in the .ts file in the **bin directory** (typically the same name as your project, i.e., `cloudshell-user.ts`), delete the entire contents and update with: 

```javascript
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AtlasBootstrapExample } from '../lib/cloudshell-user-stack'; //replace "cloudshell-user" with name of the .ts file in the lib directory

const app = new cdk.App();
const env = { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT };

new AtlasBootstrapExample(app, 'mongodb-atlas-bootstrap-stack', { env });
```

Next, in the .ts file in the **lib directory** (typically the same name as your project concatenated with “-stack”, i.e., `cloudshell-user-stack.ts`), delete the entire contents and update with: 

```javascript
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import {
  MongoAtlasBootstrap,
  MongoAtlasBootstrapProps,
  AtlasBasicResources
} from 'awscdk-resources-mongodbatlas'

export class AtlasBootstrapExample extends cdk.Stack {
  constructor (scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const roleName = 'MongoDB-Atlas-CDK-Excecution'
    const mongoDBProfile = 'development'   

    const bootstrapProperties: MongoAtlasBootstrapProps = {
      roleName,      secretProfile: mongoDBProfile,
      typesToActivate: 'ServerlessInstance', ...AtlasBasicResources]
    }

    new MongoAtlasBootstrap(this, 'mongodb-atlas-bootstrap', bootstrapProperties)
  }
}
```

Lastly, you can check and deploy the atlas-cdk-bootstrap CDK construct with: 

```
npx cdk diff mongodb-atlas-bootstrap-stack
npx cdk deploy mongodb-atlas-bootstrap-stack
```

## Step 3: store MongoDB Atlas PAK as env variables and update AWS Secrets Manager

Now that the atlas-cdk-bootstrap CDK construct has been provisioned, we then store our previously created [MongoDB Atlas programmatic API keys in AWS Secrets Manager. For more information on how to create MongoDB Atas PAK, refer to Step 2 from our prerequisites setup. 

This will allow the CloudFormation Extension execution role to provision key components including: MongoDB Atlas serverless instance, Atlas project, Atlas project IP access list, and database user. 

First, we must store these secrets as environment variables: 

```
export MONGO_ATLAS_PUBLIC_KEY=’INPUT_YOUR_PUBLIC_KEY'
export MONGO_ATLAS_PRIVATE_KEY=’INPUT_YOUR_PRIVATE_KEY'
```

Then, we can update AWS Secrets Manager with the following AWS CLI command: 

```
aws secretsmanager update-secret --secret-id cfn/atlas/profile/development --secret-string "{\"PublicKey\":\"${MONGO_ATLAS_PUBLIC_KEY}\",\"PrivateKey\":\"${MONGO_ATLAS_PRIVATE_KEY}\"}"
```

## Step 4: create and deploy the atlas-serverless-basic resource CDK L3 construct

The AWS CDK Level 3 (L3) constructs are high-level abstractions that encapsulate a set of related AWS resources and configuration logic into reusable components, allowing developers to define cloud infrastructure using familiar programming languages with less code. Developers use L3 constructs to streamline the process of setting up complex AWS and MongoDB Atlas services, ensuring best practices, reducing boilerplate code, and enhancing productivity through simplified syntax.

The MongoDB Atlas AWS CDK L3 construct for Atlas Serverless Basic provides developers with an easy and idiomatic way to deploy MongoDB Atlas serverless instances within AWS environments. Under the hood, this construct abstracts away the intricacies of configuring and deploying MongoDB Atlas serverless instances and related infrastructure on your behalf. 

Next, we then update our .ts file in the **bin directory** to: 

- Add the AtlasServerlessBasicStack to the import statement. 
- Add the Atlas Organization ID.
- Add the IP address of NAT gateway which we suggest to be the only IP address on your Atlas serverless instance access whitelist. 

```javascript
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AtlasBootstrapExample, AtlasServerlessBasicStack } from '../lib/cloudshell-user-stack'; //update "cloudshell-user" with your stack name 

const app = new cdk.App();
const env = { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT };

// the bootstrap stack
new AtlasBootstrapExample(app, 'mongodb-atlas-bootstrap-stack', { env });

type AccountConfig = {
  readonly orgId: string;
  readonly projectId?: string;
}

const MyAccount: AccountConfig = {
  orgId: '63234d3234ec0946eedcd7da', //update with your Atlas Org ID 
};

const MONGODB_PROFILE_NAME = 'development';

// the serverless stack with mongodb atlas serverless instance
const serverlessStack = new AtlasServerlessBasicStack(app, 'atlas-serverless-basic-stack', {
  env,
  ipAccessList: '46.137.146.59',  //input your static IP Address from NAT Gateway
  profile: MONGODB_PROFILE_NAME,
  ...MyAccount,
});
```

To leverage this, we can update our .ts file in the **lib directory** to:

- Update import blocks for newly used resources. 
- Activate underlying CloudFormation resources on the third-party CloudFormation registry. 
- Create a database username and password and store them in AWS Secrets Manager. 
- Update output blocks to display the Atlas serverless instance connection string and project name. 

```javascript
import * as path from 'path';
import {
  App, Stack, StackProps,
  Duration,
  CfnOutput,
  SecretValue,
  aws_secretsmanager as secretsmanager,
} from 'aws-cdk-lib';
import * as cdk from 'aws-cdk-lib';
import { SubnetType } from 'aws-cdk-lib/aws-ec2';
import {
  MongoAtlasBootstrap,
  MongoAtlasBootstrapProps,
  AtlasBasicResources,
  AtlasServerlessBasic,
  ServerlessInstanceProviderSettingsProviderName,
} from 'awscdk-resources-mongodbatlas';
import { Construct } from 'constructs';

export class AtlasBootstrapExample extends cdk.Stack {
  constructor (scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const roleName = 'MongoDB-Atlas-CDK-Excecution'
    const mongoDBProfile = 'development'   

    const bootstrapProperties: MongoAtlasBootstrapProps = {
      roleName: roleName,
      secretProfile: mongoDBProfile,
      typesToActivate: 'ServerlessInstance', ...AtlasBasicResources]
    }

    new MongoAtlasBootstrap(this, 'mongodb-atlascdk-bootstrap', bootstrapProperties)
  }
}

export interface AtlasServerlessBasicStackProps extends StackProps {
  readonly profile: string;
  readonly orgId: string;
  readonly ipAccessList: string;
}
export class AtlasServerlessBasicStack extends Stack {
  readonly dbUserSecret: secretsmanager.ISecret;
  readonly connectionString: string;
  constructor(scope: Construct, id: string, props: AtlasServerlessBasicStackProps) {
    super(scope, id, props);

    const stack = Stack.of(this);
    const projectName = `${stack.stackName}-proj`;

    const dbuserSecret = new secretsmanager.Secret(this, 'DatabaseUserSecret', {
      generateSecretString: {
        secretStringTemplate: JSON.stringify({ username: 'serverless-user' }),
        generateStringKey: 'password',
        excludeCharacters: '%+~`#$&*()|[]{}:;<>?!\'/@"\\=-.,',
      },
    });

    this.dbUserSecret = dbuserSecret;
    const ipAccessList = props.ipAccessList;

    // see https://github.com/mongodb/awscdk-resources-mongodbatlas/blob/main/examples/l3-resources/atlas-serverless-basic.ts#L22
    const basic = new AtlasServerlessBasic(this, 'serverless-basic', {
      serverlessProps: {
        profile: props.profile,
        providerSettings: {
          providerName: ServerlessInstanceProviderSettingsProviderName.SERVERLESS,
          regionName: 'EU_WEST_1',
        },
      },
      projectProps: {
        orgId: props.orgId,
        name: projectName,
      },
      dbUserProps: {
        username: 'serverless-user',
      },
      ipAccessListProps: {
        accessList: [
          { ipAddress: ipAccessList, comment: 'My first IP address' },
        ],
      },
      profile: props.profile,
    });

    this.connectionString = basic.mserverless.getAtt('ConnectionStrings.StandardSrv').toString();

    new CfnOutput(this, 'ProjectName', { value: projectName });
    new CfnOutput(this, 'ConnectionString', { value: this.connectionString });
  }
}
```

Lastly, you can check and deploy the atlas-serverless-basic CDK construct with: 

```
npx cdk diff atlas-serverless-basic-stack
npx cdk deploy atlas-serverless-basic-stack
```

Verify in the Atlas UI, as well as the AWS Management Console, that all underlying MongoDB Atlas resources have been created. Note the database username and password is stored as a new secret in AWS Secrets Manager (as specified in above AWS region of your choosing). 

## Step 5: copy the auto-generated database username and password created in AWS Secrets Manager secret into Atlas 

When we initially created the Atlas database user credentials, we created a random password, and we can’t simply copy that into AWS Secrets Manager because this would expose our database password in our CloudFormation template. 

To avoid this, we need to manually update the MongoDB Atlas database user password from the secret stored in AWS Secrets Manager so they will be in sync. The AWS Lambda function will then pick this password from AWS Secrets Manager to successfully authenticate to the Atlas serverless instance.

We can do this programmatically via the [Atlas CLI. To get started, we first need to make sure we have configured with the correct PAK that we created as part of our initial setup: 

```
atlas config init
```

We then input the correct PAK and select the correct project ID. For example: 

 for an AWS Lambda function that interacts with the MongoDB Atlas serverless instance via a public endpoint. It fetches database credentials from AWS Secrets Manager, constructs a MongoDB Atlas connection string using these credentials, and connects to the MongoDB Atlas serverless instance. 

The function then generates and inserts 20 sample sales records with random data into a sales collection within the database. It also aggregates sales data for the year 2023, counting the number of sales and summing the total sales amount by item. Finally, it prints the count of sales in 2023 and the aggregation results, returning this information as a JSON response.

Hence, we populate the Lambda/playground/index.py with: 

```python
from datetime import datetime, timedelta
from pymongo.mongo_client import MongoClient
from pymongo.server_api import ServerApi
import random, json, os, re, boto3

# Function to generate a random datetime between two dates
def random_date(start_date, end_date):
    time_delta = end_date - start_date
    random_days = random.randint(0, time_delta.days)
    return start_date + timedelta(days=random_days)

def get_private_endpoint_srv(mongodb_uri, username, password):
    """
    Get the private endpoint SRV address from the given MongoDB URI.
    e.g. `mongodb+srv://my-cluster.mzvjf.mongodb.net` will be converted to 
    `mongodb+srv://:@my-cluster-pl-0.mzvjf.mongodb.net/?retryWrites=true&w=majority`
    """
    match = re.match(r"mongodb\+srv://(.+)\.(.+).mongodb.net", mongodb_uri)
    if match:
        return "mongodb+srv://{}:{}@{}-pl-0.{}.mongodb.net/?retryWrites=true&w=majority".format(username, password, match.group(1), match.group(2))
    else:
        raise ValueError("Invalid MongoDB URI: {}".format(mongodb_uri))

def get_public_endpoint_srv(mongodb_uri, username, password):
    """
    Get the private endpoint SRV address from the given MongoDB URI.
    e.g. `mongodb+srv://my-cluster.mzvjf.mongodb.net` will be converted to 
    `mongodb+srv://:@my-cluster.mzvjf.mongodb.net/?retryWrites=true&w=majority`
    """
    match = re.match(r"mongodb\+srv://(.+)\.(.+).mongodb.net", mongodb_uri)
    if match:
        return "mongodb+srv://{}:{}@{}.{}.mongodb.net/?retryWrites=true&w=majority".format(username, password, match.group(1), match.group(2))
    else:
        raise ValueError("Invalid MongoDB URI: {}".format(mongodb_uri))

  client = boto3.client('secretsmanager')
  conn_string_srv = os.environ.get('CONN_STRING_STANDARD')
  secretId = os.environ.get('DB_USER_SECRET_ARN')
  json_secret = json.loads(client.get_secret_value(SecretId=secretId).get('SecretString'))
  username = json_secret.get('username')
  password = json_secret.get('password')

def handler(event, context):
#   conn_string_private = get_private_endpoint_srv(conn_string_srv, username, password)
  conn_string = get_public_endpoint_srv(conn_string_srv, username, password)
  print('conn_string=', conn_string)

  client = MongoClient(conn_string, server_api=ServerApi('1'))

  # Select the database to use.
  db = client'mongodbVSCodePlaygroundDB']

  # Create 20 sample entries with dates spread between 2021 and 2023.
  entries = []

  for _ in range(20):
      item = random.choice(['abc', 'jkl', 'xyz', 'def'])
      price = random.randint(5, 30)
      quantity = random.randint(1, 20)
      date = random_date(datetime(2021, 1, 1), datetime(2023, 12, 31))
      entries.append({
          'item': item,
          'price': price,
          'quantity': quantity,
          'date': date
      })

  # Insert a few documents into the sales collection.
  sales_collection = db['sales']
  sales_collection.insert_many(entries)

  # Run a find command to view items sold in 2023.
  sales_2023 = sales_collection.count_documents({
      'date': {
          '$gte': datetime(2023, 1, 1),
          '$lt': datetime(2024, 1, 1)
      }
  })

  # Print a message to the output window.
  print(f"{sales_2023} sales occurred in 2023.")

  pipeline = [
      # Find all of the sales that occurred in 2023.
      { '$match': { 'date': { '$gte': datetime(2023, 1, 1), '$lt': datetime(2024, 1, 1) } } },
      # Group the total sales for each product.
      { '$group': { '_id': '$item', 'totalSaleAmount': { '$sum': { '$multiply': [ '$price', '$quantity' ] } } } }
  ]

  cursor = sales_collection.aggregate(pipeline)
  results = list(cursor)
  print(results)
  response = {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json'
        },
        'body': json.dumps({
            'sales_2023': sales_2023,
            'results': results
        })
    }

  return response
```

Lastly, we need to create one last file that will store our requirements for the Python playground application with: 

```
touch lambda/playground/requirements.txt 
```

In this file, we populate with: 

```
pymongo
requests
boto3
testresources
urllib3==1.26
```

To then install these dependencies used in requirements.txt: 

```
cd lambda/playground 
pip install -r requirements.txt -t .
```

This installs all required Python packages in the playground directory and AWS CDK would bundle into a zip file which we can see from AWS Lambda console after deployment.

## Step 7: create suggested AWS networking infrastructure

AWS Lambda functions placed in public subnets do not automatically have internet access because Lambda functions do not have public IP addresses, and a public subnet routes traffic through an internet gateway (IGW). To access the internet, a Lambda function can be associated with a private subnet with a route to a NAT gateway.

First, ensure that you have NAT gateway created in your public subnet. Then, create a route from a private subnet (where your AWS Lambda resource will live) to the NAT gateway and route the public subnet to IGW. The benefits of this networking approach is that we can associate a static IP to our NAT gateway so this will be our one and only Atlas project IP access list entry. This means that all traffic is still going to the public internet through the NAT gateway and is TLS encrypted. The whitelist only allows the NAT gateway static public IP and nothing else. 

Alternatively, you can choose to build with [AWS PrivateLink which does carry additional costs but will dramatically simplify networking management by directly connecting AWS Lambda to a MongoDB Atlas severless instance without the need to maintain subnets, IGWs, or NAT gateways. Also, AWS PrivateLink creates a private connection to AWS services, reducing the risk of exposing data to the public internet. 

Select whichever networking approach best suits your organization’s needs. 

 and walkthrough on a recent episode of MongoDB TV Cloud Connect (aired 15 Feb 2024). Also, see the GitHub repo with the full open-source code of materials used in this demo serverless application. 

The MongoDB Atlas CDK resources are open-sourced under the Apache-2.0 license and we welcome community contributions. To learn more, see our contributing guidelines.

Get started quickly by creating a MongoDB Atlas account through the AWS Marketplace and start building with MongoDB Atlas and the AWS CDK today! 

  [1]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/blt7e6c094f4e095c73/65e61b2572b3874d4222d572/1.png
  [2]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/bltd0fe3f8f42f0b0ef/65e61b4a51368b8d36844989/2.png
  [3]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/blt262d82355ecaefdd/65e61b6caca1713e9fa00cbb/3.png
  [4]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/blt6c9d68ba0093af02/65e61badffa94a03503d58ca/4.png
  [5]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/blt76d88352feb37251/65e61bcf0f1d3518c7ca6612/5.png
  [6]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/blt0c090ebd5066daf8/65e61beceef4e3c3891e7f5f/6.png
  [7]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/blte6c19ea4fdea4edf/65e61c10c7f05b2df68697fd/7.png
  [8]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/blt904c5fdc6ec9b544/65e61c3111cd1d29f1a1b696/8.png