On this page
This tutorial builds a small but real Discord bot in JavaScript with discord.js on Node.js: two slash commands, /hello and /add, and a message that welcomes new members. You test it on your own computer, then put it on a free Torchlite Bot server so it stays online without your computer.
You need Node.js 22 or newer and a Discord account. No earlier bot experience is needed.
Create the bot in Discord
- Open the Discord Developer Portal and click New Application. The name you give it is the bot's name.
- Open Bot in the left menu and click Reset Token. Copy the token and keep it somewhere private.
- Further down on the same page, switch on Server Members Intent. The welcome message needs it; the slash commands do not.
- Open OAuth2, then URL Generator. Tick bot and applications.commands, and under Bot Permissions tick Send Messages.
- Open the generated URL, pick your Discord server and click Authorize.
Set up the project
Make a folder for the bot and install discord.js in it:
npm init -y
npm install discord.jsThat creates package.json, which lists discord.js as a dependency, and a node_modules folder you never upload or commit.
Write the bot
Create index.js:
const { Client, Events, GatewayIntentBits, SlashCommandBuilder } = require('discord.js');
const client = new Client({
// Guilds is enough for slash commands; GuildMembers adds join events
// and is switched on in the portal.
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
});
const commands = [
new SlashCommandBuilder().setName('hello').setDescription('Say hello'),
new SlashCommandBuilder()
.setName('add')
.setDescription('Add two numbers')
.addIntegerOption((o) => o.setName('a').setDescription('The first number').setRequired(true))
.addIntegerOption((o) => o.setName('b').setDescription('The second number').setRequired(true)),
];
client.once(Events.ClientReady, async (ready) => {
// Sends the slash commands to Discord once, at startup.
await ready.application.commands.set(commands.map((c) => c.toJSON()));
console.log(`Online as ${ready.user.tag} in ${ready.guilds.cache.size} server(s).`);
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'hello') {
await interaction.reply(`Hello, ${interaction.user}!`);
} else if (interaction.commandName === 'add') {
const a = interaction.options.getInteger('a');
const b = interaction.options.getInteger('b');
await interaction.reply(`${a} + ${b} = ${a + b}`);
}
});
client.on(Events.GuildMemberAdd, async (member) => {
const channel = member.guild.systemChannel;
if (channel) await channel.send(`Welcome to ${member.guild.name}, ${member}!`);
});
client.login(process.env.DISCORD_TOKEN);What each part does:
- Intents tell Discord which events the bot wants.
Guildscovers slash commands;GuildMembersadds join events. SlashCommandBuilderdescribes each command and its options.commands.setregisters them with Discord when the bot is ready.InteractionCreatefires when someone uses a command.options.getIntegerreads the numbers, already checked by Discord.GuildMemberAddruns when someone joins and writes in the server's system channel, the one set under Server Settings, Overview.- The token comes from the
DISCORD_TOKENenvironment variable, never from the code.
Run it on your computer
Set the token for this terminal session, then start the bot. On macOS or Linux:
export DISCORD_TOKEN="paste-your-token-here"
node index.jsOn Windows PowerShell:
$env:DISCORD_TOKEN="paste-your-token-here"
node index.jsThe terminal prints Online as YourBot#1234 in 1 server(s). Type / in your Discord server and the two commands appear. The first time, slash commands can take a few minutes to show up.
Host it for free on Torchlite
Your bot is offline as soon as you close the terminal. To keep it running:
- On Create your server, pick the Bot plan and Discord bot (Node.js).
- In the panel, open Startup and paste the token into Bot token.
- Open Files, delete the starter bot's files, and upload your
index.jsandpackage.json, withoutnode_modules. Check that Main file under Startup isindex.js. - Press Start in the Console. The server installs discord.js from
package.jsonand starts your bot.
To deploy from GitHub instead, so a push and a restart is your whole deploy, follow the Discord bot guide. The Bot plan gives 512 MB of RAM and asks for a Renew once every 24 hours; Discord bot hosting has the details.
Keep it healthy
A few habits save most of the trouble once real people use the bot:
- Answer within 3 seconds. Discord shows "The application did not respond" if a command is not acknowledged in time. For anything slow, such as a web request or a database query, call
await interaction.deferReply()first, then send the result withawait interaction.editReply(...). - Catch errors per command. Wrap risky code in
tryandcatch, and reply with a short message instead of letting the command fail silently. Log the error withconsole.error, so it shows up in the panel's Console. - Keep data on disk, not in memory. Everything in JavaScript variables is lost when the bot restarts, and on a free server it restarts whenever a renew is missed. Store anything that matters in the MySQL database or an SQLite file.
- Do not flood channels. Sending many messages in a loop runs into Discord's rate limits; discord.js queues them, but your bot looks frozen in the meantime. Batch the text into one message instead.
- Watch the memory. 512 MB is plenty, but caching every member of a big server adds up. Only ask for the intents you actually use.
Where to go next
- More commands: add another builder to
commandsand another branch inInteractionCreate. Options exist for strings, numbers, users, roles and channels. - Commands only admins can use: add
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)to a builder, importingPermissionFlagsBitsfrom discord.js. - A database: your Bot server includes a MySQL database, or keep it simple with an SQLite file.
- Split it up: when the file grows, put each command in its own file and load them from a folder, as the discord.js guide shows.
Common problems
"An invalid token was provided" or TokenInvalid
The environment variable is empty or has a space in it, or on Torchlite the Bot token field is empty. Copy the token again and restart.
"Used disallowed intents"
The code asks for GuildMembers, but Server Members Intent is off in the Developer Portal. Switch it on, or remove GuildMembers and the welcome message.
The slash commands do not appear
Wait a few minutes after the first start, and check that the invite URL included applications.commands. Restart the bot once.
Stuck on a step?
Ask on the forum. Your question links back to this guide, so whoever answers knows where you are.
About the author
Torchlite team
The people who run Torchlite
We build and run the Torchlite panel and the nodes. Every guide is checked on our own servers before it goes up, and updated when the panel changes.

