Building a Discord Bot in Node, TypeScript, and Eris

Building a Discord Bot in Node, TypeScript, and Eris
Photo by Gabriel Heinzer / Unsplash

Here's a simple and straightforward guide to creating your first Discord bot with Eris and TypeScript. This guide introduces you to the essentials of writing a modular Discord bot that supports slash commands.

Prerequisites

You'll need these few tools and skills in order to complete this project.

  • Node.js along with NPM
  • A text editor or IDE, I personally use VSCode although you can use what you prefer
  • A basic understanding of TypeScript/JavaScript concepts

Do you like what you're reading from the CoderOasis Technology Blog? We recommend reading our Implementing RSA in Python from Scratch series next.
Implementing RSA in Python from Scratch
Please note that it is essential for me to emphasize that the code and techniques presented here are intended solely for educational purposes and should never be employed in real-world applications without careful consideration and expert guidance. At the same time, understanding the principles of RSA cryptography and exploring various

The CoderOasis Community

Did you know we have a Community Forums and Discord Server? which we invite everyone to join us? Want to discuss this article with other members of our community? Want to join a laid back place to chill and discuss topics like programming, cybersecurity, web development, and Linux? Consider joining us today!
Join the CoderOasis.com Discord Server!
CoderOasis offers technology news articles about programming, security, web development, Linux, systems admin, and more. | 112 members
CoderOasis Forums
CoderOasis Community Forums where our members can have a place to discuss technology together and share resources with each other.

Creating the Project

First you'll want to setup your project directory:

discord-bot/
|__ bot.ts
|__ config.ts
|__ commands/

Once you have your project files created you'll need to install the required dependencies:

npm init -y
npm install typescript @types/node eris

Make sure you run these in the project directory!

Once you've got your dependencies installed you'll want to run tsc --init this will create a tsconfig.json file, the only setting you'll want to change is the outDir property to ourDir: "./out". Your tsconfig.json file will be full of information about the other settings, although those aren't important right now.

Once you've added the out directory property it should look similar to this:

{
  "compilerOptions": {
    "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
    "module": "commonjs" /* Specify what module code is generated. */,
    "outDir": "./out" /* Specify an output folder for all emitted files. */,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Setting up the Main File

In the bot.ts file, we'll be setting up the basics. We'll start by importing all the required modules from Eris, Node, and our configuration file that will be created later.

// bot.ts

// Everything you'll need from Eris.
import {
  type CommandInteraction,
  type ApplicationCommandOptions,
  type ApplicationCommandStructure,
  client
} from 'eris';
// You'll need this for loading command files.
import { readdirSync } from 'fs';
import { join } from 'path';
import { TOKEN } from './config';

...

bot.ts

Next we'll get started with the structure of the bot by creating a Bot class to store our commands. There's also a Command type that is being created, which outlines the basic structure of a slash command. A setup like this will make it easy to manage and add commands within the bot:

// bot.ts

...

// This is the basic command structure you'll need to add commands
type Command = {
  /** The name of your slash command */
  name: string;
  /** The description of the slash command */
  description: string;
  /** Type 1 is a slash command, there's other types but for now this is all we need. */
  type: 1;
  /** This is where the code for your command goes. */
  run: (interaction: CommandInteraction) => void;
};

export class Bot extends Client {
  /** A map of all the commands the bot has*/
  commands = new Map<string, Command>();
}

...

bot.ts

Within the Bot class, we'll need the loadCommands function so that we can dynamically load commands from the commands directory. We save the command name and the entire command object in the commands property on the Bot class, loading the commands dynamically makes scaling and command management much easier in the future.

// bot.ts (in the Bot class)

...

loadCommands() {
  const commandFiles = readdirSync(path.join(__dirname, 'commands')).filter(
    (file) => file.endsWith('.js'),
  );

  for (const file of commandFiles) {
    const command: Command = require(`./commands/${file}`);
    this.commands.set(command.name, command);
  }
}

...

bot.ts

Now we'll initialize the Bot instance using a token provided in config.ts and loading the commands. Once the bot is ready we'll log a message in the terminal and register the commands. We're mapping the commands without the run function so that it is compatible with Discord's API and then registering them on Discord:

// bot.ts

...

const client = new Bot(TOKEN);
client.loadCommands();

client.on('ready', async () => {
  console.log(
    `${client.user.username}#${client.user.discriminator} is now online!`,
  );

  // We're register the commands here.
  try {
    const slashCommands: ApplicationCommandStructure[] = [
      ...client.commands,
    ].map(([_, { name, description, options, type = 1 }]) => ({
      name,
      description,
      options,
      type,
    }));

    await client.bulkEditCommands(slashCommands);
  } catch (error) {
    throw new Error(error as string);
  }
});

...

bot.ts

Finally we're going to setup an event listener to handle incoming interactions from Discord. When a user invokes a slash command we acknowledge it and destructure the name property from the interaction, we then use the name to get the command object from client.commands. We wrap the command.run in a try...catch statement so that we can catch any errors, where we alert the user of an error and log it to the terminal.

After setting up the event listener we connect to Discord to start listening for interactions.

// bot.ts

...

client.on('interactionCreate', async (interaction: CommandInteraction) => {
  if (interaction.type === 2) {
    // This with respond to the interaction and make it a ephemeral message.
    await interaction.acknowledge(64);

    const { name } = interaction.data;
    const Command = client.commands.get(name);

    // Theoretically this should never happen, but it's better to check.
    if (!Command) throw new Error('Trying to run command that is not register');

    try {
      Command.run(interaction);
    } catch (error) {
      console.error(error as string);
      interaction.createFollowup('An error has occured');
    }
  }
});

// Connect your bot to Discord.
bot.connect();

bot.ts

Creating a Configuration File

In the config.ts file created previously, we'll want to define a TOKEN export for the bots token. You'll get this token from the Discord Developer page, in the bot section of your application.

// config.ts

export const TOKEN = "YOUR_TOKEN_HERE";

config.ts

Creating Your Commands

You're going to want to make some command files, let's start with ping.ts, you'll need to place this in your commands directory for it to be loaded properly. Once you've created the file your structure should look like this:

discord-bot/
|__ bot.ts
|__ config.ts
|__ commands/
   |__ ping.ts

In the ping.ts file we'll create a simple command that responds with "Pong!" when it is ran.

// ping.ts

import { CommandInteraction } from "eris";

export const name = "ping";
export const description = "PING!";

export function run(interaction: CommandInteraction) {
  interaction.createFollowup("Pong!");
}

ping.ts

Adding a Help Command

Assuming you want other users to see the commands your bot has, you'll want a help command. It'll list all the commands available and a description of each underneath it. You'll want to repeat the steps in the ping.ts command

// help.ts

import type { CommandInteraction } from 'eris';
import type { Bot } from '../bot';

export const name = 'help';
export const description = 'See all the available commands';

export function run(interaction: CommandInteraction, client: Bot) {
  interaction.createFollowup(
    [...client.commands]
      .map(([_, { name, description }]) => `### ${name}\n - ${description}`)
      .join('\n'),
  );
}

help.ts

Your directory should now look like this:

discord-bot/
|__ bot.ts
|__ config.ts
|__ commands/
   |__ ping.ts
   |__ help.ts

The help command should be dynamically loaded along with the ping command whenever you start the bot.

Running the Bot

Now that you've got all the files written you're going to want to test the bot, to get started run the following:

tsc
node ./out/bot.js

You should see the following output:

BOT_NAME#0000 is now online!

Once you see that you're ready to add the bot to your server! Although be sure to use the applications.commands and the bot scope to ensure that you add it correctly. You can use this site to easily get an invite URL for your bot.

Here's a quick preview of what your bots interactions should look like:

A GIF preview of what your command should look like once ran.

The Conclusion

Now that you've got the basics down of creating a modular Discord bot with TypeScript and Eris the possibilities are endless. With a modular command structure you're able to easily extend the functionality of your bot.

If you'd like to run your Discord bot 24/7 then you can use a hosting provider like DigitalOcean, Oracle Cloud, or CoderOasis, along with pm2. You can set it up by running the following commands in your terminal:

npm i -g pm2
pm2 start ./out/bot.js --name "Discord Bot"

To see the bot's logs you can use the logs sub command, with the name of the process, in this case Discord Bot:

pm2 logs "Discord Bot"

This should show you the last 15 lines of logging for the bot, you can use the --lines option to show more.


Do you like what you're reading from the CoderOasis Technology Blog? We recommend reading our Implementing RSA in Python from Scratch series next.
Implementing RSA in Python from Scratch
Please note that it is essential for me to emphasize that the code and techniques presented here are intended solely for educational purposes and should never be employed in real-world applications without careful consideration and expert guidance. At the same time, understanding the principles of RSA cryptography and exploring various

The CoderOasis Community

Did you know we have a Community Forums and Discord Server? which we invite everyone to join us? Want to discuss this article with other members of our community? Want to join a laid back place to chill and discuss topics like programming, cybersecurity, web development, and Linux? Consider joining us today!
Join the CoderOasis.com Discord Server!
CoderOasis offers technology news articles about programming, security, web development, Linux, systems admin, and more. | 112 members
CoderOasis Forums
CoderOasis Community Forums where our members can have a place to discuss technology together and share resources with each other.

Do you like what you're reading from the CoderOasis Technology Blog? We recommend reading our Hacktivism: Social Justice by Data Leaks and Defacements as your next choice.
Hacktivism: Social Justice by Data Leaks and Defacements
Around the end of February, a hacktivist that calls himself JaXpArO and My Little Anonymous Revival Project breached the far-right social media platform named Gab. They managed to gain seventy gigabytes of data from the backend databases. The data contained user profiles, private posts, chat messages, and more – a lot

The CoderOasis Community

Did you know we have a Community Forums and Discord Server? which we invite everyone to join us? Want to discuss this article with other members of our community? Want to join a laid back place to chill and discuss topics like programming, cybersecurity, web development, and Linux? Consider joining us today!
Join the CoderOasis.com Discord Server!
CoderOasis offers technology news articles about programming, security, web development, Linux, systems admin, and more. | 112 members
CoderOasis Forums
CoderOasis Community Forums where our members can have a place to discuss technology together and share resources with each other.