Select your cookie preferences

We use cookies and similar tools to enhance your experience, provide our services, deliver relevant advertising, and make improvements. Approved third parties also use these tools to help us deliver advertising and provide certain site features.

Amazon API Gateway HTTP API to AWS Lambda to RDS Proxy

Created with SnapVPCHTTP APIAWS LambdaRDS proxy instanceAmazon Aurora

Create an HTTP API endpoint that calls a Lambda function to RDS Proxy.

This pattern sets up API Gateway HTTP API using AWS Lambda function as an integration which talks to RDS Aurora(Postgres) via RDS Proxy with IAM Authentication enabled.
The Lambda function uses short-lived credentials to authenticate through RDS proxy via IAM authentication using the popular JavaScript ORM sequelize.

import * as path from 'path';

import { Stack, StackProps, CfnOutput, Duration, RemovalPolicy } from 'aws-cdk-lib';
import { aws_ec2 as ec2 } from 'aws-cdk-lib';
import { aws_secretsmanager as secretsmanager } from 'aws-cdk-lib';
import { aws_rds as rds } from 'aws-cdk-lib';
import { aws_lambda as lambda } from 'aws-cdk-lib';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Construct } from 'constructs';
import * as apigw from '@aws-cdk/aws-apigatewayv2-alpha';
import { HttpLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations-alpha';



export class RdsProxySequelizeStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const vpc = new ec2.Vpc(this, "RdsProxyExampleVpc", {
      subnetConfiguration: [
        {
          name: 'Isolated',
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
        {
          name: 'Private',
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
        },
        {
          name: 'Public',
          subnetType: ec2.SubnetType.PUBLIC,
        }
      ]
    });

    const dbUsername = 'syscdk';
    const rdsSecret = new secretsmanager.Secret(this, 'RdsProxyExampleSecret', {
      secretName: id+'-rds-credentials',
      generateSecretString: {
        secretStringTemplate: JSON.stringify({ username: dbUsername }),
        generateStringKey: 'password',
        excludePunctuation: true,
        includeSpace: false,
      }
    });

    // Create a security group to be used on the lambda functions
    const lambdaSecurityGroup = new ec2.SecurityGroup(this, 'Lambda Security Group', {
      vpc
    });

    // Create a security group to be used on the RDS proxy
    const rdsProxySecurityGroup = new ec2.SecurityGroup(this, 'Only Allow Access From Lambda', {
      vpc
    });
    rdsProxySecurityGroup.addIngressRule(lambdaSecurityGroup, ec2.Port.tcp(5432), 'allow lambda connection to rds proxy');

    // Create a security group to be used on the RDS instances
    const rdsSecurityGroup = new ec2.SecurityGroup(this, 'Only Allow Access From RDS Proxy', {
      vpc
    });
    rdsSecurityGroup.addIngressRule(rdsProxySecurityGroup, ec2.Port.tcp(5432), 'allow db connections from the rds proxy');
    
    const rdsCredentials = rds.Credentials.fromSecret(rdsSecret);

    const dbName = 'nflstadiums';
    const postgreSql = new rds.DatabaseCluster(this, 'RdsProxyExampleCluster', {
      instanceProps:
      {
        vpc,
        vpcSubnets: {
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED
        },
        instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM),
        securityGroups: [ rdsSecurityGroup ]
      },
      clusterIdentifier: 'RDSProxyExampleCluster',
      engine: rds.DatabaseClusterEngine.auroraPostgres({
        version: rds.AuroraPostgresEngineVersion.VER_15_2
      }),
      instances: 1,
      backup: { retention: Duration.days(1) },
      removalPolicy: RemovalPolicy.DESTROY,
      credentials: rdsCredentials,
      defaultDatabaseName: dbName,
    });

    const rdsProxy = postgreSql.addProxy('rdsProxyExample', {
      secrets: [ rdsSecret ],
      securityGroups: [ rdsProxySecurityGroup ],
      debugLogging: true,
      iamAuth: true,
      vpc
    });

    const rdsProxyPopulateLambda: NodejsFunction = new NodejsFunction(this, id + '-populateLambda', {
      memorySize: 1024,
      timeout: Duration.seconds(5),
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'handler',
      entry: path.join(__dirname, '../lambda/populate.ts'),
      vpc: vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      securityGroups: [ lambdaSecurityGroup ],
      environment: {
        PGHOST: rdsProxy.endpoint,
        PGDATABASE: dbName,
        PGUSER: dbUsername
      },
      // Bundler removes these dependencies since they aren't imported explicitly
      // Include them so sequelize can connect to Postgres
      bundling: {
        nodeModules: [ 'pg', 'pg-hstore' ]
      }
    });

    const rdsProxyGetDataLambda: NodejsFunction = new NodejsFunction(this, id + '-getDataLambda', {
      memorySize: 1024,
      timeout: Duration.seconds(5),
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'handler',
      entry: path.join(__dirname, '../lambda/getData.ts'),
      vpc: vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      securityGroups: [ lambdaSecurityGroup ],
      environment: {
        PGHOST: rdsProxy.endpoint,
        PGDATABASE: dbName,
        PGUSER: dbUsername
      },
      // Bundler removes these dependencies since they aren't imported explicitly
      // Include them so sequelize can connect to Postgres
      bundling: {
        nodeModules: [ 'pg', 'pg-hstore' ]
      }
    });

    rdsProxy.grantConnect(rdsProxyPopulateLambda, dbUsername);
    rdsProxy.grantConnect(rdsProxyGetDataLambda, dbUsername);

    const httpApi: apigw.HttpApi = new apigw.HttpApi(this, 'StadiumApi');

    const populateLambdaIntegration = new HttpLambdaIntegration('rdsProxyPopulateLambda', rdsProxyPopulateLambda );

    const getDataLambdaIntegration = new HttpLambdaIntegration('rdsProxyGetDataLambda', rdsProxyGetDataLambda);

    httpApi.addRoutes({
      path: '/populate',
      methods: [apigw.HttpMethod.POST],
      integration: populateLambdaIntegration
    });

    new CfnOutput(this, 'populateEndpointUrl', {
      value: `${httpApi.url}populate`,
      exportName: 'populateEndpointUrl'
    });

    httpApi.addRoutes({
      path: '/',
      methods: [apigw.HttpMethod.GET],
      integration: getDataLambdaIntegration
    });

    new CfnOutput(this, 'stadiumsEndpointUrl', {
      value: `${httpApi.url}`,
      exportName: 'stadiumsEndpointUrl'
    });
  };
}

< Back to all patterns


GitHub icon Visit the GitHub repo for this pattern.

Download

git clone https://github.com/aws-samples/serverless-patterns/ cd serverless-patterns/apigw-http-api-lambda-rds-proxy-cdk

Deploy

npm installcdk deploy


Testing

See testing in the README.md file in the repo

Cleanup

1. Delete the stack: npx cdk destroy.
2. Confirm the stack has been deleted: aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus".
3. You see a message confirming DELETE_COMPLETE.

Jesse Goeglein

Presented by Jesse Goeglein

Jesse is a Cloud Application Architect with AWS Professional Services helping customers succeed with AWS.