The “API Gateway-first approach” is the idea that, in the context of an AWS cloud stack, when we build a new service, we first consider using API Gateway. If it doesn’t fit our use case, we can consider something else, such as a load balancer in front of a Node.js server running on Fargate.
API gateway is a powerful service and by far my favorite of the AWS ecosystem. It is the glue that connects a web request to another AWS service—like Lambda or DynamoDB—which gives us the ability to take granular design decisions for each endpoint of our service. It helps us define strict boundaries in our code to leave the orchestration code out of our business logic code—handled in a Lambda, for example.
What I like about API Gateway:
- AWS fully manages it, so we don’t need to handle its availability, and we can set it up using CloudFormation (CDK).
- We can collocate different stacks under the same domain. For instance, we can run a React application (Fargate) alongside a REST API (Lambda + DynamoDB). That allows us to choose different solutions depending on the endpoint’s need. Therefore it will be easier to maintain the service over time because we can replace components separately. It is worth noting that API Gateway can proxy to different types of AWS services, so we don’t necessarily need to use a Lambda. A rule of thumbs is that if the Lambda is only responsible for passing data from one service to another, it’s a sign that we can use an API Gateway integration. For instance, we can directly map a request coming through API Gateway to DynamoDB using mapping templates.
- We can scale at the endpoint level instead of at the service level. However, like any serverless service, we can’t scale indefinitely—we need to consider the API Gateway request per second limit and concurrency burst limit.
- We can grant different permissions for each endpoint to reduce the blast radius if the service is compromised (following the least privilege principle).
- It’s the perfect candidate to apply the strangler pattern for migrating a legacy application to a new stack. When we start the migration, API Gateway will proxy all queries to the legacy service. Piece by piece, we can migrate each endpoint individually to point to a new integration.
- There is integrated support with CloudFront (for caching and running our service at the edge) and CloudWatch (to track errors and other metrics), so it is easier to set up, and we have less boilerplate code.
However, API Gateway is not a silver bullet and has some limitations. Remember that it is a “first approach,” not an “only approach.” Drawbacks of API Gateway:
- It connects different services with their limitation (API throttling, timeout, etc.), impacting performances or leading to failure. When designing a system, we also need to take them into account.
- Since each endpoint can be a different integration, it makes testing more challenging. For instance, if we use API Gateway + DynamoDB we cannot run it locally. We need to deploy a test stack against which we run our tests (end-to-end tests). It gets slow to run on a CI because deploying and destroying a stack takes several minutes. Some solutions, like LocalStack, can mock different AWS services locally, which will reduce the time to run the tests, but we lose the benefits of testing against the real environment.
- The possibility to use a mapping template to transform the data from API Gateway to another service is excellent in theory. Because it removes the need to build and monitor another Lambda, and we leave it to AWS to fully handle the transaction. However, the template language can be tricky to use and debug. Since we are writing a string, our editor has no support to help us figure out what data we’re handling. The only source of information is the documentation page. So, the learning curve can be steep, and depending on your needs, it might be simpler to use a Lambda.
Next time you start to build a new service before jumping on Node.js, Express, and Docker, think about it, can you use API Gateway instead?