Building Discord Bots Shouldn't Feel Like Wiring Everything Together — Meet Nexcord
If you've ever built a Discord bot with discord.js, you've probably ended up writing code that looks something like this:
- Register every command manually.
- Register every event manually.
- Create service instances yourself.
- Pass dependencies through constructors.
- Keep import lists up to date as the project grows.
None of these tasks are particularly difficult.
But after you've done them dozens of times, they start feeling more like maintenance than development.
After working on several Discord projects, I found myself wanting something closer to the experience of frameworks like NestJS or Angular, while still keeping everything I like about discord.js—its flexibility, performance, and ecosystem.
That's how Nexcord started.
What is Nexcord?
Nexcord is a decorator-driven, dependency-injection-powered framework built on top of discord.js.
Rather than spending time wiring your application together, you simply describe what each class is responsible for.
@Service()
export class GreetingService {
greet(name: string) {
return `Welcome ${name}!`;
}
}
@Listener()
export class MemberListener {
constructor(private readonly greeting: GreetingService) {}
@On('guildMemberAdd')
async execute(member: GuildMember) {
await member.send(
this.greeting.greet(member.user.username)
);
}
}
There's no need to manually create services.
No event registration to keep track of.
No growing bootstrap file that slowly turns into hundreds of lines.
Nexcord discovers your classes automatically, builds the dependency graph, and wires everything together for you.
Dependency Injection without the Boilerplate
Dependency Injection isn't a new idea.
What interested me was bringing the same experience to Discord bot development.
Every class decorated with one of Nexcord's decorators automatically becomes part of the dependency injection container.
@Service()
class QuoteService {}
@SlashCommand(...)
class QuoteCommand {
constructor(
private readonly quotes: QuoteService
) {}
}
If QuoteService depends on another service...
...and that service depends on another...
...Nexcord resolves the entire dependency graph automatically.
Each service is instantiated only once and shared across the application, so you don't have to think about lifecycle management.
Convention over Configuration
One thing that always bothered me in larger projects was maintaining registration files.
Every new command or listener meant adding another import somewhere.
Eventually those files became longer than the actual feature I was implementing.
Nexcord takes a different approach.
src/
├── commands/
├── events/
├── configs/
├── common/
When the application starts, these folders are scanned automatically.
Add a new command.
Run the bot.
That's it.
No extra registration step.
Guards for More Than Commands
Most Discord frameworks offer middleware, but it's usually tied to slash commands.
I wanted something that worked everywhere.
That's why Nexcord introduces Route Forwarding Guards.
A guard can target:
- individual listeners
- commands
- groups of handlers
- wildcard patterns
- regular expressions
- prefixes and suffixes
For example:
@Guard(
{ MemberListener },
"checkCooldown->MemberListener::onMessage"
)
If the guard returns false, execution stops before the listener ever runs.
The same mechanism works consistently for commands and events, so you don't need different middleware systems depending on what you're handling.
Shared Configuration that Understands Dependency Injection
Configuration is often more than static strings.
Sometimes generating a configuration value requires access to another service.
That's the reason I built @nexcord/config.
Instead of writing something like this during startup:
const prefix = prefixService.get();
you register the configuration once.
registerConfig(
"messages.welcome",
(prefix: PrefixService) =>
`Type ${prefix.get()}help`,
PrefixService
);
Nexcord automatically resolves PrefixService.
The value is computed once and becomes available everywhere through ConfigService.
For values that require the Discord client to already be connected, there's also registerConfigWait().
JSX for Discord Components
Anyone who's built more complex Discord UIs knows that component builders can become fairly verbose.
Buttons.
Embeds.
Select menus.
Modals.
Components V2.
All of them involve long builder chains that can quickly become difficult to read.
Nexcord includes @nexcord/tsx, a JSX runtime designed specifically for Discord components.
<button
id="confirm"
label="Confirm"
style="success"
action={ConfirmAction}
/>
This compiles directly into the appropriate discord.js builders.
There's no virtual DOM.
No rendering engine.
Just a cleaner, more expressive syntax for creating Discord components.
Built Around an Ecosystem
Nexcord is currently made up of three packages.
- @nexcord/core — decorators, dependency injection, automatic discovery, guards, and the framework runtime.
- @nexcord/config — dependency-injection-aware shared configuration.
- @nexcord/tsx — a JSX runtime for Discord components.
Each package can be used independently.
Together, they provide a more structured and enjoyable development experience.
Why I Built It
Nexcord wasn't created because Discord needed another library.
discord.js already does an excellent job.
The motivation was different.
I wanted to spend less time wiring projects together and more time building actual features.
Less repetitive setup.
Cleaner architecture.
Automatic discovery.
Dependency injection.
Better organization as projects grow.
Nexcord doesn't try to replace discord.js.
It simply aims to provide a higher-level development experience on top of it.
Current Status
Nexcord is currently in beta.
The core architecture is already in place, but there are still APIs I'm refining before the 1.0 release.
This stage is where feedback matters the most.
Many of the decisions that will shape the stable release are still open to improvement, so hearing how other developers approach Discord bot development is incredibly valuable.
If you're interested in building larger Discord bots with a more structured architecture, I'd genuinely love to hear your thoughts.
Thanks for reading.
Top comments (9)
Nice work on the framework! A quick question from a security perspective:
How does it handle permission validation? If developers must manually check permissions inside every single command, it almost always leads to Broken Access Control when scaled.
Does the framework enforce these at the router/middleware level (e.g., using a declarative config) before hitting the execution block?
Ensuring secure defaults is critical for production-ready tools.
Also, I noticed there's no ** SECURITY.md** in the repo yet. Establishing a simple vulnerability disclosure policy is a huge trust-builder for production adoption.
These are incredibly sharp security questions, and I completely agree with your point about Broken Access Control. Relying on developers to manually verify permissions within every execution block is like a time bomb for production applications. Here's how Nexcord solves this securely at a fundamental level:
Declarative Routing Protectors (Middle Layer Level) Nexcord solves this through a robust, highly optimized Protection System. Instead of manual checks, you can declare intersecting security concerns (such as permissions, bot filtering, or rate limiting) even before the execution block is reached. Thanks to Nexcord's compile-time parser, these protectors are resolved at boot using custom string routing patterns (supporting wildcards and regex). When an event or command is triggered, Nexcord verifies it at the router level with $O(1)$ performance. If the protector returns false, execution is immediately blocked. Secure defaults are a core philosophy here. In the future, I will add security and/or control packages that can be used chained instead of using if blocks within these protectors.
Regarding the SECURITY.md File: You are 100% right about the SECURITY.md file and the vulnerability reporting policy. Since Nexcord is currently in a very early v1-alpha phase, I've primarily focused on stabilizing the core architecture. However, building trust for production use is my top priority. To provide a clear channel for security notifications, I will be adding a suitable SECURITY.md file to the repository today. Thank you for your efforts in making Nexcord more ready for production. Your feedback is invaluable.
Really impressive work. I like that you're building on top of discord.js instead of abstracting it away, and the DI + automatic discovery approach looks like it could make larger bots much easier to maintain. But how does Nexcord handle modular plugins or feature packages? For example, can a module register its own commands, listeners, and services independently so it can be shared across multiple projects?
Thanks for the great question and positive feedback! You touched on a very critical point regarding enterprise-scale architecture. Here is how Nexcord handles this under the hood:
No Module Boilerplate (Folder-Based Architecture)
Nexcord currently does not use a NestJS-like @Module decorator, and honestly, we are planning to keep it that way. We believe that over-abstracting modules introduces unnecessary boilerplate. If the goal is to categorize features, a clean, folder-based structure combined with Nexcord’s automatic discovery mechanism handles this perfectly without the extra overhead.
Shared Packages & External Services (DI Compatibility)
Yes, absolutely! External services, commands, or listeners can independently be registered and shared. Since any external package will consume the same @nexcord/core instance from node_modules, Nexcord’s DI container will automatically discover and register those external services. As you might have noticed, built-in core services like ChannelService or ConfigService utilize this exact same injection pipeline seamlessly.
Great work! I like the idea with decorators and dependency injection. Registering every command manually is really annoying in big projects. The JSX syntax for buttons and embeds also looks very clean
Thank you for your comment. I'm glad if Nextcord makes your work easier.
How does Nexcord handle shard management compared to discord.js? I've struggled with this in my own projects and would love to swap ideas on this.
Hi. First of all, thank you for your attention. I haven't thought about this logic on Nexcord yet. I'm glad you mentioned it. I think I can resolve this problem you're experiencing with Nexcord within a few days. I'll be happy to share any developments with you.