Choosing a Database for a Discord Bot: SQLite, PostgreSQL, or Redis
Select storage based on data durability, concurrent writes, and operational limits.
Read article ↗Create a working discord.js slash-command bot, register commands in a test guild, keep the token private, and deploy with a reliable startup workflow.
A Discord bot is a small service that reacts to events in a Discord community. Before thinking about hosting, it helps to build a bot that works locally, has the correct permissions, and can explain its own failures through safe logs. This tutorial creates a working /ping slash command with discord.js, registers the command in a private test server, and prepares the project for deployment to a compatible Node.js hosting environment.
We use an interaction-based command instead of reading every message in a channel. That keeps the example focused and avoids requiring the privileged Message Content intent for a simple ping command. The walkthrough also separates one-time command registration from the long-running bot process: a distinction that prevents several common beginner mistakes.
You need a Discord account, permission to install an application in a test server, a recent supported Node.js version, npm, and a text editor. If your current hosting environment does not support the Node.js version required by the latest discord.js release, choose a compatible supported combination after consulting the library’s current documentation. Avoid copying an old tutorial’s package versions without checking their maintenance status.
Our bot does one thing: respond to /ping with Pong!. This is enough to prove authentication, command registration, gateway connection, event handling, and reply permissions. Once it works, you can extend the project with moderation or community features.
Discord uses several identifiers that look similar but serve different purposes. The application ID identifies the application, the guild ID identifies your test server, and the bot token is a secret credential. Application and guild IDs are used for command registration; the token authenticates your bot. Never publish the token, share it in screenshots, or copy it into a support chat.
Open the Discord Developer Portal, create an application, and find its bot settings. Prepare the application for installation in your test server. Use the installation settings and authorization scopes appropriate for a bot with application commands; Discord’s interface evolves, so follow the current portal labels instead of assuming every screen matches a screenshot from an older guide.
Install the bot into a server where you have the necessary permissions. Prefer a private development server until you have tested all commands. For a ping command, there is no need to grant administrator permission or enable privileged intents. Only request additional access when your bot’s documented behavior requires it.
Discord’s desktop client includes a developer option that lets you copy the guild ID. Copy the application ID from the Developer Portal and the guild ID from your test server. Keep them nearby for configuration, but do not confuse them with the token. The token should be placed only in a local environment file or secret manager.
Make a new project folder and initialize npm:
mkdir discord-ping-bot
cd discord-ping-bot
npm init -y
npm install discord.js dotenv
Install a currently compatible discord.js release. The commands above intentionally let npm select the current published version rather than hardcoding a version that may become outdated. Commit the generated lockfile for reproducible deployments, and check the library’s documented runtime requirement before deploying.
Replace the generated package.json with a minimal CommonJS example:
{
"name": "discord-ping-bot",
"version": "1.0.0",
"private": true,
"scripts": {
"register": "node register.js",
"start": "node index.js",
"check": "node --check index.js && node --check register.js"
},
"dependencies": {
"discord.js": "^14.0.0",
"dotenv": "^16.0.0"
}
}
Important: Treat this manifest as a structural example rather than a version lock: after npm install, keep the real dependency versions and lockfile generated by npm. If you copy the example manifest over the installed one, run an appropriate install to synchronize the lockfile before deploying. It is safer to retain npm’s installed versions and add only the scripts section.
Create a .env file for local development. Replace the placeholders with values from your own application and test guild:
DISCORD_TOKEN=replace_with_your_private_bot_token
CLIENT_ID=replace_with_your_application_id
GUILD_ID=replace_with_your_test_server_id
Next create .gitignore:
node_modules/
.env
*.log
Never commit .env. For production deployment, configure these variables through the host’s startup or secrets controls. The host may inject the environment directly, in which case a physical .env file is unnecessary. Do not echo the token to check its value: verify only whether the variable is defined.
Guild commands usually update much faster than global commands during development and let you test in one server. Create register.js as follows:
require("dotenv").config();
const { REST, Routes, SlashCommandBuilder } = require("discord.js");
const { DISCORD_TOKEN, CLIENT_ID, GUILD_ID } = process.env;
if (!DISCORD_TOKEN || !CLIENT_ID || !GUILD_ID) {
throw new Error("Missing DISCORD_TOKEN, CLIENT_ID, or GUILD_ID");
}
const commands = [
new SlashCommandBuilder()
.setName("ping")
.setDescription("Replies with Pong!")
.toJSON()
];
const rest = new REST({ version: "10" }).setToken(DISCORD_TOKEN);
async function register() {
await rest.put(
Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID),
{ body: commands }
);
console.log("Guild slash commands registered successfully");
}
register().catch(error => {
console.error("Command registration failed:", error.message);
process.exitCode = 1;
});
This code sends the command definition to Discord’s API. You run it when installing or updating command definitions; it does not need to run on every bot restart. The put call replaces the set of commands for the specified application and guild, so plan carefully before expanding a bot with multiple commands.
From the project directory, execute:
npm run register
If registration fails, inspect the application ID, guild ID, token, and installation permissions. A missing command in the client can also result from installing the wrong application or testing in the wrong guild. Avoid regenerating tokens repeatedly before understanding the actual error.
Create index.js. It connects using the bot token, listens for command interactions, and replies to the ping command:
require("dotenv").config();
const {
Client,
Events,
GatewayIntentBits
} = require("discord.js");
const token = process.env.DISCORD_TOKEN;
if (!token) throw new Error("DISCORD_TOKEN is missing");
const client = new Client({
intents: [GatewayIntentBits.Guilds]
});
client.once(Events.ClientReady, readyClient => {
console.log(`Ready as ${readyClient.user.tag}`);
});
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName !== "ping") return;
try {
await interaction.reply({ content: "Pong!" });
} catch (error) {
console.error("Ping command failed:", error.message);
}
});
client.on(Events.Error, error => {
console.error("Discord client error:", error.message);
});
client.login(token).catch(error => {
console.error("Discord login failed:", error.message);
process.exitCode = 1;
});
function shutdown(signal) {
console.log(`Received ${signal}; disconnecting bot`);
client.destroy();
}
process.once("SIGINT", () => shutdown("SIGINT"));
process.once("SIGTERM", () => shutdown("SIGTERM"));
The Guilds intent is enough for this interaction example; privileged message-content access is not required. In a larger bot you may need additional intents, but each should have a concrete purpose and be configured consistently between the Developer Portal and your code.
First check syntax:
npm run check
Then start the process:
npm start
Wait for the ready message, switch to your test Discord server, and type /ping. The bot should reply Pong!. If you see the command but there is no response, inspect logs for interaction failures, timing issues, or missing permissions. Slash-command interactions must be acknowledged promptly; longer operations should usually defer a reply before doing expensive work.
Dumping an entire interaction or configuration object into a production log can expose unnecessary metadata. Log an operation name and concise error description instead. Do not log the token or full environment, and avoid collecting user data that your bot does not need.
A hosted Discord bot needs a compatible Node.js version, installed dependencies, the correct startup file, and environment variables. Upload your source files and npm lockfile, but not local .env credentials. In the hosting dashboard, configure DISCORD_TOKEN, CLIENT_ID, and GUILD_ID where supported. For routine operation, the startup command is npm start, which runs index.js.
If your host rebuilds dependencies during deployment, follow its package-installation workflow. The command-registration step is separate: run it through a controlled deployment command or from your local development environment when you add or change slash commands. Running it repeatedly with every restart is unnecessary and can create confusing logs.
Read the console for the ready message, then use /ping in your test server. A process marked online is not proof that Discord authentication, registered commands, and replies all work. If the hosted bot does not connect, compare runtime versions, dependency installation, configuration names, and network rules against the successful local test.
On free hosting, review current resource limits, expiration behavior, restart policies, and maintenance rules. Free access is valuable for learning, but it is not the same as a contractual uptime guarantee. Keep a working local copy of the project and a record of your dependency versions.
Check that npm run register completed successfully, the application is installed into the same guild as GUILD_ID, and the correct bot application is being tested. Guild command propagation is usually suited to development, but you should still allow for normal API and client behavior.
Confirm you copied the bot token rather than the application ID or public key. Do not reveal the token in terminal screenshots or support messages. If it has been exposed, reset it in the Discord Developer Portal and replace it in the host’s environment.
The example uses only GatewayIntentBits.Guilds. If you extend the bot to read message content or track certain member events, the required intents and portal configuration may change. Do not enable every intent blindly; use only what the feature needs.
A local terminal is not a process supervisor. Use the hosting platform’s supported startup and lifecycle controls rather than relying on a development shell remaining open. If the application repeatedly crashes, investigate the first meaningful error before enabling restart loops.
Test one command and inspect its error handler. Confirm the bot has the necessary permissions in the target channel. If your handler performs work that takes time, learn how to defer interaction responses. Keep all debugging messages free of credentials.
As your bot grows, separate commands into modules, validate configuration at startup, and log failures with enough context to reproduce them. Document what each intent enables and why it is needed. Consider command-specific permissions and rate-limit handling before adding moderation or mass-message behavior. Test changes in a development server before installing them into your main community.
Continue with the official Discord Developer Documentation, discord.js guide, and Volyx Documentation. The portal and APIs change over time, so always compare tutorials with the current reference documentation.
Explore more in-depth articles with code, visuals, and practical walkthroughs.
Browse all articles ↗