How to Build a Discord Bot With Node.JS
Tags: node, discord, howto, javascript
Published: 2024-02-07 12:17:37

In this tutorial, you will learn how to connect a bot to the Discord server and create commands that it will respond to. To achieve this, you will use Node, Typescript, DiscordJS, and dependency injection(InversifyJS). But before we start, let’s talk about the benefits Discord offers.

Every day we have more and more VoIP services to choose from, but most gamers have already decided on Discord for communication. Today, not only gamers but also agencies and companies started using Discord. Why? You may ask because it offers everything that business communication platforms, such as _**Slack**_ or _**RocketChat**_, need. The main differences are that Discord:

- Is free to use,
- Provides out-of-the-box solutions without much setup or configuration,
- It's perfect for larger teams, as it offers text and voice support for a great number of people

_**Note: Discord, along with all of the features we will be using in this tutorial, is free to use.**_

Before we begin with the tutorial, I assume you have the basics of Typescript and a beginner level of understanding of NodeJS. It is also recommended you are at least at an intermediate level of Javascript. Since you will be using NodeJS, having an environment set up on your PC will help you follow the code. To finish this tutorial you will also need a Discord account, so if you don’t have one, please create one.

To summarize, in this tutorial, you will learn:

- How to create a Discord server and a bot
- How to add a Discord bot to your server
- How to set up a Node project with Typescript superset
- How to use Dependency Injection with InversifyJS
- How to create business logic for bot commands with DiscordJS
- How to test commands through the Discord channel

**_Note: If you want to skip ahead to the final project, check out the code in [my GitHub repository](https://github.com/damirljub/discord-bot)._**

## Setting up the project

For the IDE, you can use whatever you feel comfortable with - for me, that is Webstorm. Before creating a bot, you need to create a project. To do that, use the following command:

```terminal
npm init
```

This will open an interactive process for initializing your project with the **package.json** file. Fill these questions or just skip all by pressing Enter, because it does not matter for this tutorial.

After initializing the project, install the following dependencies by running this command inside your project (terminal):

```terminal
npm i --save discord.js dotenv typescript inversify @types/node reflect-metadata
```

Then for the last step of setup, you need to change scripts inside the **package.json** file. Later on, you will need two scripts, and these **start** and **watch**.

We will use the start script to **start** the bot and the **watch** one to compile the Typescript code with flag -w(atch), which will watch any changes done to the code and compile upon saving. This is how the **package.json** file should look like after your changes:

**package.json**
```json
{
   "name":"discord-bot",
   "version":"1.0.0",
   "description":"",
   "main":"index.js",
   "scripts":{
      "start":"node dist/index.js",
      "watch":"tsc -w -p tsconfig.json"
   },
   "author":"",
   "license":"ISC",
   "dependencies":{
      "@types/node":"^14.14.37",
      "discord.js":"^12.5.3",
      "dotenv":"^8.2.0",
      "inversify":"^5.0.5",
      "reflect-metadata":"^0.1.13",
      "typescript":"^4.2.3"
   }
}
```

Now that you have set up the scripts, it is time to define the TS compiler for your project.

## Defining Typescript compiler

Create **tsconfig.json** in the root directory and add the following snippet to the file: 

```json
"experimentalDecorators": true,

"emitDecoratorMetadata": true,

"resolveJsonModule": true
```

**tsconfig.json**
```json
{
   "compilerOptions":{
      "module":"commonjs",
      "moduleResolution":"node",
      "target":"es2016",
      "lib":[
         "es6",
         "dom"
      ],
      "sourceMap":true,
      "types":[
         "node",
         "reflect-metadata"
      ],
      "typeRoots":[
         "node_modules/@types"
      ],
      "experimentalDecorators":true,
      "emitDecoratorMetadata":true,
      "resolveJsonModule":true
   },
   "exclude":[
      "node_modules"
   ]
}
```

We are adding **experimentalDecorators**, **emitDecoratorMetadata**, **es6**, and **reflect-metadata** because InversifyJS requires it. Once we have created this, we can create **index.ts** inside the src folder and run something like _console.log(“Hello”)_ just for testing purposes.

**index.ts**
```typescript
console.log(“Hello”)
```
If everything is correct, run **_npm run watch_** which should create **src/index.js**, and then run **npm start** which returns the simple message Hello.

![Watch mode 1/2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uu0xb278cjldklr0s5gp.png)


![Watch mode 2/2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rwjfkxchr3io11ayvy1k.png)


The next step is to create a Discord app and connect a Bot to your server.


## Creating an application on Discord

Our goal is to communicate and work with the Discord server, but before you do that you need to create a Discord bot that will connect to the server.

To create the bot, first, you need to sign in to Discord and create an application. You can do that by visiting **[here](https://discord.com/developers/applications/)** and clicking on the New Application button at the top right corner.


![Creating Discord application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bz4m8q0mu6ja9udn0fjd.png)

Clicking the **New Application** button will prompt you to the next step, where you need to choose the **NAME** of your application. Whenever you are ready, click **Create**.

Now, you should see an Application screen with the possibility to enter information about the application - you can skip it for this tutorial.

To create a bot, click **Bot** and then the **Add Bot** button. It will tell you that this action is irrevocable and ask you if you want to create a bot. After clicking **Yes, do it!** You will create a Discord bot. So simple, right?

A page, including the **token** you need, should open up on your screen right now.


![Created bot application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4yc12vid70hlzqduwn7.png)


## Creating and adding a Discord bot to your server

To work with and later on test your bot, you need a Discord server. You need to either create a new Discord server or use an existing one. Whatever you decide, connecting will look the same.

To add the bot to your server, follow these steps:

1. Copy **APPLICATION_ID** from the **General Information** tab.
2. Change APPLICATION_ID in the _[following URL](https://discord.com/oauth2/authorize?client_id=APPLICATION_ID&scope=bot&permissions=1)_ to the one you copied
3. Open the URL in a browser of your choice to start the authentication process.
4. Authorize the process of adding the bot to the server through the steps provided by Discord.
5. Once the bot is successfully added, you will get a message in the default text channel.


![Bot joined](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/25lr95kwrr9zuh33v0aa.png)

Now that you have added a bot to the server, it is time to set up the environment in your project.

## Saving variables in environment files

To safely provide a token to our application, we are using the **dotenv** package. You need to get the Bot token from the Discord Application developer dashboard. It is located at Bot > Click to Reveal Token. After you have the token, you need to safely store it in the **.env** file in the following way:

```env
DISCORD_TOKEN=paste.the.token.here
```

One more thing we need to create for the future is the **.env.example** file. It explains how to define the .env file. Inside of the .env.example place:

```env
DISCORD_TOKEN=
```

## Dependency Injection Container

For an easier understanding of dependency injections, you should already have a _solid_ knowledge of the SOLID principle. If you do, you will have no issue understanding what dependency injections are used for.

In our case, we use InversifyJS for dependency injections, and that is why we need to create our **DI container** in the **inversify.config.ts** file:

```typescript
import "reflect-metadata"
import { Container } from "inversify"
import { TYPES } from "./types"
import { Bot } from "./bot"
import { Client } from "discord.js"
let container = new Container() 

container.bind < Bot > (TYPES.Bot).to(Bot).inSingletonScope() 
container.bind < Client > (TYPES.Client).toConstantValue(new Client()) 
container.bind < string > (TYPES.Token).toConstantValue(process.env.TOKEN) 

export default container
```

One more thing we need to do for dependency injections is to define types. This will give us the security to not have name collisions with containers. Even though it doesn’t matter that much for us in this tutorial, it is good practice to follow the clean architecture and best practices even when developing a project like this.

**types.ts**

```typescript
export const TYPES = { 
Bot: Symbol("Bot"), 
Client: Symbol("Client"), 
Token: Symbol("Token"), 
}
```

## Creating a Bot Class

We will create **bot.ts** inside the src folder and add the following:

```typescript
import {Client, Message} from "discord.js" 
import {inject, injectable} from "inversify" 
import {TYPES} from "./types"
import {CommandResponder} from "./services/command-responder" 

@injectable() 
export class Bot { 
    private client: Client 
    private readonly token: string 
    
    constructor( @inject(TYPES.Client) client: Client, @inject(TYPES.Token) token: string ) { 
        
        this.client = client 
        this.token = token 
    } 
    
    public listen(): Promise < string > { 
        
        this.client.on('message', (message: Message) => { 
            console.log("We got message, it says: ", message.content) 
        }); 
        
        return this.client.login(this.token) 
    } 
}
```

After this, you can add the bot to **index.ts**:

```typescript
require('dotenv').config() 

import container from "./inversify.config" 
import {TYPES} from "./types" 
import {Bot} from "./bot" 


let bot = container.get<Bot>(TYPES.Bot) 


bot.listen().then(() => { 
    console.log('Logged in!') 
}).catch((error) => { 
    console.log('Oh no! ', error) 
})
```

With this done, we have created our basic logic and we can start the project and connect the bot with the Discord server.

```terminal
node src/index.js 
Logged in! 
We got message, it says: Test
```

These are your project foundations. The next step is to develop the logic for listening and responding to messages.

## Responding to messages

Now, you need to create the logic that listens to certain types of messages and responds to them accordingly. Specifically, you want the bot to listen to two commands: !who and !roll. These two commands will make the bot introduce himself and roll one number between 1 and 100. ‍ Then, you need to create two classes - **CommandListener** and **CommandResponder**. Do it by injecting CommandResponder into the Bot class and CommandListener into the CommandResponder. But first, we need to create command-listener.ts inside **src/services**.

```typescript
import { injectable } from "inversify" 

@injectable() 

export class CommandListener { 
    private regexp = ['!who', '!roll'] 
    public isWhoCommand(string: string): boolean { 
        return string.search(this.regexp[0]) >= 0 
    } 
    
    
    public isRollCommand(string: string): boolean { 
        return string.search(this.regexp[1]) >= 0 
    } 
}
```

You can inject that to **command-responder.ts**, which you can create inside **src/services**.

```typescript
import {Message} from "discord.js" 
import {CommandListener} from "./command-listener" 
import {inject, injectable} from "inversify" 
import {TYPES} from "../types" 

@injectable() 
export class CommandResponder { 
    private commandListener: CommandListener 
    
    constructor( 
        @inject(TYPES.CommandListener) commandListener: CommandListener ) { 
            this.commandListener = commandListener 
        } handle(message: Message): Promise<Message | Message[]> { 
            if (this.commandListener.isWhoCommand(message.content)) { 
                return message.reply('I am bot that is made to demonstrate connection between NodeJs and Discord.') 
            } 
            
            if (this.CommandListener.isRollCommand(message.content)) { 
                const rolled = Math.floor(Math.random() * 100) + 1 return message.reply(`You have rolled ${rolled}`) 
            } 
            
            return Promise.reject() 
        } 
    }
```

And then, the **Bot** class needs to be changed to use the CommandResponder class:

```typescript
import {Client, Message} from "discord.js" 
import {inject, injectable} from "inversify" 
import {TYPES} from "./types" 
import {CommandResponder} from "./services/command-responder" 

@injectable() export class Bot { 
    private client: Client 
    private readonly token: string 
    private commandResponder: CommandResponder constructor( 
        
        @inject(TYPES.Client) client: Client, 
        @inject(TYPES.Token) token: string, 
        @inject(TYPES.CommandResponder) commandResponder: CommandResponder) { 
            
        
            this.client = client 
            this.token = token 
            this.commandResponder = commandResponder 
        } 
        
        public listen(): Promise<string> { 
            this.client.on('message', (message: Message) => { 
                
                if (message.author.bot) { console.log('Ignoring my own or other bot messages.') 
                return 
            
            } 
            
            this.commandResponder.handle(message).then(() => { 
                console.log("A response sent!") 
            }).catch(() => { 
                console.log("There was an error with sending a response.") 
            }) 
        }) 
        
        return this.client.login(this.token) 
    } 
}
```

One last thing to do is create new **DI containers** and types for **CommandResponder** and **CommandListener**.

Remember how you bound containers before for token, Bot, and Client classes? Now you need to do the same for CommandResponder and CommandListener.

**inversify.config.ts**

```typescript
container.bind<CommandResponder>(TYPES.CommandResponder).to(CommandResponder).inSingletonScope() 

container.bind<CommandListener>(TYPES.CommandListener).to(CommandListener).inSingletonScope()
```

You also need to add these two types, so that you don’t create a bug by the last addition to the inversify.config.ts. An error will occur because you used CommandResponder and CommandListener types which are not declared in types.ts.

**types.ts**

```typescript
CommandResponder: Symbol("CommandResponder"), 
CommandListener: Symbol("CommandListener")
```

And voila! Your Discord bot is ready to listen to your two new commands and respond to them accordingly. The only thing left to do is to restart the app and test the bot.

After testing our commands, this is the response you get:


![Response bot 1/2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1x4ii4q14tmylnti3mh.png)


![Response bot 2/2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/26crrbz9xqhh13k9paq8.png)

This is the response you should expect - it shows everything works as developed.


## What’s next?

First of all, congratulations! You just made it through the whole article and (hopefully) learned how to do a Discord bot. But you might say - _**OK, that's cool and all. But what can I do with it?**_ Well, you can play around and create more complex commands, connecting Discord to your external database or integrating Discord with some type of API (such as OMDB API or any other of the free ones). However, since I covered the basics in this article, I suggest you research larger communities and learn about all the commands and the complexity of Discord bots.

If you have any questions or ideas regarding this blog post, please feel free to reach out. And most importantly, have fun coding! :)