Body-based routing in AWS API Gateway using Proxy Lambda — with AWS SAM
In my previous article, we built a Discord bot using AWS Lambda. We clicked through everything in AWS Portal, and it was all good. We have set up an API Gateway and the “hello world” Lambda.
But I’ve hidden one little detail.
Making a serverless application means creating one lambda function for each duty — it’s called Single Responsibility Principle. In the article, we had just one responsibility. Take a look:
Now, imagine we add a second function to this file. And a third one. Cool. And these functions, probably, don’t simply reply with a string, but rather perform some rendering/calculations/whatever. And they become larger. Great.
Now we have implemented a main anti-pattern for Serverless — the Lambda Monolith.
We want to handle different Discord user interactions by delegating each interaction to different Lambda. The problem is… Discord sends all the events to one endpoint. API Gateway can natively route requests to Lambda’s based on request header and paths, but routing by request body requires using Template Mapping which is not easy, and it is limited.
We need something to proxy the request.
We will use Proxy API Gateway and Proxy Lambda to handle requests going to
/ path (Proxy part). Then, we will route it to SNS Topic with a special parameter that will further split events into Main Lambdas.
- Makes Proxy Lambda run only during routing.
Proxy Lambda is not being run when Main Lambda handles requests (as it would happen with nested Lambdas).
- Decouples Proxy and Main parts.
It helps in the development, testing, and migration from other Serverless applications (ie, not Discord).
- Makes Proxy Lambda an ideal place to put Discord request verification into.
- User sends
/helloon Discord Channel
- Discord App Server sends an event to your Proxy API Gateway URL (set in settings)
- Proxy API Gateway sends an event to Proxy Lambda
- Proxy Lambda verifies requests and responds to Discord App Server with a temporary response “Loading…” to be shown while the main function runs. Sets MessageParameter to invoke a command name for further handling.
- Simple Notification Service (SNS) receives an event. Handles event with appropriate Lambda, based on MessageParameter.
- (Single Responsibility) Lambda handles an event and sends a POST request with the content “Hello from Lambda!” back to Discord App Server’s webhook.
- Discord App Server edits response to
/helloon Discord Channel from “Loading…” to “Hello from Lambda!”.
We will build this app using Infrastructure as Code tool with AWS SAM and a template from a git repository. Once you will have this boilerplate cloned, it will be easy for you to go further and beyond with your own Lambda’s.
Never heard of AWS SAM? Find a quick recap in my other article.
1. Create Discord application
Go to Discord Developer Portal → New Application.
Bot → Add Bot.
Invite the bot to your guild (OAuth2 menu) with
Use Application Commandsand
Send Messages privileges.
2. Register commands
Get your development Guild (Discord Server) Id by enabling Developer Mode in settings and right-clicking your guild.
Get App Id and Bot Token from Discord Developers Portal.
Then, register commands on one guild for development (instant):
node register_commands/register.js --env dev --guild-id YOUR_GUILD_ID --bot-token YOUR_BOT_TOKEN --app-id YOUR_APPLICATION_ID
3. Clone the boilerplate
mkdir serverless-discord-bot &&
cd serverless-discord-bot &&
sam init --location gh:jakjus/serverless-discord-bot
4. Build and deploy the application on AWS
sam build && sam deploy --guided
Every answer can be a default one (just press enter), except:
- When prompted for
DiscordAppPublicKeypaste the key from Step 1.
- When prompted
[Some function] may not have authorization defined, Is this okay?, answer with
5. Set Discord Webhook URL
The previous command shows you:
------------------------------------------Outputs------------------------------------------Key ProxyGWEndpointDescription API Gateway endpoint URL to pass
to Discord Application PortalValue https://s0meur1.execute-api.zone
The output’s description says it all.
Copy the URL, and paste it back into Discord Developer Portal’s “Interaction Endpoint URL” field.
Click Save Changes.
/hello to your brand new bot.
The bot will reply with the following:
The power of Infrastructure as a Code, right?
Make the function do something else
- Change the code in
sam build && sam deploy
If you saved input values during
--guidedrun, you can then run it without this flag to reuse values.
Add your own function
- Add a command in
register_commands/commands.yamland register it again.
- Create a handler for new function in
- Add a reference to new handler in
All of these resources are in the free tier. It’s still nice to not forget to clean your resources with:
after you’re done.
In this article you have:
- learned how to use templates in AWS SAM
- comprehended specified serverless architecture and its reasoning
- a full-stack serverless application on AWS that listen for events forever
- and ever
- or until your credit card expires
The setup that we have performed is pretty tough to figure out at the start. That’s why AWS SAM and IaC practices make it so only one poor person has to go through all of it. The person was me in that case.
Here’s the boilerplate repository. Contributions welcome.
I’ve researched a lot for this article, but the most important question still remains.
What will you build with it?