Crafting the Ultimate Serverless Discord Slash Bot with AWS Lambda

Matt MartzMatt Martz
5 min read

Unleash the power of serverless architecture by building a Discord bot that elegantly processes slash commands using AWS Lambda and AWS CDK. We’ll tap into the power of constructs from @martzmakes/constructs to streamline our infrastructure management, ensuring your bot is scalable, cost-effective, and easy to maintain—all without requiring you to manage any servers.

The code for this blog post is located at: https://github.com/martzmakes/discord-lambda

The CDK Construct library I used for this project is located at: https://github.com/martzmakes/constructs

The Power of Serverless: Why AWS Lambda Is Your Best Bet

AWS Lambda presents a compelling case for bot creators:

  • Cost Efficiency: Pay only for computing time used—no need to maintain costly idle servers.

  • Scalability on Demand: Effortlessly manage traffic surges with Lambda’s auto-scaling capabilities.

  • Simplicity: Focus on building features while AWS manages the infrastructure.

Unlike an ECS-based bot that could rack up recurring costs due to constant server activity, Lambda charges only during actual code execution, saving you money when demand is low.

Getting Started: Setting Up Your Discord Bot

First, lay the groundwork for your Discord bot:

  1. Create a new app in the [Discord Developer Portal](https://discord.com/developers/applications).

  2. Build your bot under the Bot section and note down your bot token and application ID.

  3. Store these credentials securely in AWS Secrets Manager:

{
  "BOT_TOKEN": "<tokenhere>",
  "APPLICATION_ID": "<application id>",
  "DISCORD_PUBLIC_KEY": "<public key>"
}
  1. Add the secretArn to the project’s bin file.

  2. Deploy your CDK Project… continue these steps after the project is deployed, you may need to skip ahead.

  3. Add your interactions endpoint to your bot’s configuration

  4. Add the permissions you need

  5. Use the OAuth2 URL from the Developer Portal to add your bot to the server.

Understanding the CDK Structure

Your project begins with setting up the entry point (discord-lambda.ts):

#!/usr/bin/env node
import { App } from 'aws-cdk-lib';
import { DiscordLambdaStack } from '../lib/discord-lambda-stack';

const app = new App();
new DiscordLambdaStack(app, 'DiscordLambdaStack', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION,
  },
  envName: 'main',
  eventSource: 'discordLambda',
  discordSecretArn: 'put your discord secret arn here',
  domainName: 'martzmakes.com',
});

This initializes a DiscordLambdaStack for organizing and managing resources, leveraging constructs for seamless AWS integration.

Leveraging @martzmakes/constructs

Explore the potency of these constructs:

  • Discord Construct: Simplifies setup involving API Gateway and command registration.

  • Lambda Construct: Ensures optimal Lambda configuration with added flexibility.

The Build Process: From Stack to Lambda Setup

Dive deeper into resource configuration in the stack (discord-lambda-stack.ts):

import { MMStackProps } from "@martzmakes/constructs/cdk/interfaces/MMStackProps";
import { MMStack } from "@martzmakes/constructs/cdk/stacks/MMStack";
import { Discord } from "@martzmakes/constructs/cdk/constructs/discord";
import { Lambda } from "@martzmakes/constructs/cdk/constructs/lambda";
import { join } from "path";

export interface DiscordLambdaStackProps extends MMStackProps {
  discordSecretArn: string;
  domainName: string;
}

export class DiscordLambdaStack extends MMStack {
  constructor(scope: Construct, id: string, props: DiscordLambdaStackProps) {
    super(scope, id, props);

    const registerCommandLambda = new Lambda(this, 'register', {
      entry: join(__dirname, `./fns/register-command.ts`)
    });

    const interactionsLambda = new Lambda(this, 'interactions', {
      entry: join(__dirname, `./fns/interactions.ts`)
    });

    new Discord(this, 'Discord', {
      discordSecretArn: props.discordSecretArn,
      domainName: props.domainName,
      interactionsLambda,
      registerCommandLambda,
    });
  }
}

Within this stack, you set up Lambda functions to handle command registrations and Discord interactions. The Discord construct takes care of API Gateway integration and domain configuration.

Mastering Command Registration and Interactions

Command Registration Simplified

Commands are registered using the Discord API through a custom resource utilizing the bot’s credentials securely stored in Secrets Manager:

import { initHandler } from "@martzmakes/constructs/lambda/handlers/initHandler";
import { registerCommands } from "./utils/registerCommands";

const main = async (event: any) => {
  await registerCommands({});
  return {
    PhysicalResourceId: event.PhysicalResourceId,
  }
};

export const handler = initHandler({ handler: main });

Interaction Handling Magic

In the interactions.ts, manage how your bot will respond to commands:

import {
  DiscordInteractionHandler,
  initDiscordInteractionHandler,
} from "@martzmakes/constructs/lambda/handlers/initDiscordInteractionsHandler";
import { roll } from "./utils/roll";

const slashHandler: DiscordInteractionHandler<any, any> = async ({
  body,
}) => {
  switch (body.data.name) {
    case "roll":
      return {
        statusCode: 200,
        data: {
          type: 4,
          data: {
            content: await roll({ input: body.data.options[0].value }),
          },
        },
      };
    default:
      return {
        statusCode: 200,
        data: {
          content: "Unknown command",
        },
      };
  }
};

export const handler = initDiscordInteractionHandler({
  slashHandler,
});

Discord interactions span several categories, but our focus here is on handling application commands.

Example Use Cases

Want your bot to stand out? Here are some ideas:

  • Gaming Assistance: Command your bot to manage scores or deliver game statistics.

  • Community Manager: Automate server moderation tasks with slash commands. Kick off processes to archive a channel's contents or perform LLM analysis.

  • Content Curation: Use advanced AI to summarize key discussion topics and insights from the server.

Dungeons and Dragons Bot Idea

Imagine a bot dedicated to Dungeons and Dragons that can help settle arguments between players (or players and the DM)… an impartial Rule Judge, if you will. I created this using event-driven architecture and langchain to resolve game queries fairly and efficiently. If you’d like to learn more about RuleJudge, let me know.

Limitations

While this approach is powerful, it has its boundaries:

  • Limited Interaction: Bots can't be directly DM'd or mentioned with @; command use is via slash commands, buttons, and modals only.

  • Latency Possibilities: Cold starts and network delays can affect response times.

It's actually really disappointing that @ mentioning the bot isn't considered an interaction. By contrast, Slack's API they do consider @ mentions as an interaction.

Conclusion

By adopting this serverless approach, you open up a multitude of possibilities for your Discord bot. Imagine seamlessly handling various slash command requests, orchestrating server activities, or even integrating AI features—all while enjoying the benefits of reduced costs and easy scalability that AWS Lambda offers. Your bot could become a versatile tool for communities, provide insightful server analytics, or even automate routine tasks for server admins. The combination of serverless architecture and Discord's interaction model empowers you to build a bot that is not only functional but also transformative in its capabilities. Now, it's time for you to transform your Discord ideas into reality. With this guide, you have the blueprint to create a sophisticated, serverless bot ready to meet and exceed the needs of any community. Start experimenting, share your creations, and see how your bot can make a difference in the Discord ecosystem. Let's see what you build!

4
Subscribe to my newsletter

Read articles from Matt Martz directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Matt Martz
Matt Martz

I'm an AWS Community Builder and a Principal Software Architect I love all things CDK, Event Driven Architecture and Serverless