Skip to main content

Creating A Standalone REST Process

The first thing we want to make is our standalone REST process. This process will be used by almost every other process, so it is going to be the foundation of the bot.

Before, we dive into how, here is a quick summary of why you will want a standalone REST process.

Why Use Standalone REST Process?

  • Easily host on any serverless infrastructure.
  • Freedom from global rate limit errors
    • As your bot grows, you want to handle global rate limits better. Shards don't communicate fast enough to truly handle it properly so this allows 1 rest handler across the entire bot.
    • In fact, you can host multiple instances of your bot and all connect to the same rest server.
  • REST does not rest!
    • Separate rest means if your bot for whatever reason crashes, your requests that are queued will still keep going and will not be lost.
    • Seamless updates! When you want to update and reboot the bot, you could potentially lose tons of messages or responses that are in queue. Using this you could restart your bot without ever worrying about losing any responses.
  • Single source of contact to Discord API
    • This will allow you to make requests to discord from anywhere including a bot dashboard. You no longer need to have to communicate to your bot processes just to make a request or anything. Free up your bot process for processing bot events.
  • Scalability! Scalability! Scalability!

Preparations

Before going further, you should have already made the following pieces:

  • rest/mod.ts
  • deps.ts (Make sure to import discordeno)
  • configs.ts
  • Deno extension(if you are using deno, this is required)
  • TabNine extension to make your life so much better. (Optional)

Creating Rest Manager

Now let's open up that rest file and start coding.

import { DISCORD_TOKEN, REST_AUTHORIZATION, REST_PORT } from '../../configs.ts'
import { BASE_URL, createRestManager } from '../../deps.ts'

const rest = createRestManager({
token: DISCORD_TOKEN,
secretKey: REST_AUTHORIZATION,
customUrl: `http://localhost:${REST_PORT}`,
})
  • createRestManager is imported from your deps file which should have exported everything from discordeno.
  • DISCORD_TOKEN is the bot's token itself.
  • REST_AUTHORIZATION is a special password you want to use to authenticate that requests being sent to your port are indeed from you.
  • customUrl the url where this rest process will be running. This can be localhost which we are using in this guide if you want all processes on same VPS or separate them to different servers for horizontal scaling. REST_PORT is just the port where you want the process hosted.

Now you have an entire Rest manager ready and waiting. Only thing you need now, is to listen for requests.

Creating HTTP Listener

Since this is not a beginner guide, I am assuming you know already how to create a HTTP listener. There are enough guides on this out there. I will only cover the rough functionality.

// START LISTENING TO THE URL(localhost)
const server = Deno.listen({ port: REST_PORT })
console.info(
`HTTP webserver running. Access it at: http://localhost:${REST_PORT}/`,
)

// Connections to the server will be yielded up as an async iterable.
for await (const conn of server) {
// In order to not be blocking, we need to handle each connection individually
// in its own async function.
handleRequest(conn)
}

async function handleRequest(conn: Deno.Conn) {
// This "upgrades" a network connection into an HTTP connection.
const httpConn = Deno.serveHttp(conn)
// Each request sent over the HTTP connection will be yielded as an async
// iterator from the HTTP connection.
for await (const requestEvent of httpConn) {
if (
!REST_AUTHORIZATION ||
REST_AUTHORIZATION !== requestEvent.request.headers.get('AUTHORIZATION')
) {
return requestEvent.respondWith(
new Response(JSON.stringify({ error: 'Invalid authorization key.' }), {
status: 401,
}),
)
}

const json = (await requestEvent.request.json()) as any

// IMPLEMENT ANY ERROR HANDLING HERE IF YOU WOULD LIKE BY WRAPPING THIS IN A CATCH

// MAKE THE REQUEST TO DISCORD
const result = await rest.runMethod(
rest,
// USE THE SAME METHOD THAT CAME IN. IF DELETE CAME IN WE SEND DELETE OUT
requestEvent.request.method as any,
// OVERWRITE THE CUSTOM URL WITH DISCORDS BASE URL
`${BASE_URL}/v${rest.version}${requestEvent.request.url.substring(
rest.customUrl.length,
)}`,
json,
)

// RETURN DISCORDS RESPONSE BACK TO THE PROCESS MAKING THE REQUEST
if (result) {
requestEvent.respondWith(
new Response(JSON.stringify(result), {
status: 200,
}),
)
} else {
requestEvent.respondWith(
new Response(undefined, {
status: 204,
}),
)
}
}
}