At first, setting up an API Gateway is fairly straightforward. As your application grows, you have multiple services behind an API and you’re likely to need some sort of single domain to access all APIs. I have come across this scenario a few times. When working with microservices in a container environment, you might have a dedicated API Service with some intelligence (at least for authorization), a reverse proxy or load balancer. There are many solutions for this. With Serverless on AWS, the approach will be different.
AWS API Gateway
In an AWS-Serverless context, there is one go-to option - the AWS API Gateway. You could use an Application Load Balancer but this is typically for the case where you have non-serverless systems in the mix and want to serve all traffic through the same API endpoint and get load-balancing to containers or EC2 instances as part of the solution. This post is all about the API Gateway and how to set it up so that you can serve requests to multiple serverless “services”. A service is defined as an independently-deployable stack within your overall serverless application that deals with a single concern.
Let’s start with the simple case. For this example, I’ll use the scenario from SLIC Starter, the open-source serverless starter application we recently released on GitHub. The application in SLIC Starter is called SLIC Lists and it allows you to create and manage checklists. At first, we had a service called the Checklist Service. It has an API to create, read and edit checklists. It is implemented in AWS Lambda and backed by DynamoDB.
We are using the Serverless Framework to define and deploy this service. It deals with a lot of the boilerplate of creating all the CloudFormation resources that make up an API Gateway deployment. In the Serverless Framework configuration (serverless.yml), we define an http event for the Lambda function as follows.
Once deployed, the important output of this API Gateway is the API Gateway endpoint URL. It looks something like this:
The generated ID in this URL is the ID of the REST API resource for the API. For each application/stack you deploy with the Serverless Framework, this will be different. In order for API clients to invoke our API, we prefer to avoid a generated name like this. Using the generated URL means we would have to inject the URL into our front-end code somehow. If our API is redeployed, it has to be changed. I would rather address my API using a URL derived according to a known convention. This is why I always use a dedicated API domain, like api.sliclists.com to address the application’s API.
Using Custom API Domains
How do we set up our API Gateway to use a domain instead of the generated endpoint? This is achieved using an API Gateway Custom Domain Name. It can be created via CloudFormation (via SAM or Serverless Framework) and linked to the API Gateway via a Base Path Mapping. In the AWS Console, the Custom Domain Name configuration looks like this:
The configuration in CloudFormation (via serverless.yml) looks like this.
The custom domain is created and linked to an AWS Certificate Manager certificate which has been provisioned separately for the domain. The next resource, the BasePathMapping, links our custom domain to the API Gateway created by the serverless framework. This resource is always given the name ApiGatewayRestApi. This configuration ensures that all requests ( denoted by the ‘/’ path) are mapped to the single API Gateway REST API for our service.
The last part of this single API setup is to create DNS entries for our domain that alias the API Gateway HTTP servers. We do this by creating A records (also AAAA for IPv6) that alias our API domain to the distribution domain name for the custom domain. The value of this distribution domain name will be something like a1bcd9xyzovgag.cloudfront.net and is an output of the Custom Domain Name resource. We use Route53 for our DNS entries so all of this can be represented in a single CloudFormation stack.
When API requests are made to https://api.sliclists.com, they then follow this sequence:
Moving to Multiple Services
We have outlined the configuration for one service. How does this expand to support multiple services as part of an overall application? For our SLIC Lists application, we dealt with this when we added a “sharing service” to handle requests to share lists with new users. This was deemed to be out of the scope of the checklists service.
The simple approach here is to add a second base path mapping. Instead of directing all requests to a single services, we have some routing:
The updated Custom Domain Name base path mapping looks like this:
This example shows the development environment. That’s why the stage is dev and a dedicated .dev subdomain is shown.
In our single-service case, everything could be provisioned within one service. When we want multiple APIs, this solution does not work or scale. Each service creates a separate API REST API in API Gateway but is required to share an API Custom Domain. For SLIC Starter, we separate our deployments into four separate packages to provide these two services. The four services are:
The sharing service and checklist service do not have to look up any reference to the custom domain name created in the API service. Thankfully, this link is established by providing the domain name in the base path mapping alone.
NOTE! There is an important caveat when doing this with the Serverless Framework. A known issue sometimes results in a dependency issue when creating the base path mapping. To work around the issue, a dummy “AWS::ApiGateway::Deployment” resource must be created.
This is unfortunate! The issue is here and the workaround can be seen in the SLIC Starter project here.
Putting it altogether, the picture of these four services and the resources they create looks like this:
You can see the full code for this in the SLIC Starter project. Take a look in particular at api-service/serverless.yml, checklist-service/serverless.yml and certs/serverless.yml. Special thanks to Paul Kevany for implementing this in SLIC Starter and for reviewing this post.
If you have any questions, feel free to create an issue on GitHub or reach me on Twitter - @eoins.
We just sent you an email. Please click the link in the email to confirm your subscription!