Stop Fighting with NestJS Modules: Meet Rikta
Source: Dev.to
The Module Problem
NestJS modules solve a real problem: dependency boundaries. They prevent chaos in large codebases, but they come at a cost.
Every new service requires you to:
- Create the service with
@Injectable(). - Add it to a module’s
providersarray. - Add it to the
exportsarray if other modules need it. - Import the module everywhere it’s used.
A typical NestJS module looks like this:
// user.module.ts
@Module({
imports: [
DatabaseModule,
ConfigModule,
AuthModule,
LoggingModule,
],
controllers: [UserController],
providers: [
UserService,
UserRepository,
UserValidator,
UserMapper,
],
exports: [UserService, UserRepository],
})
export class UserModule {}
That’s a lot of boilerplate—pure configuration overhead that grows quickly.
A medium‑sized app can have 20+ modules, each with 5‑10 providers. You end up spending more time managing arrays than writing business logic.
The worst part? A typo in any array breaks the entire dependency graph, and debugging can take hours.
What If You Didn’t Need Modules?
Rikta is a new TypeScript framework built on Fastify. It keeps the parts developers love about NestJS (decorators, DI, structure) and removes the module configuration entirely.
- No
importsarrays. - No
exportsarrays. - No
providersarrays.
Just decorate your class and everything works.
// user.service.ts
import { Injectable } from '@riktajs/core';
@Injectable()
export class UserService {
getUsers() {
return ['Alice', 'Bob'];
}
}
// user.controller.ts
import { Controller, Get, Autowired } from '@riktajs/core';
import { UserService } from './user.service';
@Controller('/users')
export class UserController {
@Autowired()
private userService!: UserService;
@Get()
getUsers() {
return this.userService.getUsers();
}
}
That’s it—no module file, no registration. Rikta scans your code at startup and resolves dependencies automatically.
How Zero‑Config Autowiring Works
Rikta uses three mechanisms:
- Automatic Discovery – At startup it scans the project for classes decorated with
@Controller()or@Injectable(). It builds a dependency graph from constructor parameters and@Autowired()decorators. - Global Provider Registry – All providers live in a single registry. Any injectable class is available anywhere in the app; there’s no need to export or import.
- Dependency Resolution – Rikta reads TypeScript metadata (via
reflect-metadata) to understand what each class needs, creates instances in the correct order, and injects them automatically.
Circular dependencies are detected during initialization and produce a clear error:
Error: Circular dependency detected:
UserService -> AuthService -> UserService
No cryptic stack traces, no runtime surprises.
Side‑by‑Side Comparison
Creating a new feature in NestJS
// 1. Create the service
@Injectable()
export class PaymentService {
constructor(private configService: ConfigService) {}
}
// 2. Create the module
@Module({
imports: [ConfigModule],
providers: [PaymentService],
exports: [PaymentService],
})
export class PaymentModule {}
// 3. Import in app.module.ts
@Module({
imports: [PaymentModule, /* ... */],
})
export class AppModule {}
// 4. Import in any module that needs it
@Module({
imports: [PaymentModule],
providers: [OrderService],
})
export class OrderModule {}
Creating the same feature in Rikta
@Injectable()
export class PaymentService {
@Autowired()
private configService!: ConfigService;
}
Done.
Performance Benefits
Rikta uses Fastify as its HTTP layer. Fastify handles up to 30,000 requests per second in benchmarks.
Official benchmark results:
| Metric | Rikta vs. NestJS |
|---|---|
| Startup time | 43 % faster |
| GET requests | 41 % faster |
| POST requests | 25 % faster |
| Route parameters | 46 % faster |
Rikta adds only 2‑5 % overhead over vanilla Fastify, giving you DI and decorators without sacrificing speed.
Built‑in Zod Validation
NestJS relies on class-validator decorators, which means you define types twice: once for TypeScript, once for validation.
Rikta integrates Zod natively. Define a schema once and get both validation and TypeScript types automatically.
import { Controller, Post, Body, z } from '@riktajs/core';
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2),
age: z.number().optional(),
});
@Controller('/users')
export class UserController {
@Post()
create(@Body(CreateUserSchema) user: z.infer<typeof CreateUserSchema>) {
// 'user' is validated AND typed
// Invalid requests return 400 automatically
return { created: user };
}
}
No duplicate type definitions, no manual error handling for validation failures.
When to Use Rikta
Rikta works best for:
- Startups and MVPs where development speed matters.
- Small to medium teams (1‑15 developers) that want a clean, zero‑config DI system.
If you love NestJS’s ergonomics but are tired of the module boilerplate, give Rikta a try.
- Microservices – where each service stays focused.
- Developers who know NestJS and want less boilerplate.
Consider NestJS if:
- You have a large team that needs strict module boundaries.
- You require the extensive NestJS ecosystem (specific adapters, plugins).
- Your organization mandates explicit dependency documentation.
Getting Started
Create a new project in seconds:
npx @riktajs/cli new my-app
cd my-app
npm run dev
Your API runs at http://localhost:3000.
The CLI generates a complete project with:
- TypeScript configuration optimized for Rikta
- Example controller with REST endpoints
- Example service with dependency injection
- Hot‑reload development server
Resources
- Documentation: (link)
- GitHub: (link)
- NPM: (link)
Rikta is MIT licensed and open source.
Have you tried zero‑config frameworks? What do you think about the module vs. autowiring trade‑off? Share your experience in the comments.