AWS API Gateway with a Custom Domain Using AWS CDK

Paul Allies
5 min readApr 18, 2024

--

(without Route 53)

In an earlier blog post, I walked you through this process using the AWS console. In this blog I’ll illustrate how we do so using CDK.

CDK, or the Cloud Development Kit, is a powerful tool that allows you to define and deploy AWS cloud infrastructure using programming languages like TypeScript, Python, and Java.

Since we’re not utilising Route 53, I recommend setting up an SSL certificate outside of the CDK stack. We’ll use it’s reference ARN later.

Requesting a Certificate in AWS Console:

  1. Sign in to the AWS Management Console and access your account.
  2. Open AWS Certificate Manager (ACM)
  3. Request a Certificate by clicking on the “Request” button.
  4. Enter Domain Name and click “Request”.
  5. Open Certificate from the list, and Store CNAME Details: Note the CNAME name and values provided.
  6. Adding a DNS Record:
  7. Log in to your domain registrar’s website.
  8. Go to the DNS settings or management section.
  9. Add a CNAME record: Use the AWS-provided name in the “Name” or “Host” field and the CNAME value in the “Value” field.
  10. Save the changes.

Awaiting Validation:

  1. Return to the AWS ACM console.
  2. Refresh the page to check the validation status of your certificate. It might take a while.
  3. Once validated and the certificate status changes to “Issued,” you are not ready to use the certificate.

Create a CDK Project for your API

$ mkdir my-aws-api
$ cd my-aws-api
$ cdk init app --language typescript

CDK Stack

A CDK Stack(represented here by the Stack Class) is a collection of AWS resources to be provisioned.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import {aws_certificatemanager as acm} from 'aws-cdk-lib';

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

const DOMAINName = 'api.example.com';
const SSL_CERTIFICATE_ARN = "arn:aws:acm:af-south-1:123456788000:certificate/12abcde1-1200-3400-5600-d12c12345678"
const API_STAGE_NAME = 'dev';

const certificate = acm.Certificate.fromCertificateArn(this, 'Certificate', SSL_CERTIFICATE_ARN);

}
}

In the first few lines of the constructor we will specify some constants and references that will be used later

API

The first thing we’ll do is add an API.

import {aws_apigateway as apigateway} from 'aws-cdk-lib';

...

const api = new apigateway.RestApi(this, 'MyApi', {
restApiName: 'My API',
deployOptions: {
stageName: API_STAGE_NAME,
}
});

Here we are creating a new REST API using AWS API Gateway. The restApiName is used so that we can identify it in the AWS Console. and the stageName is to specify the deployment stage of the API(e.g. dev, testing, staging, production)

API Custom Domain

We always want to link our API to a custom domain. This can help ensure consistency in how your API is accessed, even if you switch backend services.

const domain =  new apigateway.DomainName(this, 'CustomDomainName', {
domainName: DOMAINName,
certificate
});

new apigateway.BasePathMapping(this, 'ApiMapping', {
domainName: domain,
restApi: api,
})

Here we are setting up API Gateway custom domain name with the SSL certificate. We are then required to map the API we’ve created in the previous step to the custom domain name

Securing the API with an API Key

API keys are essential for managing and securing API access, They uniquely identify consumers of the API. While not foolproof, API keys contribute an additional layer of security. API keys also help with monitoring usage patterns and control rate limits

const plan = new apigateway.UsagePlan(this, 'MyUsagePlan', {
apiStages: [
{
api: api,
stage: api.deploymentStage,
},
],
});

const key = new apigateway.ApiKey(this, 'MyApiKey', {
description: 'API key'
});

plan.addApiKey(key);

Because API keys enable us to manage access and impose rate limits, we must first create a usage plan and then associate the key with this plan. Later, we will demonstrate how to select specific endpoints to secure.

API Functionality

Besides configuring the API, we can also define the specific functionality each API endpoint should offer by using AWS Lambda.


const myLambda = new lambda.Function(this, 'MyLambda', {
runtime: lambda.Runtime.NODEJS_LATEST,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
});


const integration = new apigateway.LambdaIntegration(myLambda);

api.root.addMethod('GET', integration);

api.root.addResource('hello').addMethod('GET', integration, {
apiKeyRequired: true
})

This code configures an AWS Lambda function and links it to an API Gateway endpoint. It creates two endpoints: one for GET requests to the root URL (`/`) and another for GET requests to `/hello`. The `/hello` endpoint is secured with an API key, ensuring controlled access.

Below is an example of a lambda function

// lambda/index.mjs

export const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify({
message: 'Hello from Lambda!'
}, null, 2),
};
};

Retrieving the Information

During the CDK deployment process, we can configure CDK to output certain values:


new cdk.CfnOutput(this, ‘Api Key ID’, {
value: key.keyId,
});

new cdk.CfnOutput(this, ‘API Gateway Domain Name’, {
value: domain.domainNameAliasDomainName,
});

This code will generate output values for the API Gateway’s CNAME associated with our custom domain and the API Key ID. Note that it outputs the API Key ID, not the actual key. To retrieve the full API key value from the command line, use the following AWS CLI command:

$ aws apigateway get-api-key — api-key <API_KEY_ID> — include-value

Replace <API_KEY_ID> with the key ID obtained from the CDK deployment output. Once retrieved it can be used in the header follows

$ curl -X GET "https://api.example.com/hello" -H "x-api-key:<API_KEY>"

Complete Code

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import {aws_lambda as lambda} from 'aws-cdk-lib';
import {aws_certificatemanager as acm} from 'aws-cdk-lib';
import {aws_apigateway as apigateway} from 'aws-cdk-lib';

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

const DOMAINName = 'api.example.com';
const SSL_CERTIFICATE_ARN = "arn:aws:acm:af-south-1:123456788000:certificate/12abcde1-1200-3400-5600-d12c12345678"
const API_STAGE_NAME = 'dev';

const certificate = acm.Certificate.fromCertificateArn(this, 'Certificate', SSL_CERTIFICATE_ARN);

const api = new apigateway.RestApi(this, 'MyApi', {
restApiName: 'My API',
deployOptions: {
stageName: API_STAGE_NAME,
}
});

const domain = new apigateway.DomainName(this, 'CustomDomainName', {
domainName: DOMAINName,
certificate
});

new apigateway.BasePathMapping(this, 'ApiMapping', {
domainName: domain,
restApi: api,
})

const plan = new apigateway.UsagePlan(this, 'MyUsagePlan', {
apiStages: [
{
api: api,
stage: api.deploymentStage,
},
],
});

const key = new apigateway.ApiKey(this, 'MyApiKey', {
description: 'API key'
});

plan.addApiKey(key);

const myLambda = new lambda.Function(this, 'MyLambda', {
runtime: lambda.Runtime.NODEJS_LATEST,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
});

const integration = new apigateway.LambdaIntegration(myLambda);

api.root.addMethod('GET', integration);

api.root.addResource('hello').addMethod('GET', integration, {
apiKeyRequired: true,
})


new cdk.CfnOutput(this, 'Api Key ID', {
value: key.keyId,
});

new cdk.CfnOutput(this, 'API Gateway Domain Name', {
value: domain.domainNameAliasDomainName,
});

}
}

Deploy

We can now deploy the stack

$ cdk deploy

Finally

Add a CNAME DNS record for the custom domain using the CDN domain name, follow these steps:

Updating DNS Settings at Your Domain Registrar:

  1. Sign in to your domain registrar’s website.
  2. Go to the DNS settings or management section.
  3. Add a DNS Record.
  • Choose Record Type: CNAME record:
  • Input your domain name (e.g., api.example.com).
  • Input the CNAME value provided by the AWS API Gateway output ( eg, d-1h1abc12ab.execute-api.af-south-1.amazonaws.com).
  • Save Changes

Once the DNS changes have propagated, your custom domain will be successfully mapped.

--

--