DEV Community

Cover image for Stop Fighting with NestJS Modules: Meet Rikta
Riccardo Tartaglia
Riccardo Tartaglia

Posted on

Stop Fighting with NestJS Modules: Meet Rikta

You love NestJS. The decorators feel elegant. The structure keeps your codebase clean. TypeScript integration works well.

Then you add a new service.

You import it in one module. Export it in another. Inject it somewhere else. Suddenly, you get this error:

Nest can't resolve dependencies of the UserService (?). 
Please make sure that the argument at index [0] 
is available in the current context.
Enter fullscreen mode Exit fullscreen mode

Sound familiar?

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 in every place that uses it

Here's what a typical NestJS module looks like:

// user.module.ts
@Module({
  imports: [
    DatabaseModule,
    ConfigModule,
    AuthModule,
    LoggingModule,
  ],
  controllers: [UserController],
  providers: [
    UserService,
    UserRepository,
    UserValidator,
    UserMapper,
  ],
  exports: [UserService, UserRepository],
})
export class UserModule {}
Enter fullscreen mode Exit fullscreen mode

This is boilerplate. Pure configuration overhead.

And it grows. Fast.

A medium-sized app has 20+ modules. Each module has 5-10 providers. You spend more time managing arrays than writing business logic.

The worst part? A typo in any array breaks your entire dependency graph. Debugging takes 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.

Decorate your class. Everything works.

// user.service.ts
import { Injectable } from '@riktajs/core';

@Injectable()
export class UserService {
  getUsers() {
    return ['Alice', 'Bob'];
  }
}
Enter fullscreen mode Exit fullscreen mode
// 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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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, Rikta scans your 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 your app. No need to export or import.

3. Dependency Resolution

Rikta reads TypeScript metadata (via reflect-metadata) to understand what each class needs. It creates instances in the correct order and injects them automatically.

The framework detects circular dependencies during initialization and throws a clear error:

Error: Circular dependency detected: 
UserService -> AuthService -> UserService
Enter fullscreen mode Exit fullscreen mode

No cryptic stack traces. No runtime surprises.

A 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 {}
Enter fullscreen mode Exit fullscreen mode

Creating the same feature in Rikta:

@Injectable()
export class PaymentService {
  @Autowired()
  private configService!: ConfigService;
}
Enter fullscreen mode Exit fullscreen mode

Done.

Performance Benefits

Rikta uses Fastify as its HTTP layer. Fastify handles up to 30,000 requests per second in benchmarks.

Benchmark results from the official repository:

Metric Rikta vs NestJS
Startup time 43% faster
GET requests 41% faster
POST requests 25% faster
Route parameters 46% faster

Rikta adds minimal overhead (2-5%) over vanilla Fastify. You get dependency injection and decorators without sacrificing speed.

Built-in Zod Validation

NestJS uses class-validator decorators for validation. This works, but you define types twice: once for TypeScript, once for validation.

Rikta integrates Zod natively. Define a schema once. Get 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 };
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
  • 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
Enter fullscreen mode Exit fullscreen mode

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

Rikta is MIT licensed and open source.


Have you tried zero-config frameworks? What do you think about the module vs. autowiring tradeoff? Share your experience in the comments.

Top comments (0)