> ## Content Index
> Fetch the complete content index at: https://sudosecurity.org/llms.txt
> Use this file to discover other available public pages before exploring further.

# Building a Discord Bot in Node, TypeScript, and Eris
- URL: https://sudosecurity.org/js-discord-bot-simplified-guide/
- Published: 2023-04-01T04:55:00.000Z
- Updated: 2026-10-16T05:09:33.000Z
- Author: Landen
- Tags: Software Development, JavaScript, Tutorials

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](https://nodejs.org/en/download?ref=sudosecurity.org) along with NPM
- A text editor or IDE, I personally use [VSCode](https://code.visualstudio.com/?ref=sudosecurity.org) although you can use what you prefer
- A basic understanding of TypeScript/JavaScript concept

## Creating the Project

First you'll want to setup your project directory:

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

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

```bash
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:

```json
{
  "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.

```typescript
// 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:

```typescript
// 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.

```typescript
// 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:

```typescript
// 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.

```typescript
// 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](https://discord.com/developers/applications?ref=sudosecurity.org) page, in the `bot` section of your application.

```typescript
// 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:

```plaintext
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.

```typescript
// 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 

```typescript
// 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:

```plaintext
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:

```bash
tsc
node ./out/bot.js
```

You should see the following output:

```bash
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](https://discordapi.com/permissions.html?ref=sudosecurity.org) to easily get an invite URL for your bot.

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

![](https://sudosecurity.org/content/images/2024/03/Discord_nxQXcsLrwe.gif)

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](https://www.digitalocean.com/?ref=sudosecurity.org), [Oracle Cloud](https://www.oracle.com/cloud/?ref=sudosecurity.org), or CoderOasis, along with `pm2`. You can set it up by running the following commands in your terminal:

```bash
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`:

```bash
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.