AWS CDK – API Gateway Custom Domain

When creating the Simple Web Service in the CDK we got an output that was API Gateways unique random domain. This is not a useful domain for a production system as we often want to place the API’s behind a custom domain name.

This article will look at how to create the infrastructure needed with Infrastructure as Code and the AWS CDK to hook up an API Gateway with a custom domain name. The Article assumes your domain is already registered on AWS with a Hosted Zone.

The example API will be a simple Lambda function that returns Hello World to the caller.

First to create the API, this will be a simpler version of The Simple Web Service and just has our Lamnda and the API Gateway hooked together under /v1/hello as a GET method.

    // Node Lambda Function
    const lambdaFn = new lambdaNode.NodejsFunction(this, 'LambdaFunctionHandler', {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: 'lambda/helloWorld.ts',
      handler: 'main',
      timeout: Duration.seconds(3),
      bundling: {
        externalModules: [
        ],
        nodeModules: [
        ],
        minify: true
      },
      environment: {
        region: process.env.CDK_DEFAULT_REGION!,
      }
    });

    // API Gateway
    const gateway = new apigw.RestApi(this, 'ApiGAteway');
    const version = gateway.root.addResource('v1');
    const api = version.addResource('hello');
    // enable cors
    api.addCorsPreflight({
      allowOrigins: apigw.Cors.ALL_ORIGINS
    });
    // Integration of lambda
    const lambdaIntegration = new apigw.LambdaIntegration(lambdaFn);
    // Add the Lambda as a GET method
    api.addMethod('GET', lambdaIntegration)

The Lambda Function code (lambda/helloWorld.ts) will also be simple, just returning a 200, Hello World message.

import { Context, Callback } from 'aws-lambda';
import {DynamoDB} from 'aws-sdk';

export async function main(event: any, _context: Context, callback: Callback) {
    return {
        statusCode: 200,
        headers: { "Content-Type": "application/json" },
        body: "Hello World"
    };
}

Now for the Custom Domain. First, we must add our domain name as it is in the hosted zone. We can also set the domain name part; this will give us the sub domain.

    // Add Custom Domain for API gateway
    const domainName = 'AddDomainName.com'; // MODIFY THIS WITH YOUR DOMAIN NAME! 
    const certDomainName = `*.${domainName}`; // Cirtificate will be generated for *.AddDomainName.com
    const domainNamePart = 'test'; // this will add a subdomain eg. test.AddDomainName.com

Once the custom domain parameters have been set we can get the hosted zone looked up by its domain name.

    // Get the zone by the domain name
    const zone = route53.HostedZone.fromLookup(this, 'HostedZone', { "domainName": domainName });

The zone is then used in creating the SSL certificate. We pass the domain name as the cert domain name not the base domain name as this will be a wild card domain name.

    // Create New Wild Card Certificate
    const certVal = new cert.DnsValidatedCertificate(this, 'Certificate', {
      hostedZone: zone,
      domainName: certDomainName,
      region: 'us-east-1',
    });

Now we have a certificate we can create the custom domain itself. The domain will be made up of the sub domain and base domain. We will also set the endpoint type to EDGE; this is optimised for geographically distributed clients. We also set the security policy to use TLS 1.2

    const customDomain = new apigw.DomainName(this, 'customDomain', {
      domainName: ( domainNamePart + '.' + domainName ),
      certificate: cert.Certificate.fromCertificateArn(this, 'ACM_Certificate', certVal.certificateArn),
      endpointType: apigw.EndpointType.EDGE,
      securityPolicy: apigw.SecurityPolicy.TLS_1_2
    });

Now we have created the domain we can add the base path mapping to connect the API gateway with the custom domain created.

    const basePathMap = new apigw.BasePathMapping(this, 'CustomBasePathMapping', {
      domainName: customDomain,
      restApi: gateway
    });

Finally, we must add the CName record to the hosted zone for this domain name part and the custom domain.

    const r53Record = new route53.CnameRecord(this, 'ApiGatewayRecordSet', {
      zone: zone,
      recordName: domainNamePart,
      domainName: customDomain.domainNameAliasDomainName
    });

Now we can build and deploy the stack, The api can easily be tested by going to the browser and hitting https://%5Bsubdomain%5D.%5B base domain]/v1/hello or by doing a GET request in a tool like postman.

Checkout the Stack on GitHub



Categories: AWS

Tags: , , , , , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: