Aurora Serverless V2 with AWS CDK
Tags: cdk, aurora
Published: 2022-11-09 07:16:44

## Introduction

[Aurora Serverless](https://aws.amazon.com/rds/aurora/serverless/) is a fully managed, auto-scaling configuration for Amazon Aurora. It automatically starts up, shuts down, and scales capacity up or down based on your application’s needs. You can use Aurora Serverless to run your most demanding production workloads with less administrative effort than with other database options. Aurora Serverless is available in two versions: Aurora Serverless v1 and Aurora Serverless v2.

[Aurora Serverless v2](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.html) is a new version of Aurora Serverless that provides a new storage engine, Aurora Storage Engine (Aurora SE), and a new query engine, Aurora Query Engine (Aurora QE). Aurora Serverless v2 is a drop-in replacement for Aurora Serverless v1. You can use the same API calls and tools to manage your Aurora Serverless v2 clusters as you use for Aurora Serverless v1.

In this article, we will use the AWS CDK to create an Aurora Serverless v2 cluster with a database and a database user. We will also create a Lambda function that will connect to the database and query the data.

Now AWS CDK has support for Aurora Serverless v2. You can use the AWS CDK to create an Aurora Serverless v2 cluster with a database and a database user.

Let’s get started.

```typescript
// create a vpc
const vpc = new Vpc(this, 'VPC', {
  cidr: '10.0.0.0/16',
  subnetConfiguration: [{ name: 'egress', subnetType: SubnetType.PUBLIC }], // only one subnet is needed
  natGateways: 0, // disable NAT gateways
})

// create a security group for aurora db
const dbSecurityGroup = new SecurityGroup(this, 'DbSecurityGroup', {
  vpc: vpc, // use the vpc created above
  allowAllOutbound: true, // allow outbound traffic to anywhere
})

// allow inbound traffic from anywhere to the db
dbSecurityGroup.addIngressRule(
  Peer.anyIpv4(),
  Port.tcp(5432), // allow inbound traffic on port 5432 (postgres)
  'allow inbound traffic from anywhere to the db on port 5432'
)

// create a db cluster
// https://github.com/aws/aws-cdk/issues/20197#issuecomment-1117555047
const dbCluster = new rds.DatabaseCluster(this, 'DbCluster', {
  engine: rds.DatabaseClusterEngine.auroraPostgres({
    version: rds.AuroraPostgresEngineVersion.VER_13_6,
  }),
  instances: 1,
  instanceProps: {
    vpc: vpc,
    instanceType: new InstanceType('serverless'),
    autoMinorVersionUpgrade: true,
    publiclyAccessible: true,
    securityGroups: [dbSecurityGroup],
    vpcSubnets: vpc.selectSubnets({
      subnetType: SubnetType.PUBLIC, // use the public subnet created above for the db
    }),
  },
  port: 5432, // use port 5432 instead of 3306
})

// add capacity to the db cluster to enable scaling
Aspects.of(dbCluster).add({
  visit(node) {
    if (node instanceof CfnDBCluster) {
      node.serverlessV2ScalingConfiguration = {
        minCapacity: 0.5, // min capacity is 0.5 vCPU
        maxCapacity: 1, // max capacity is 1 vCPU (default)
      }
    }
  },
})
```

Let see what we have done here.

- We created a VPC with a public subnet. We will use this VPC to create the Aurora Serverless v2 cluster.
- We created a security group for the Aurora Serverless v2 cluster. We will use this security group to allow inbound traffic from anywhere to the Aurora Serverless v2 cluster.
- We created an Aurora Serverless v2 cluster with a single instance. We used the `serverless` instance type to create an Aurora Serverless v2 cluster. We also set public accessibility to `true` to allow the Lambda function to connect to the Aurora Serverless v2 cluster. We also set the port to `5432` to use the Postgres port.
- We added capacity to the Aurora Serverless v2 cluster to enable scaling. We set the `minCapacity` to `0.5` and `maxCapacity` to `1`. This will allow the Aurora Serverless v2 cluster to scale up to `1` vCPU and scale down to `0.5` vCPU.

### Create a lambda function to connect to the database and query the data

In this section, we will create a lambda function that will connect to the database and query the data. We will use the AWS CDK to create the lambda function.

```typescript
const fn = new NodejsFunction(this, 'Lambda', {
  entry: './lambda/index.ts',
  runtime: Runtime.NODEJS_16_X,
  handler: 'main',
  bundling: {
    externalModules: ['aws-sdk', 'pg-native'],
    minify: false,
  },
  environment: {
    databaseSecretArn: dbCluster.secret?.secretArn ?? '', // pass the secret arn to the lambda function
  },
})

// allow the lambda function to access credentials stored in AWS Secrets Manager
// the lambda function will be able to access the credentials for the default database in the db cluster
dbCluster.secret?.grantRead(fn)

const api = new LambdaRestApi(this, 'Api', {
  handler: fn,
})
```

Let see what we have done here.

- We created a lambda function with the AWS CDK. We used the `NodejsFunction` construct to create the lambda function. We used the `externalModules` option to exclude the `aws-sdk` and `pg-native` modules from the lambda function. We also used the `minify` option to disable minification.
- We passed the secret arn to the lambda function using the `environment` option. The lambda function will use the secret arn to access the credentials for the default database in the Aurora Serverless v2 cluster.
- We allowed the lambda function to access credentials stored in AWS Secrets Manager. The lambda function will be able to access the credentials for the default database in the Aurora Serverless v2 cluster.
- And finally, we created a REST API using the lambda function. We will use this REST API to test the lambda function.

You can read more about how to [Building Lambda functions with TypeScript in AWS CDK here](https://www.codewithyou.com/blog/writing-typescript-lambda-in-aws-cdk)

Below is the code for the lambda function.

```typescript
import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager'
import { APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda'
import { Client } from 'pg'

export async function main(event: APIGatewayEvent): Promise<APIGatewayProxyResult> {
  // get the secret from secrets manager.
  const client = new SecretsManagerClient({})
  const secret = await client.send(
    new GetSecretValueCommand({
      SecretId: process.env.databaseSecretArn,
    })
  )
  const secretValues = JSON.parse(secret.SecretString ?? '{}')

  console.log('secretValues', secretValues)

  // connect to the database
  const db = new Client({
    host: secretValues.host, // host is the endpoint of the db cluster
    port: secretValues.port, // port is 5432
    user: secretValues.username, // username is the same as the secret name
    password: secretValues.password, // this is the password for the default database in the db cluster
    database: secretValues.dbname ?? 'postgres', // use the default database if no database is specified
  })

  await db.connect()

  // execute a query
  const res = await db.query('SELECT NOW()')

  // disconnect from the database
  await db.end()

  return {
    body: JSON.stringify({
      message: `DB Response: ${res.rows[0].now}`,
    }),
    statusCode: 200,
    isBase64Encoded: false,
    headers: {
      'Content-Type': 'application/json',
    },
  }
}
```

Let see what we have done here.

- We created a `SecretsManagerClient` to get the secret from AWS Secrets Manager.
- We used the `SecretsManagerClient` to get the secret from AWS Secrets Manager. We used the `SecretId` option to specify the secret arn. We used the `GetSecretValueCommand` to get the secret from AWS Secrets Manager.
- We parsed the secret string to get the secret values. The secret string contains the credentials for the default database in the Aurora Serverless v2 cluster.
- We created a `Client` to connect to the database. We used the `host`, `port`, `user`, `password`, and `database` options to connect to the database.
- We executed a query to get the current time from the database.

That's it. We have created an Aurora Serverless v2 cluster with a single instance. We have also created a lambda function that will connect to the database and query the data. Now, we can deploy the stack to AWS.

### Deploy the stack to AWS

To deploy the stack to AWS, run the following command.

```bash
cdk deploy
```

After the stack is deployed, you can verify that the Aurora Serverless v2 cluster is created in the AWS console.

![Aurora Serverless v2 cluster](https://www.codewithyou.com/_next/image?url=%2Fstatic%2Fimages%2Faurora-serverless-v2-cluster%2Faurora-serverless-v2-cluster.png&w=1920&q=75)

Besides, you can test the lambda function by making a request to the REST API.

```bash
curl -X GET https://<api-id>.execute-api.<region>.amazonaws.com/prod
```

You should get a response similar to the one below.

```json
{
  "message": "DB Response: 2022-11-08T15:52:00.000Z"
}
```

### Clean up

Don't forget to delete the stack after you are done. This will prevent you from incurring any charges.

```bash
cdk destroy
```

## Conclusion

In this article, we created an Aurora Serverless v2 cluster with a single instance. We also created a lambda function that will connect to the database and query the data. We used the AWS CDK to create the Aurora Serverless v2 cluster and the lambda function. You can find the code for this article [here](https://github.com/buithaibinh/blog-tutorials/blob/main/aurora-serverless-v2/lib/aurora-serverless-v2-stack.ts)

That's it for this article. I hope you found it useful. If you have any questions or suggestions, please leave a comment below. You can also reach out to me on [Twitter](https://twitter.com/bvbinh) or [LinkedIn](https://www.linkedin.com/in/binhbv/).