# Starting the application
# Configuration files
After adding your bot to a server, the next step is to kickstart development and get it online. We'll begin by creating a configuration file for your token and a main file for your bot application.
Open your VSCode; we're going to create some files and set up a few sections.
# Setting up environment variables
Environment variables are like special containers that hold important information your computer and programs need to work correctly. They can store things like settings, passwords, and other essential data, and programs can access these variables whenever they need that information. It's a way to keep sensitive or changing data separate from the code itself, making your applications more secure and flexible.
Inside your VSCode, at the root of your project, we're going to create a .env file, and within it, we'll place the following information:
TOKEN=your-token-goes-here
In this way:
Following the same installation process as the discord.js module, we will install the dotenv module, which is responsible for connecting us to the environment variables.
In the terminal, we'll execute the following command:
npm i dotenv
# Setting up the package.json
In node.js, the default module type is CommonJS, which is widely used. However, in this tutorial, we will be taking a different approach, aligning ourselves with the latest ECMAScript (ES) standards. We will be using the type module
, a more recent and flexible feature for managing modules in node.js applications. This will allow us to take advantage of advanced module import and export capabilities, providing greater flexibility and readability to our code.
# Main file
Just like we created the .env
file, we'll now create our main file, index.js
. Inside this file, we will add the following content for a quick test:
import { Client, GatewayIntentBits } from 'discord.js';
import 'dotenv/config';
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once('ready', () => {
console.log(`Ready! Logged in as ${client.user.tag}`);
});
client.login(process.env.TOKEN);
Great, now let's check if our bot is working correctly:
Excellent, in VSCode, it says it's online. Now, let's open Discord and go to the server where we added our application to verify if it's truly online.
As we can see, it's indeed online. However, it doesn't have any useful functionality yet. Let's add and refine a few things to enhance its performance.
We're going to enhance our index.js
for better performance and organization. We'll extend the Client
class and add our methods. We'll add this to our main file:
import Client from './src/Structure/Client.js';
import dotenv from 'dotenv/config';
import options from './src/Config/Options.js';
const client = new Client(options);
client.login(process.env.TOKEN);
Wow, that's a significant simplification, isn't it? However, if we start the application now, it will return some errors because we haven't created the Client.js
or Options.js
files yet. In the following steps, we will learn how to configure these and more files.
# Options
Within our project, we will create a folder called src
(short for "source"). This folder will be used to organize all our code in a structured manner. Creating the folder is quite simple: just click on the icon located next to the project name and select New Folder
. Like this:
Next, within our src
folder, we will create a subfolder called Config
. This folder will be used to store project configurations, including settings, options, and various important elements for the functioning of our application.
Inside our Config
folder, we will create the Options.js
file. This file will serve as a storage location for our Intents
and potentially application Partials
. Like this:
import { GatewayIntentBits, Partials } from 'discord.js';
const options = {
intents: [
GatewayIntentBits.AutoModerationConfiguration,
GatewayIntentBits.AutoModerationExecution,
GatewayIntentBits.DirectMessageReactions,
GatewayIntentBits.DirectMessages,
GatewayIntentBits.GuildEmojisAndStickers,
GatewayIntentBits.GuildIntegrations,
GatewayIntentBits.GuildInvites,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildMessageTyping,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildModeration,
GatewayIntentBits.GuildPresences,
GatewayIntentBits.GuildScheduledEvents,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildWebhooks,
GatewayIntentBits.Guilds,
GatewayIntentBits.MessageContent
],
partials: [
Partials.Channel,
Partials.GuildMember,
Partials.GuildScheduledEvent,
Partials.Message,
Partials.Reaction,
Partials.ThreadMember,
Partials.User
]
};
export default options;
These are all the Intents and Partials that your application can have; you are not obligated to use all of them. Feel free to modify them according to your application's specific needs. At the end, everything will look like this:
# Structures configuration
# Client
Continuing in the same vein as when we created the Config
folder, we will now establish the Structure
folder to accommodate our classes and methods. Within the Structure
folder, we will add a file named Client.js
with the following content:
import { readdirSync } from 'fs';
import { join } from 'path';
import { Client } from 'discord.js';
export default class extends Client {
constructor(options) {
super(options);
this.SlashCommandArray = [];
this.getSlashCommands();
this.getEvents();
}
async registerCommands() {
await this.application.commands.set(this.SlashCommandArray)
}
async getSlashCommands(path = 'src/SlashCommands') {
const categories = readdirSync(path);
for (const category of categories) {
const commands = readdirSync(`${path}/${category}`);
for (const command of commands) {
const commandFile = join(process.cwd(), `${path}/${category}/${command}`);
const { default: SlashCommands } = await import('file://' + commandFile);
const cmd = new SlashCommands(this);
this.SlashCommandArray.push(cmd);
}
}
}
async getEvents(path = 'src/Events') {
const eventsFolders = readdirSync(path);
for (const folders of eventsFolders) {
const eventsFiles = readdirSync(`${path}/${folders}`);
for (const files of eventsFiles) {
if (!files.endsWith('.js')) return;
const eventFile = join(process.cwd(), `${path}/${folders}/${files}`);
const { default: EventMap } = await import('file://' + eventFile);
const evnt = new EventMap(this);
if (!evnt.once) {
this.on(evnt.name, evnt.run);
} else {
this.once(evnt.name, evnt.run);
}
}
}
}
};
If configured correctly, everything will appear as follows:
# EventMap
Following our extended Client
class, we will create another class to handle our events, which will be named EventMap
. Within our "Structure" folder, we will add the EventMap.js
file and include the following content:
class EventMap {
constructor(client, options) {
this.client = client;
this.name = options.name;
this.once = options.once || false;
}
}
export default EventMap;
Like this:
# SlashCommands
Following the approach we used for creating the EventMap
, we will now create the SlashCommands
class. In a similar manner, we will add a SlashCommands.js
file within the Structure
folder, with the following content:
class SlashCommands {
constructor(client, options) {
this.client = client;
this.name = options.name || options.data.name;
this.description = options.description || options.data.description;
this.options = options.options || options.data?.options;
}
}
export default SlashCommands;
If configured correctly, everything will look like this:
# PrefixCommands
Alright, we've added SlashCommands, but why not PrefixCommands
? It's a valid question, and in this section, we'll explain how to add PrefixCommands to the structure. Remember that these are entirely optional, just like SlashCommands.
First, let's return to our Client.js
file, where we will add the PrefixCommands
by including the following content:
async getPrefixCommands(path = 'src/PrefixCommands') {
const categories = readdirSync(path);
for (const category of categories) {
const commands = readdirSync(`${path}/${category}`);
for (const command of commands) {
const commandFile = join(process.cwd(), `${path}/${category}/${command}`);
const { default: PrefixCommands } = await import('file://' + commandFile);
const cmd = new PrefixCommands(this);
this.PrefixCommandArray.push(cmd);
}
}
}
Next, we will call this method in the constructor precisely to register our PrefixCommands:
export default class extends Client {
constructor(options) {
super(options);
this.SlashCommandArray = [];
this.PrefixCommandArray = [];
this.getPrefixCommands();
this.getSlashCommands();
this.getEvents();
}
And if you're as sharp as I am, everything will look like this:
But hold on, we're not quite done yet. We still need to add the class that defines our PrefixCommands. To do this, navigate to the Structure
folder and create a file named PrefixCommands.js
for this purpose. Include the following content in the file:
class PrefixCommands {
constructor(client, options) {
this.client = client;
this.name = options.name;
this.description = options?.description;
this.aliases = options?.aliases;
}
}
export default PrefixCommands;
# Events
Events serve as the notifications or signals that something has happened within Discord, such as a user sending a message or a server member joining. In Discord.js, we capture and process these events to create specific responses or functionalities in our bot.
# ready
TIP
If you didn't add the registerCommands
method to your Client.js
file, simply remove the await this.client.registerCommands();
from the code below.
Now, remember when we configured our ready
event in the test we conducted? We'll do the same thing, but this time using classes instead of the raw approach.
We will create an Events
folder inside the src
directory. Inside this Events
folder, we'll create a subfolder named Client
and create a file named ReadyEvent.js
with the following content:
import EventMap from '../../Structure/EventMap.js';
export default class extends EventMap {
constructor(client) {
super(client, {
name: 'ready',
once: true
});
}
run = async () => {
await this.client.registerCommands(); // We use 'this.client' to reference our extended class from the 'Client.js' file.
console.log(`Ready! Logged in as ${this.client.user.tag}`);
};
};
If you, just like me, have done everything correctly, it will look like this:
# interactionCreate
WARNING
This section will be applicable only to those who have configured the SlashCommands
. If you haven't configured them, please refer to the SlashCommands
section or simply proceed to the next step.
Okay, just like we created and configured our ready
event, we'll do the same for our interactionCreate
event. We'll add a subfolder within the 'Events' directory called 'Interaction
and place our interactionCreate.js
file inside with the following content:
import EventMap from '../../Structure/EventMap.js';
export default class extends EventMap {
constructor(client) {
super(client, {
name: 'interactionCreate'
});
}
run = async (interaction) => {
const commandName = interaction.commandName;
const command = this.client.SlashCommandArray.find((c) => c.name === commandName);
if (!command) return;
command.run(interaction);
}
}
Now, everything will look like this:
# messageCreate
WARNING
This section will be applicable only to those who have configured the PrefixCommands
. If you haven't configured them, please refer to the PrefixCommands
section or simply proceed to the next step.
Following the section above, we will create a subfolder within the Events
folder named Message
and include a file called messageCreate.js
with the following content:
import EventMap from '../../Structure/EventMap.js';
export default class extends EventMap {
constructor(client) {
super(client, {
name: 'messageCreate'
});
}
run = async (message) => {
if (message.author.bot) return;
const prefix = '--'; // change the prefix here
if (!message.content.toLowerCase().startsWith(prefix)) return;
const content = message.content.slice(prefix.length).trim();
const [cmd, ...args] = content.split(" ");
const command = this.client.PrefixCommandArray.find((c) => c.name === cmd.toLowerCase() || c.aliases?.includes(cmd.toLowerCase()))
if (!command) return;
command.run(message, args);
}
}
I think you're already tired of hearing this, but everything will look like this:
# Creating SlashCommands
Perhaps this is the most crucial module so far. We'll create our first slash command to check if everything is in order. First, let's create a folder named SlashCommands
inside the src
directory. Within the SlashCommands
folder, we'll establish a subfolder named Test
with a file named TestCommand.js
with the following content:
import SlashCommands from '../../Structure/SlashCommands.js';
import { SlashCommandBuilder } from 'discord.js';
export default class extends SlashCommands {
constructor(client) {
super(client, {
data: new SlashCommandBuilder()
.setName('test')
.setDescription('.')
});
}
run = async (interaction) => {
interaction.reply({ content: 'hello world' })
}
}
Having said that, everything will look like this:
Now it's time for the truth. Let's see if everything in our structure is functional and harmonious. Let's open a new terminal and run the command node .
WARNING
If you have implemented the PrefixCommands
, your application will not start at this point. You will launch it in the following module.
Congratulations, our application is online and functional! Now, all you need to do is fill it with fun and creative commands to turn it into a great bot. When you're ready and feel confident with the structure we just implemented, you can move on to the next module, which covers Advanced implementation
, to take this structure to the next level.
# Creating PrefixCommands
Following the same structure as above, we will create a folder named PrefixCommands
. Inside it, we will create a subfolder named Test
, and within this folder, we will create the file TestCommand.js
with the content below:
import PrefixCommands from '../../Structure/PrefixCommands.js';
export default class extends PrefixCommands {
constructor(client) {
super(client, {
name: 'test',
aliases: ['testing']
});
}
run = (message, args) => {
message.reply({ content: 'hello world' })
}
}
And if you are as good as I am, everything would look like this:
And finally, let's verify if everything we've done is correct and fully functional. Open a new terminal and start our application with node .
Of course, that worked correctly. Well, gentlemen, it was an honor to create this documentation for you and teach you a bit about discord.js and class-based structure. This module will be the last for many, but if by some miracle you wish to continue honing your skills and truly studying this structure, I hope to see you in the next module Advanced implementation
. See you there.