Welcome to Part 2 of the "NestJS v12.0 vs. Ditsmod v3.0" series! If you missed Part 1, we covered the foundational differences in how these two frameworks handle Modules, Dependency Injection (DI) hierarchies, and Application Extensions.
Right now, both frameworks are in active development for their next major leaps and can be downloaded from npm using the next tag. This is an exciting time for Node.js developers because the ecosystem is undergoing a massive architectural shift.
In this article, we will dive into the code. We'll explore how NestJS v12.0 is modernizing its stack with native ESM and Standard Schema support, and compare that with Ditsmod v3.0’s highly strict, DI-integrated approach to parameter validation and route interception.
1. The Native ESM Era Has Arrived
For years, developers have been trapped in the CommonJS (CJS) vs ECMAScript Modules (ESM) limbo.
NestJS v12.0 is finally slamming the door on that friction. The upcoming v12 release brings a massive full migration to Native ESM across all official packages. Thanks to Node.js's new require(esm) support, the transition is smoother than ever.
But the NestJS team didn't stop there. They are completely modernizing the default toolchain:
- Vitest replaces Jest.
- oxlint replaces ESLint.
- Rspack replaces Webpack.
Ditsmod v3.0, on the other hand, was built with modern ECMAScript standards and top-level await in mind from the beginning. Ditsmod applications natively leverage ESM for bootstrapping (like await RestApplication.create(AppModule)). When it comes to testing, Ditsmod easily integrates with Vitest, requiring only that reflect-metadata/lite is loaded early in the test setup.
Both frameworks now ensure that your entire stack—from frontend to backend in a monorepo—can finally run seamlessly on a single, native module language without redundant compilation layers.
2. Parameter Validation: Standard Schema vs. Factory Providers
When a request comes in, you need to validate the payload and extract parameters. Here, the two frameworks take wildly different philosophies.
NestJS v12.0: Standard Schema Integration
NestJS 12 introduces a highly anticipated feature: native Standard Schema support. You no longer need to rely heavily on the decorator-heavy class-validator and class-transformer packages.
Instead, NestJS v12 allows you to pass schemas from modern validation libraries like Zod, Valibot, or ArkType directly into your route decorators:
import { Controller, Post, Body } from '@nestjs/common';
import { z } from 'zod';
const createUserSchema = z.object({
email: z.email(),
age: z.number().min(18),
});
@Controller('users')
export class UsersController {
@Post()
create(@Body(createUserSchema) body: z.infer<typeof createUserSchema>) {
return `User ${body.email} created!`;
}
}
This reduces metadata overhead and gives developers immediate access to the rich type inference of modern schema libraries.
Ditsmod v3.0: Parameter Validation via DI Pipes
Ditsmod does not use separate conceptual entities like "Pipes" for validation. Instead, it handles parameter validation and transformation strictly through its Dependency Injection System, using custom FactoryProvider logic.
When you inject a parameter in Ditsmod, you can pass contextual data directly to the injection token via the second argument of @inject(Dependency, 'input-data'). The DI injector skips caching for this dependency and executes the factory method afresh for each injection site, passing that second argument to the @input decorator inside the factory.
Here is how you build an equivalent Zod validation pipe for the request body in Ditsmod:
import { injectable, factoryMethod, inject, ctx, input, AnyObj } from '@ditsmod/core';
import { controller, route } from '@ditsmod/rest';
import { HTTP_BODY } from '@ditsmod/body-parser';
import { z } from 'zod';
const createUserSchema = z.object({
email: z.email(),
age: z.number().min(18),
});
// 1. Create a generic Zod Factory Provider
@injectable()
export class ZodBodyPipe {
@factoryMethod()
transform(@ctx(HTTP_BODY) body: AnyObj, @input schema: z.ZodSchema) {
// Assuming the body is parsed by @ditsmod/body-parser and attached to body
const result = schema.safeParse(body);
if (!result.success) {
// In a real app, you would throw a specific HTTP error (e.g., BadRequestError)
throw new Error(`Validation failed: ${result.error.message}`);
}
return result.data;
}
}
// 2. Inject it into the Controller Method
@controller()
export class UsersController {
@route('POST', 'users')
create(
// We pass the Zod schema as the second argument to @inject.
// Ditsmod dynamically feeds it into the @input parameter of ZodBodyPipe!
@inject(ZodBodyPipe, createUserSchema) body: z.infer<typeof createUserSchema>
) {
return `User ${body.email} created!`;
}
}
The difference:
In the NestJS ecosystem, native Standard Schema support is a major framework-level feature that developers have been waiting for the core team to ship in v12.
Ditsmod, on the other hand, shifts this power directly to the developer right now. Because Ditsmod handles validation entirely through its standard Dependency Injection system via FactoryProvider mechanics, you don't need to wait for official framework integrations or special decorators to support new schema libraries. You have the flexibility to easily write these custom, deeply integrated pipes yourself using the core DI tools already provided.
3. Interceptors and Request Execution
NestJS Interceptors
In NestJS, the lifecycle is broadly: Middleware -> Guards -> Interceptors -> Pipes -> Controllers.
You bind an interceptor using the @UseInterceptors() decorator. Inside, you manipulate the RxJS stream using next.handle().
Ditsmod's Strict RequestDispatcher Pipeline
Ditsmod (@ditsmod/rest) has a strict, highly optimized, non-RxJS interceptor chain managed by the RequestDispatcherExtension:
-
HttpFrontend: Parses path/query parameters intoRequestContextand formats the final response. -
GuardedInterceptor: ExecutesCanActivateguards. (If a guard fails, execution stops and throws a CustomError). - Custom `HTTP_INTERCEPTORS`: Your custom logic.
-
HttpBackend: Invokes the controller method.
Adding Route-Level Interceptors in Ditsmod:
Instead of a separate decorator, Ditsmod allows you to pass an array of interceptors directly into the 4th argument of the @route() decorator.
import { controller, route, HttpInterceptor, HttpHandler, RequestContext } from '@ditsmod/rest';
export class LoggingInterceptor implements HttpInterceptor {
async intercept(next: HttpHandler, ctx: RequestContext) {
console.log(`[${ctx.rawReq.method}] ${ctx.rawReq.url}`);
return next.handle();
}
}
@controller()
export class DashboardController {
// Signature: @route(method, path, guards, interceptors)
@route('GET', 'dashboard/stats', [], [LoggingInterceptor])
getStats() {
return { visitors: 1024 };
}
}
During bootstrap (stage1), the Ditsmod InterceptorExtension automatically extracts these interceptors and registers them into the HTTP_INTERCEPTORS multi-provider array at the providersPerRou or providersPerReq level.
(Crucial Note: If you need to trace or wrap the ENTIRE request lifecycle—including Guard execution—in Ditsmod, you don't use standard interceptors. You override the entire RequestDispatcher at the providersPerApp level).
Conclusion
Both NestJS v12.0 and Ditsmod v3.0 (available now under the next npm tags) represent the bleeding edge of Node.js enterprise frameworks.
-
NestJS 12.0 brings a massive quality-of-life improvement with Native ESM, Rspack/Vitest tooling, and Standard Schema route validation, shedding years of CommonJS and
class-validatorlegacy overhead. - Ditsmod 3.0 continues to refine its radically explicit architecture, proving that you don't need magic—you just need a meticulously designed Dependency Injection hierarchy. By handling everything from parameter validation (via Factory Providers) to route interception within the boundaries of DI, Ditsmod offers unmatched predictability.
Which architectural philosophy do you prefer? The familiar, schema-friendly ecosystem of NestJS, or the strict, DI-centric predictability of Ditsmod?
Let me know in the comments below! If you haven't yet, run npm i @nestjs/core@next or npm i @ditsmod/core@next and try them out for yourself.
Top comments (0)