Stop Fighting with NestJS Modules: Meet Rikta

Published: (January 19, 2026 at 11:45 AM EST)
4 min read
Source: Dev.to

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:

  1. Create the service with @Injectable().
  2. Add it to a module’s providers array.
  3. Add it to the exports array if other modules need it.
  4. 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 imports arrays.
  • No exports arrays.
  • No providers arrays.

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:

  1. 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.
  2. 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.
  3. 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:

MetricRikta vs. NestJS
Startup time43 % faster
GET requests41 % faster
POST requests25 % faster
Route parameters46 % 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.

Back to Blog

Related posts

Read more »