How to create a discord bot with javascript
Tags: discord, tutorial, node, beginners
Published: 2024-01-02 18:10:46

If you use Discord you probably knows some bot used in the platform, be it Loritta, MEE6 or some other. In fact, using bots in your server is a crucial part of the Discord experience nowadays. With that in mind, let's find out today how to create our own bot! 

## Table of Contents
- [Why create a bot?](#why-create-a-bot)
- [Configuring the Development Enviroment](#configuring-the-development-enviroment)
- [Creating a new Application](#creating-a-new-application)
- [Giving your Bot life](#giving-your-bot-life)
- [Creating your first command](#creating-your-first-command)
- [Slash Commands](#slash-commands)
- [Handling commands and events](#handling-commands-and-events)
- [Tips](#tips)
- [Learn more](#learn-more)
- [Conclusion](#conclusion)

## Why create a bot?

As I said earlier, nowadays on Discord we have thousands of bots with the most diverse functionalities, from moderation to RPG or something completely new, so why bother creating your own?

There are several reasons that can attract you to the development of bots, the first of them is simply curiosity, maybe you are interested in how they are made, another is the need, maybe you had a crazy idea that no other bot has implemented yet and want to create it (I for example have a bot that is only available on my college class server).

Whatever the reason is, it is going to lead you to study and practice, develop real projects and study unknown areas is the key to becoming a better developer, and maybe who knows, you might fall in love with the area :) ok, enough rambling and let's get our hands dirty!

## Configuring the Development Enviroment

First of all, we need to install [Node.js](https://nodejs.org/en) and an IDE or text editor, I like [Visual Studio Code](https://code.visualstudio.com/).

After that, create a new folder with the name you want, your project will be inside this folder, enter your newly created folder with your preferred code editor and use the command `npm init -y`, this will create a javascript project for you.

![Content of the file package.json](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u2t28dgsr0sr2tv6vmkz.png)

As you can see, this command created the file **package.json**, this file stores the information of your project, as the focus of this article is not to teach javascript, I will not go into the functionality of each field, the important thing to know is that this file is essential for the correct functioning of your project.

## Creating a new Application

Before doing anything else in our project, we need to register a new application on the [Discord Developer Portal](https://discord.com/developers/applications)

This is the Discord applications page, here all your applications can be viewed and edited, "applications" is the term Discord uses to refer to bots, if you have never created one before, this page will be empty, let's click on "New Application" in the upper right corner.

![Discord Developers Portal Initial Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hn70j1yuf3mw81anp5ms.png)

You'll now be asked to enter a name for your application, don't worry, it can be edited later!

![Application name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmff98abzy97baymjwdj.png)

After that, the general information page will appear, it looks like this:

![General information about the bot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nadwkaco201ftfg2p5b.png)

This page contains information such as the name, description, photo, tags, number of servers of your bot among others, feel free to leave the bot with your face, for simplicity, I will leave everything as it is.

The next step is to create an invite link for your bot, so we can add it to any server we want, for that we need to select the OAuth2 button in the side menu, when the menu opens below this button choose the URL generator.

![OAuth2 Button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cy42mgnvxvl43on08mem.png)

In the "Scopes" section, we will select "bot" and "applications.commands", this will tell Discord that we want to add a Bot that has permission to register commands.

![Bot Scopes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3hnn7ikkgxxlp9uquia9.png)

As soon as you click on "bot", a new section of bot permissions will appear, this is used for Discord to automatically create a role for your bot when it joins a server, so you make sure that your bot will have certain permissions that may be essential for its operation (if the person who adds the bot allows it), in this tutorial we don't need to select any, because our bot will only send messages, something that the "everyone" role can already do.

Scroll to the bottom of the page and copy the invite link Discord has generated, this is the link that will be used to add your bot to servers, add your bot to a server of yours that serves to do tests (I recommend creating a new server for this).

![Invite link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nxd46uiqkev6z62r8mmm.png)

Once again access the lateral menu anc click on the "Bot" page.

![Bot page button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xgmeyapx3nkvztyd5kt8.png)

On this page we have a very important part, the "Build-A-Bot" section, it controls how users see your bot, your photo, your username and it also stores your **token**.

The token is what tells Discord which is your bot, **do not share it**, whoever has access to your token has access to your bot, go ahead and click to reset the token, don't forget to write it down because Discord only shows it once!

![Build-A-Bot Section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vtmbrx2937wmjc27j64c.png)
(don't waste your time trying to control my bot, it has already been reset)

Finally, scroll down to the "Priviled Gateway Intents" section, on the same page, and activate the permission, "Message Content Intent", this section contains special permissions, which if your bot is in more than 100 servers, requires a Discord analysis to be activated, but don't worry, that's not our case. This permission is necessary for the bot to be able to read the messages that users send, so it can respond to them.

![Special Permissions Section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qa68wxjksw15fqlmpbyg.png)

If you've made it this far, congratulations! You've successfully registered a new bot on Discord, now let's see how to program it.

## Giving your Bot life

Previously we created a new javascript project, let's access it again.

We will use a library called [discord.js](https://discord.js.org/#/docs/main/stable/general/welcome), it is one of the most used libraries for creating bots and will be our main tool.

We can install it by running the command `npm i discord.js`, note that a folder called **node_modules** was created in your project, it stores all the libraries you install, don't mess with it unless you know what you're doing!

Let's now create a new file called **.env**, who will be responsible with storing our bot token in a safe way, when you create it put "TOKEN=" and your token after it, this way we create an environment variable called TOKEN.

![.env file content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bj926yuuogrclxyut7k4.png)

To be able to use that variable in our project, we need to download a library called dotenv, `npm i dotenv`.

Besides that, we need to create a file called **index.js**, it will be our bot's main file, responsible for keeping it online, registering commands, etc.

Initially the content of our index file will be the following:

```javascript
// Importing the necessary dependencies
const { Client, GatewayIntentBits } = require("discord.js")
require("dotenv").config()

// Creating the client instance
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] })

// When the client (bot) is ready, we write to the console
client.once("ready", () => {
	console.log("Hello world!")
})

// Logging the bot
client.login(process.env.TOKEN)

```

This is a very straightforward code, we import dotenv and the necessary classes from discord.js, create a new "client" that is our bot's client, these GatewayIntentBits are events (intents) that you declare that you want to receive in our case we are interested in events related to messages and servers (guilds), as well as the content of the message, which is that special permission we activated earlier, for more information check the [Discord Documentation](https://discord.com/developers/docs/topics/gateway#list-of-intents).

Afterwards, we declare the function `client.once("ready")`, when the bot is logged in, all the code inside it will be executed **only once**, note the difference between `client.on` and `client.once`, the first one executes the code every time that event happens, while the second one executes only the first time that event happens.

Finally we invoke `client.login` passing the token we put in **.env**, if everything is correct, when you use `node .` in the terminal, the bot should respond and be online on discord.

![Bot responding on the terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g63ri57ujcmf3yx11qxo.png)
![Bot online on discord](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62i94rzh5yhyv3zt5la8.png)

## Creating your first command

The time that everyone was waiting for has come, let's create our first command! To do this, open your **index.js** file again and let's add the following code:

```javascript
client.on("messageCreate", (message) => {
	if (message.content === "!ping") {
		message.reply(`Pong! ${client.ws.ping}ms`)
	}
})
```

This is the standard structure of what we call "prefix commands", since we use prefixes to differentiate common messages from command calls, in this case the prefix "!", as you can see, we use here the `client.on`, because as it is a command, we want it to be whenever necessary, and we use the **messageCreate**, event that happens every time a message is sent.

Inside the event, we check if the message is "!ping", if it is, we respond with "Pong!" and the bot's ping, which is the time it takes for the bot to respond to the message, in milliseconds. If everything is correct, when you type "!ping" in the discord chat, the bot should respond with "Pong!" and the ping:

![Bot replying: Pong! 273ms](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dwb9d3su3kpj658tfcsr.png)

This is a pretty simple command, but it illustrates very well how these types of commands work, however, in the next topic we will learn about "slash commands", which are much more powerful and easy to use, besides being the way Discord recommends to create commands.

## Slash commands

Note that the way we created a command previously, adding a new "if" for each command would be a headache, it would be necessary to chain "ifs" or a huge switch case that would basically encapsulate your entire project, which is terrible for the developer and user.

To mitigate this problem, we will create a folder called **src** and inside it a folder called **commands**, this way, each command will be its own file, let's start with a command called **user.js**.

![Project structure, with the user.js file inside the commands folder inside the src folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tkmghy8kvlf98kvi6oru.png)

A Slash Command is nothing more than an object with the properties "data" and "execute" (of course you can add more properties, but these are the essential ones), "data" refers to the information of your command, such as name, description, arguments, etc. As the name of the file suggests, we will make a command that shows some information about a user, we start like this:

```javascript
const { SlashCommandBuilder, EmbedBuilder, time } = require("discord.js")

module.exports = {
	data: new SlashCommandBuilder()
		.setName("user")
		// we can internacionalize the name of the command like this, 
		// so if the user is using the discord in portuguese, the command will appear in portuguese
		.setNameLocalization("pt-BR", "usuário")
		.setDescription("See some information about a user")
		.setDescriptionLocalization("pt-BR", "Veja algumas informações sobre um usuário")
		// we don't want this command to be used in DM
		.setDMPermission(false)
		// we declare an argument that the user will have to provide when using the command, of the type user
		.addUserOption((option) =>
			option
				.setName("user")
				.setNameLocalization("pt-BR", "usuário")
				.setDescription("The user to get information")
				.setDescriptionLocalization("pt-BR", "O usuário para pegar as informações")
				.setRequired(true)
		),
```

Let's hold on for a moment, it's a lot of new information at once, don't rush and read everything and the comments, most of the methods are trivial and are explained by the name.

Here we are declaring a command using the `SlashCommandBuilder`, notice that we pass the name and description in English and use the "Localization" methods to put the name in Portuguese, this is a great tool for internationalization of discord, so whoever is using the application in Portuguese will see the command in Portuguese, otherwise, the command will be shown in English and for us developers nothing changes.

We use `.setDMPermission(false)` to indicate that we don't want this command to be used in DM, additionally, we declare an argument that the user will have to provide when using the command, of the type user.

Now let's write the propert **execute** below the code we already have:

```javascript
	execute: async ({ interaction }) => {
		// I always like to do this to have more time to respond, it's that message that the bot is thinking
		await interaction.deferReply().catch(() => {})

		// we get the user that was passed as an argument
		const member = interaction.options.getMember("user")

		// we get user information and put it in an embed
		const embed = new EmbedBuilder()
			.setTitle(member.displayName)
			.setColor(member.displayColor)
			.setThumbnail(member.displayAvatarURL())
			.addFields(
				{
					name: "Membro do Discord desde",
					value: time(member.user.createdAt),
					inline: true,
				},
				{
					name: "Membro desse servidor desde",
					value: time(member.joinedAt),
					inline: true,
				}
			)
			.setFooter({ text: member.user.tag, iconURL: member.displayAvatarURL() })

		// we need to edit the response and not just reply because deferReply() was used earlier
		interaction.editReply({ embeds: [embed] })
	},
}

```

Like the name implies, this is the piece of code that will be executed every time a user uses this command, in short, we create an embed with some information about the user, such as image, name, when they created their discord account, etc.

If you are like me, maybe when you finish this command you have already restarted the bot and tried to use the command, if you did that, you noticed that it doesn't appear, why?

## Handling commands and events

Our command does not appear yet because slash commands are different from prefix commands, this is because we have to register them in the Discord API, let's learn how to do that.

Inside the **src** folder, but outside the **commands** folder, we will create a new file **functions.js**

![Project Structure with the functions.js file outside the commands folder but inside the src folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/29cwj89qdbszeky5h4ri.png)

Inside this file, we will declare the function **loadFiles**:

```javascript
const fs = require("fs")
const { promisify } = require("util")

const readdir = promisify(fs.readdir)

async function loadFiles(dirName) {
	const basePath = `${process.cwd().replace(/\\/g, "/")}/src/${dirName}`

	const files = []
	const items = await readdir(basePath)
	for (const item of items) {
		const itemPath = `${basePath}/${item}`
		if (itemPath.endsWith(".js")) {
			files.push(itemPath)
			delete require.cache[require.resolve(itemPath)]
		}
	}

	return files
}
```

Maybe this code looks alien to you, but it serves to load all the files in a folder inside the **src** directory.

In sequence, we will write the function **loadCommands**, which as the name suggests serves to load all the commands and register them:

```javascript
async function loadCommands(client) {
	await client.commands.clear()

	const commandsArray = []

	const Files = await loadFiles("commands")

	Files.forEach((file) => {
		const command = require(file)
		client.commands.set(command.data.name, command)
		commandsArray.push(command.data.toJSON())

		console.log(`Command: ${command.data.name} ✅`)
	})

	client.application.commands.set(commandsArray)
}
```

Analagously, we will declare the **loadEvents**, so we can declare events as we declare commands, one per file inside its own folder:


```javascript
async function loadEvents(client) {
	await client.events.clear()

	const Files = await loadFiles("events")

	Files.forEach((file) => {
		const event = require(file)

		const execute = (...args) => event.execute(...args, client)
		client.events.set(event.name, execute)

		if (event.once) {
			client.once(event.name, execute)
		} else {
			client.on(event.name, execute)
		}

		console.log(`Events: ${event.name} ✅`)
	})
}

module.exports = {
	loadFiles,
	loadEvents,
	loadCommands,
}
```

Pay attention that if in the event declaration there is the property `once: true`, we declare it as `client.once` and otherwise as `client.on`, difference that we discussed earlier. Finally, we export the 3 functions.

Maybe now you are a little confused, I just showed you 3 functions and went into little detail about them, I ask for patience, soon the importance of them will become clear.

Now that we hve these functions, let's go back to **index.js** and change the code:

```javascript
// Importing the necessary dependencies
const { Client, GatewayIntentBits, Collection } = require("discord.js")
require("dotenv").config()

// Importing the functions we created
const { loadEvents, loadCommands } = require("./src/functions")

// Creating the client instance
const client = new Client({
	intents: [GatewayIntentBits.Guilds],
})

// When the client (bot) is ready, we write to the console
client.once("ready", () => {
	console.log("Hello world!")
})

// Logging the bot
client.login(process.env.TOKEN)
```

Attentive readers will notice that we imported the functions we just created, more attentive readers will notice that we removed some of the intents that existed before, that's because we don't need to read messages anymore (which also implies that that special permission we activated in the Discord portal is also not necessary in this type of command), and even more attentive readers will notice that we added **Collection** to the classes we import from discord.

A **Collection** is a data structure created by discord.js, which is basically a Map with extra methods, we will use it inside the "ready" event as follows:

```javascript
// When the client (bot) is ready, we write to the console
client.once("ready", () => {
	console.log("Olá mundo!")

	client.commands = new Collection()
	client.events = new Collection()

	loadEvents(client)
	loadCommands(client)
})
```

The commands and events will be stored inside client using the **Collections**, we use **loadEvents** and **loadCommands** to load them.

Now, you may be wondering why I created a function called **loadEvents** and called it inside the main file, when we don't have any events, remember how in the prefix commands it was necessary to use **messageCreate**? So, to control the prefix commands, we also need an event, the **interactionCreate**.

Let's create the folder **events** inside the **src** folder and inside it the file **interactions.js**

![Project Structure with the interactions.js file inside the events foled inside the src folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fl2ts56pksls0rlk66u0.png)

We will declare the event **interactionCreate** inside this file:

```javascript
module.exports = {
	name: "interactionCreate",
	execute: async (interaction, client) => {
		if (interaction.isChatInputCommand()) {
			// we get the command by the name
			const command = client.commands.get(interaction.commandName)

			// we invoke the command's execute function, passing some useful information
			command.execute({
				interaction,
				client,
				member: interaction.member,
				guild: interaction.guild,
				user: interaction.user,
				channel: interaction.channel,
			})
		}
	},
}
```

This is the structure of an event, **name** refers to the type of event, in this case **interactionCreate** and again we have the execute as the code that will be executed every time this event is invoked.

We use `interaction.isChatInputCommand()` to make sure it is a command that is invoked by chat, because we have other types of commands that are handled within this same event, after that we check which command it is and invoke its execute passing some useful properties.

Alright! Now, if we restart the bot, we will see the bot in the terminal telling us that the user command and the interactionCreate event were loaded successfully.

![Terminal with the success messages](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/car1ucvi7e92dm9x1i3h.png)

Now, if we go to the Discord and type **/user**, we will see the command!

![Command appearing in discord chat](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ll1rya9o5978w9j8asqs.png)

Choose a user and see the result!

![Command working in the discord chat, showing a embed with some information about the choosen user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tmkzelqzywcn1uraxwkt.png)

Congratulations! You have created your first Discord bot!

This is the basic structure of a bot, there are still many more advanced concepts, cooldowns, sub commands, tests... but with this structure you can already start your journey in this universe!

## Tips

I will leave here some loose tips for you who want to keep developing in this area:

- Create a second bot, leave your first bot as a "production" environment and do your tests on the second bot, that way you can develop without affecting your users (we can switch between the two just by changing the token)
- The site [top.gg](https://top.gg/) contains thousands of different bots and it is possible to filter by categories, it is a good place to search for other bots similar to yours and it is also possible to put your bot there for more exposure
- Think about creating a website for your bot, if you want to verify your bot, you have to fill some requirements, two of them is to provide the link to your Privacy Policy and Terms of Service, a good way to do this is with a website, which is also a good vehicle to promote your bot
- My way of creating bots is not supreme! The discord.js library is very non-opinionated and there are infinite ways to declare a bot (it is even possible to create bots with Typescript with the same library), use Github! Search!

## Learn more

If you wish to learn more and take your bot to the next level, here are some useful materials:

- [Discord.js Guide](https://discordjs.guide/#before-you-begin) this is a very complete and well written guide that covers everything I covered here and MUCH more!
- [Discord.js library documentation](https://discord.js.org/docs/packages/discord.js/14.14.1) the documentation is a great site to save and check whenever you need, it is very simple to find what you want inside it and see all the methods and properties of that class
- A little marketing on my part, but which can also be useful as a reference are the repositories of my [main bot](https://gihub.com/falcao-g/Falbot), my own [discord minigames library](https://github.com/falcao-g/Falgames) and my [discord bot template](https://github.com/falcao-g/discord-bot-template) which is a little more complete than what we developed together here
- The code developed in this article will also be available [on my github](https://github.com/falcao-g/tutorial-discord-bot)

## Conclusion

If you sticked with me until here, thank you very much! I hope you have at least learned something new, this is my first article here so any (constructive) criticism, comment, question or compliment please leave in the comments :)