Command
Handles loading and executing all bot commands efficiently.
Introduction
The Command Handler is responsible for discovering, validating, and registering all commands used by your bot.
In nsCore, commands are split into two types:
- Slash Commands (
/weather) - Message Commands (
ns.help)
Instead of manually importing every command, the command handler automatically scans directories, validates command structure, and stores them in collections.
This system allows you to add new commands without touching the client or handler logic.
What the Command Handler Does
The command handler performs four key tasks:
- Initializes command collections
- Recursively scans command folders
- Validates command interfaces
- Logs a clean summary for debugging
Command Collections
Before loading commands, the handler initializes two collections on the client:
client.slashCommands = new Collection()
client.messageCommands = new Collection()These collections are later used by:
interactionCreateeventmessageCreateevent
Using collections provides O(1) lookup time and clean separation between command types.
Folder Structure Requirement
The handler expects the following structure:
src/
├─ commands/
│ ├─ slashCommands/
│ │ └─ utility/
│ │ └─ ping.ts
│ └─ messageCommands/
│ └─ general/
│ └─ help.tsNested folders are supported.
Full Command Handler Code
import { Collection } from 'discord.js'
import { readdirSync, statSync } from 'fs'
import { join } from 'path'
import { Command } from '../interfaces/Command'
import { ExtendedClient } from '../interfaces/ExtendedClient'
import { logger } from '../utils/logger'
export const commandHandler = (client: ExtendedClient) => {
const scope = 'CommandLoader'
const stopTimer = logger.timer('Command loading')
client.slashCommands = new Collection()
client.messageCommands = new Collection()
let slashCount = 0
let messageCount = 0
let warnCount = 0
/* ───────── Slash Commands Loader ───────── */
const loadSlashCommands = (dir: string) => {
for (const file of readdirSync(dir)) {
const filePath = join(dir, file)
const stat = statSync(filePath)
if (stat.isDirectory()) {
loadSlashCommands(filePath)
continue
}
if (!file.endsWith('.js') && !file.endsWith('.ts')) continue
try {
const command: Command = require(filePath).default
if (!('executeSlash' in command)) {
logger.warn(scope, `Invalid slash command → ${filePath}`)
warnCount++
continue
}
client.slashCommands.set(command.name, command)
slashCount++
logger.success(scope, `Slash loaded: ${command.name}`)
} catch {
logger.error(scope, `Failed to load slash command → ${filePath}`)
warnCount++
}
}
}
/* ───────── Message Commands Loader ───────── */
const loadMessageCommands = (dir: string) => {
for (const file of readdirSync(dir)) {
const filePath = join(dir, file)
const stat = statSync(filePath)
if (stat.isDirectory()) {
loadMessageCommands(filePath)
continue
}
if (!file.endsWith('.js') && !file.endsWith('.ts')) continue
try {
const command: Command = require(filePath).default
if (!('executeMessage' in command)) {
logger.warn(scope, `Invalid message command → ${filePath}`)
warnCount++
continue
}
client.messageCommands.set(command.name, command)
messageCount++
logger.success(scope, `Message loaded: ${command.name}`)
} catch {
logger.error(scope, `Failed to load message command → ${filePath}`)
warnCount++
}
}
}
/* ───────── Load All Commands ───────── */
loadSlashCommands(join(__dirname, '../commands/slashCommands'))
loadMessageCommands(join(__dirname, '../commands/messageCommands'))
/* ───────── Summary ───────── */
logger.build(scope, 'Command loading summary')
logger.build(scope, '+------------+-------+')
logger.build(scope, `| Slash | ${slashCount.toString().padStart(5)} |`)
logger.build(scope, `| Message | ${messageCount.toString().padStart(5)} |`)
logger.build(scope, '+------------+-------+')
if (warnCount > 0) {
logger.warn(scope, `Warnings during load: ${warnCount}`)
}
stopTimer()
}Validation Logic Explained
Slash Commands
A file is treated as a slash command only if it exports:
executeSlash(interaction, client)If missing, the file is skipped safely.
Message Commands
A file is treated as a message command only if it exports:
executeMessage(message, args, client)Invalid command files never crash the bot. They are logged and skipped safely.
Why Recursive Loading?
Recursive loading allows you to:
- Organize commands by category
- Scale without changing the handler
- Keep folders clean and readable
Example:
slashCommands/
├─ utility/
├─ moderation/
├─ fun/How Events Use This Handler
interactionCreate→client.slashCommands.get(name)messageCreate→client.messageCommands.get(name)
The handler is the single source of truth for commands.
Last updated on