My profile picture. My mom says I'm a handsome boy!
← All Posts

Deploying a Discord Bot as a Vercel Serverless Function

My last post talked about Discord slash commands and some of the benefits of the new HTTP API, but I didn't spend much time talking about hosting. Using HTTP instead of the Gateway for your bot unlocks some cool new options, like hosting it as a serverless function through a provider like Vercel!

Writing a Vercel Serverless Function Bot

We only need to create two files to get up and running - a package.json and an api/index.js page.

package.jsonjson
1{
2 "name": "my-bot",
3 "version": "1.0.0",
4 "dependencies": {
5 "discord-interactions": "^2.0.2",
6 "raw-body": "^2.4.1"
7 }
8}

The only two dependencies here are used to verify incoming Discord requests (an API requirement).

The serverless function looks pretty similar to the server I created with fastify. Vercel requires functions be put into an api directory - you can find additional documentation here.

api/index.jsjavascript
1const {
2 InteractionResponseType,
3 InteractionType,
4 verifyKey,
5} = require("discord-interactions");
6const getRawBody = require("raw-body");
7
8const INVITE_COMMAND = {
9 name: "Invite",
10 description: "Get an invite link to add the bot to your server",
11};
12
13const HI_COMMAND = {
14 name: "Hi",
15 description: "Say hello!",
16};
17
18const INVITE_URL = `https://discord.com/oauth2/authorize?client_id=${process.env.APPLICATION_ID}&scope=applications.commands`;
19
20/**
21 * Gotta see someone 'bout a trout
22 * @param {VercelRequest} request
23 * @param {VercelResponse} response
24 */
25module.exports = async (request, response) => {
26 // Only respond to POST requests
27 if (request.method === "POST") {
28 // Verify the request
29 const signature = request.headers["x-signature-ed25519"];
30 const timestamp = request.headers["x-signature-timestamp"];
31 const rawBody = await getRawBody(request);
32
33 const isValidRequest = verifyKey(
34 rawBody,
35 signature,
36 timestamp,
37 process.env.PUBLIC_KEY
38 );
39
40 if (!isValidRequest) {
41 console.error("Invalid Request");
42 return response.status(401).send({ error: "Bad request signature " });
43 }
44
45 // Handle the request
46 const message = request.body;
47
48 // Handle PINGs from Discord
49 if (message.type === InteractionType.PING) {
50 console.log("Handling Ping request");
51 response.send({
52 type: InteractionResponseType.PONG,
53 });
54 } else if (message.type === InteractionType.APPLICATION_COMMAND) {
55 // Handle our Slash Commands
56 switch (message.data.name.toLowerCase()) {
57 case SLAP_COMMAND.name.toLowerCase():
58 response.status(200).send({
59 type: 4,
60 data: {
61 content: "Hello!",
62 },
63 });
64 console.log("Slap Request");
65 break;
66 case INVITE_COMMAND.name.toLowerCase():
67 response.status(200).send({
68 type: 4,
69 data: {
70 content: INVITE_URL,
71 flags: 64,
72 },
73 });
74 console.log("Invite request");
75 break;
76 default:
77 console.error("Unknown Command");
78 response.status(400).send({ error: "Unknown Type" });
79 break;
80 }
81 } else {
82 console.error("Unknown Type");
83 response.status(400).send({ error: "Unknown Type" });
84 }
85 }
86};

Make sure you add your Application ID, Token, and Public Key as project environment variables or secrets (for this example, call them APPLICATION_ID, TOKEN, and PUBLIC_KEY).

And that's it! You can deploy this project to Vercel, get a project URL, and plug it straight into your Discord application (make sure you add /api to it). Using Vercel as a host for my new bots has worked wonderfully so far.

I have a Vercel-hosted bot you can test here or view on GitHub.