DEV Community

Sahil Multani
Sahil Multani

Posted on

Facing the Ugly Truth About Backend Architecture

The Backend Development Lie We've All Bought Into

Let me paint you a familiar picture:

You start a new backend project. You grab NestJS, TypeORM, maybe some fancy decorators. Everything feels clean at first. Controllers! Services! Repositories! What could go wrong?

Fast forward three months:

  • Your "clean" controllers now contain 400 lines of business logic
  • Services call other services in a dependency web no one understands
  • That "simple" endpoint now has six side effects no one documented
  • New team take weeks to onboard because nothing is where they expect
  • You're afraid to refactor because you don't know what might break

This isn't your fault. Most backend frameworks and boilerplates are designed to make the first 10% easy while completely ignoring the remaining 90% of a project's lifecycle.

Why Does This Happen?

The problem lies in the design of many backend tools. Boilerplates that focus on making the initial developer experience smooth, with features like automatic dependency injection, decorators, or pre-built authentication and billing systems. While this gets you up and running fast, it often hides complexity behind “magic” that becomes a liability as your project grows. Implicit behaviors, like runtime reflection or auto-wired dependencies, make debugging and refactoring harder, and the lack of enforced structure lets bad habits creep in unnoticed.

That’s where VanguardNx crashes through the wall like it’s here to collect a debt. This isn’t another bootstrapped fantasy dressed in TypeScript and empty patterns. It’s a backend boilerplate ripped straight from the trenches, built by people who’ve been screwed by their own abstractions too many times to believe the hype anymore. It doesn’t whisper “best practices,” it shouts don’t be stupid. Every line exists to drag your codebase out of chaos and slap your hand when you reach for the sugar. No silent bindings. No reflective black magic. Just a cold, ruthless layout that makes you earn every decision and leaves no place for bad code to hide. If you want to survive the next 18 months of backend hell, Keep reading.

Enter VanguardNx: An Architectural Intervention

VanguardNx isn't another magical framework that falls apart when you look at it sideways. It's a brutally honest approach to backend architecture that:

  1. Acknowledges most backend code becomes unmaintainable
  2. Provides actual structure instead of just pretty patterns
  3. Forces explicitness over magic
  4. Makes bad architecture decisions painfully obvious

VanguardNx: Built From the Pain

VanguardNx doesn’t pretend to be perfect. It doesn’t enforce everything. It doesn't protect you from bad decisions. But it does give you structure, clarity, and a way out of chaos if you're willing to follow it.

Here’s what makes it different—factually, not aspirationally:

1. No Hidden Wiring

Every controller, handler, and profile is manually registered. No reflection. No discovery magic. If it runs, you wired it.

That means:

  • You can grep your code and find every flow.
  • Stack traces make sense.
  • Refactoring doesn’t trigger existential dread.

2. Feature Isolation That Actually Works

src/
└── features/
    └── user-management/
        ├── commands/
        ├── queries/
        ├── domain/
        ├── models/ # Request/response types (not DB models!)
        └── user-management.controller.ts
Enter fullscreen mode Exit fullscreen mode

Each feature is a vertical slice with everything it needs. No more:

  • Hunting through 10 directories to find related code
  • Accidental coupling between unrelated features
  • "Shared" folders that become dumping grounds

In VanguardNx, models/ contains pure types and classes used for request/response payloads and API contracts not DB schemas, DTOs, or ORM entities.

All persistence logic (db connectivity, entities, migration scripts and seed) lives under infrastructure/persistence a dedicated infra layer, keeping your business logic and transport models clean and separated.

3. CQRS is a First-Class Citizen (But Not Forced)

You get a full command/query split with folder segregation, handler scaffolding, and mediator-driven flows. VanguardNx scaffolds the right way by default, but you can break the rules if you want (and you’ll feel it).

Here's how it looks like:

// get-user.query.ts
export class GetUserQuery extends QueryBase { // Always extend QueryBase/CommandBase for consistency
  @AutoMap()
  public userId: string;
}

// get-user.handler.ts
@QueryHandlerStrict(GetUserQuery) // strict custom decorator
export class GetUserHandler implements IQueryHandler<GetUserQuery, User> {
  constructor(
    @Inject(USER_REPO) private readonly repository: IUserRepo // always inject interface, never implementation
  ) {}

  async execute(query: GetUserQuery): Promise<User> {
    return this.repository.getAsync(query.userId); // repo logic is fully isolated in infra
  }
}
Enter fullscreen mode Exit fullscreen mode

Most CQRS implementations feel heavy. VanguardNx keeps it strict but opinionated:

  • Commands and queries are first-class citizens
  • Handlers are colocated with their operations
  • The mediator pattern ensures consistent execution
  • You can trace every execution path (explicit wiring!)

Notes:

  • Query Object (GetUserQuery in this case) must extend QueryBase or CommandBase: This is enforced by VanguardNx's strict CQRS decorators (@QueryHandlerStrict, @CommandHandlerStrict). If you forget to extend the base class, the executor bus (CqrsMediator aka mediator) will throw during registration—this keeps your codebase consistent and prevents silent miswiring.
  • Why?
    • Ensures every command/query is uniquely typed and traceable
    • Guarantees all handlers are compatible with the strict CQRS mediator, making debugging and auditing straightforward
  • Handlers inject only interfaces (e.g., IUserRepo), ensuring pure application logic never knows about infrastructure details.
  • Repositories are implemented only in the infrastructure layer, application code can never leak implementation details.
  • Mediators wire the flow, keeping registration explicit and discoverable.

For real examples, check the repo’s /features and /infrastructure directories.

4. Controllers That Stay Skinny (Or Scream at You)

@Controller({ path: '/users', version: '1' })
export class UserController {
  constructor(
    private readonly mediator: CqrsMediator, // Central dispatcher for command and queries !
    private readonly mapper: Mapper
  ) {} 

  @Get(':id')
  async getUser(@Param() request: GetUserRequest): Promise<GetUserResponse> {
    const query = this.mapper.map(request, GetUserRequest, GetUserQuery);
    const result = await this.mediator.execute(query);
    return this.mapper.map(result, User, GetUserResponse);
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice what's missing? Business logic. Validation. Database calls. The controller does exactly one thing: coordinates between HTTP and your domain layer.

Domain Logic Is Respected - Only If You Respect It, The architecture expects you to keep domain logic inside commands/queries. Controllers aren’t enforced as pure, but they’re scaffolded that way. If you jam business logic into a controller, that’s on you—but you’ll know it immediately.

5. Mapping That Doesn't Make You Want to Cry

// user-management.profile.ts
createMap(mapper, User, GetUserResponse, 
  forMember(dest => dest.fullName, 
    mapFrom(src => `${src.firstName} ${src.lastName}`)
);
Enter fullscreen mode Exit fullscreen mode

AutoMapper handles the boring stuff while staying completely type-safe. No more manual property assignments or @Expose() decorators that break your API contracts.

6. Feature Isolation is Real

Every feature is vertically sliced: domain, DTOs, handlers, helpers, and mappers all live in their separate folders. Shared logic is in a mirrored shared/ directory with the same structure.

There are no shortcuts, no ambient state, and no imports from infra into core logic.

7. Nothing is Implicit

Modules are registered manually. Options are passed explicitly. ConfigService usage is confined to modules. No magic globals. No mysterious lifecycle events.

Note:
In VanguardNx, everything is registered using central barrel files and module arrays—there’s minimal "decorator magic" for these. CQRS handlers, however, still use decorators for registration. This explicit registration increases boilerplate but improves traceability, predictability, and refactoring safety. In contrast, standard NestJS often hides registration behind decorators, making DI more implicit and flexible but sometimes harder to trace.

VanguardNx optimizes for:

  • Long-term maintainability
  • Team scalability
  • Production readiness

Compared to the Alternatives

Many Starter Kits

  • Claim "clean architecture" but leak everywhere.
  • Lack central registration—everything runs, no one knows how.
  • Encourage shortcuts in the name of "developer experience."

VanguardNx avoids all of that by doing less. It gives you:

  • Structure without noise.
  • Patterns with enforcement.
  • Rules without runtime tricks.

The Developer Experience

What You'll Love

  1. Traceability: Every request flow is explicit and grep-able
  2. Consistency: The same patterns work across your entire codebase
  3. Type Safety: AutoMapper + TypeScript = fewer runtime surprises
  4. Separation of Concerns: Business logic stays where it belongs

What You'll Hate (At First)

  1. More Files: Each operation gets its own class/files
  2. Less Magic: You have to wire things up manually
  3. Strictness: The architecture resists quick-and-dirty solutions
  4. Learning Curve: It requires understanding the patterns upfront
  5. Automapper Everywhere: Every DTO, domain object, entity, and model uses Automapper.
  6. Decorator Heavy: You will find Decorators on every single file you explore.

The Hard Truths About VanguardNx

What It Solves

  1. Architecture Decay: The structure prevents the gradual breakdown of your codebase
  2. Cognitive Load: Features are isolated so you only need to think about one at a time
  3. Refactoring Fear: Explicit dependencies mean changes are predictable
  4. New Developer Onboarding: The structure is consistent across the entire project

What It Doesn't Solve

  1. Bad Developers: You can still write terrible code within this structure
  2. Team Buy-In: If your team won't follow the patterns, it won't help
  3. Initial Setup: You'll write more boilerplate upfront (but save time later)
  4. Infrastructure: It's not a full-stack solution - just the architectural core

What It Doesn’t Do

  • It doesn’t enforce security. You build your own.
  • It doesn’t block you from bad practices. You can still inject repositories in a controller.
  • It doesn’t give you a complete CRUD or full-stack setup. It’s the skeleton, not the meat.

This is not a plug-and-play solution. It’s a foundation.

Honest Pros and Cons

Pros:

  • Predictable, traceable code flow
  • Vertical feature isolation
  • Simple but effective CQRS support
  • AutoMapper removes boilerplate transforms

Cons:

  • No guardrails—you can still mess up
  • You write more code upfront
  • No out-of-the-box auth, security, or advanced infra

Your Choice: Pay Now or Pay Later

Every backend reaches a crossroads:

  • Option A: Quick wins today, painful rewrites later
  • Option B: Slightly more structure now, smooth sailing forever

VanguardNx is for developers who choose Option B. Who understand that real productivity isn't about typing speed—it's about creating systems that don't collapse under their own weight.

The best part? It's not theoretical. This is battle-tested architecture distilled from:

  • 4 years of Node.js in production
  • Countless "how did this break?" moments
  • Painful lessons learned across industries

Try It Before You Need It

VanguardNx won’t save you from bad habits. But it will expose them. And if your team values explicitness, composability, and the long-term health of your codebase, it’s worth a look.

Explore VanguardNx on GitHub – because your future self will thank you when:

  • New features slot is clean
  • Refactors don't cause panic
  • Onboarding takes hours, not weeks
  • Production issues get traced in minutes

The choice is yours: keep fighting your architecture, or finally have it work for you.

Top comments (3)

Collapse
 
emil profile image
Emil

It’s all about experience. Not the framework. Embrace one and master it.

Collapse
 
xwero profile image
david duymelinck

If you mess up the code with a framework it is your own fault. A boilerplate is something that sets you up when you start, but there is no reason to change it later. So it don't think they are not valid reasons to go full DDD from the start.

I never used NestJS, but i took a look at the documentation and I see it is cause of much of your frustration.

Having a predefined file structure becomes a problem over time. I used, and stil use, many frameworks with MVC as the base of their file structure. And overtime they added more DDD features. But if I want to go from their file structure to the file structure I pick for the project it requires extra configuration.
There are multiple DDD file structures based on how the people that work on the project interact with each other and how far you want keep the contracts from the implementation.
So if a framework provides no predefined file structure it is a win in my book.

Your intentions with the framework are good. And I applaud the work you did. I think it will be too opinionated for a lot of people. If you look at Express, being unopinionated is one of its selling points.

Collapse
 
api-ace profile image
Sahil Multani

appreciate the feedback. you’re absolutely right that poor structure is ultimately on the team, not the framework. but that’s the crux, most teams, especially under delivery pressure, default to the path of least resistance. frameworks that lean on implicit behaviours and predefined structures make it easy to create messes quickly.

This template isn’t for everyone and yeah it’s definitely opinionated. and that’s intentional. It’s not trying to be Express. It’s trying to give teams who’ve already felt the pain of “just figure it out later” a foundation that forces decisions early, keeps boundaries clear, and exposes entropy before it compounds.

you’re also spot-on that DDD structure varies depending on team dynamics and contract boundaries. VanguardNx favors one specific flavour and that is vertical slicing, strict segregation, explicit wiring. not because it’s universally best, but because it works reliably in high-change, multi-team backends.