Skip to content

13. Avi.js for NestJS developers ​

Status: Runnable. A map of familiar NestJS ideas to their Avi.js equivalents.

Avi.js is inspired by NestJS, and the name is a nod to it: birds live in nests. NestJS showed how well modules, controllers, providers and dependency injection organize a Node.js backend, and Avi.js builds on those ideas. If you know NestJS, you already know most of Avi.js.

The two projects make different trade-offs. NestJS has a large ecosystem, many integrations and years of production use. Avi.js explores a smaller core: explicit dependency lists instead of reflection, standard decorators, and an optional decorator-free API. This page shows how the familiar concepts translate, so you can feel at home quickly.

The same feature in both ​

NestJS

ts
@Injectable()
export class UsersService {
  constructor(
    private readonly db: Database,
    @Inject(CONFIG) private readonly config: Config,
  ) {}
}

@Controller('users')
export class UsersController {
  constructor(private readonly users: UsersService) {}

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.users.findOne(id);
  }

  @Post()
  @HttpCode(201)
  create(@Body() dto: CreateUserDto) {
    return this.users.create(dto);
  }
}

Avi.js

ts
@Injectable()
export class UsersService {
  static inject = [Database, CONFIG] as const;
  constructor(
    private readonly db: Database,
    private readonly config: Config,
  ) {}
}

@Controller('/users')
export class UsersController {
  static inject = [UsersService] as const;
  constructor(private readonly users: UsersService) {}

  @Get('/:id', { schema: { params: UserId } })
  findOne(ctx: Context<{ params: { id: number } }>) {
    return this.users.findOne(ctx.input.params.id);
  }

  @Post('/', { schema: { body: CreateUser } })
  create(ctx: Context<{ body: CreateUserDto }>) {
    return reply(this.users.create(ctx.input.body), { status: 201 });
  }
}

The structure is the same: an injectable service, a controller with routes, and constructor injection. The differences are in how dependencies are declared and how a handler reads the request.

Concept map ​

In NestJSIn Avi.js
reflect-metadata, emitDecoratorMetadataDependencies are listed in static inject = [...] as const
experimentalDecorators: trueStandard TC39 decorators; leave experimentalDecorators off
@Inject(TOKEN) on a parameterThe token goes in static inject, in constructor order
String or symbol tokenscreateToken<Config>('CONFIG'), typed
@Param(), @Query(), @Body(), @Headers()ctx.params, ctx.query, ctx.json(), ctx.headers, or validated ctx.input
ValidationPipe with DTO classesA route schema from any Standard Schema library
ParseIntPipeConversion inside the schema (v.transform(Number), z.coerce.number())
@HttpCode(), @Header()reply(value, { status, headers })
@Res()Return a value, a stream or a web Response
MiddlewareMiddleware functions or classes, at app, module, controller or route level
Guards, @UseGuards()Guards returning true, @UseGuards()
InterceptorsMiddleware: await next() returns the handler's result
Exception filtersOne app-wide errorHandler
@Global() modulesImports are always explicit
forwardRef()Dependency cycles are reported at startup instead
onModuleInit, onModuleDestroyAsync factories, Symbol.asyncDispose or a dispose callback
Request-scoped providersscope: 'request' and the REQUEST token
Test.createTestingModule().overrideProvider()createTestClient(AppModule, { overrides })
@nestjs/swagger@avijs/openapi
Express or Fastify adapterNative node:http

Trying Avi.js with NestJS experience ​

These steps help if you want to rebuild a feature you know from NestJS in Avi.js:

  1. Use standard decorators. Leave experimentalDecorators and emitDecoratorMetadata off.
  2. List each class's dependencies in static inject. avi check confirms every list matches its constructor and reports any mismatch with file and line.
  3. Read request data from ctx. Start with ctx.params['id'], then add a route schema for typed ctx.input.
  4. Use reply() for status codes and headers.
  5. Turn cross-cutting logic into middleware or guards, and errors into one errorHandler.
  6. Start the app. Missing providers, export problems, cycles and route conflicts are reported at startup, with the module and the dependency chain.

The explicit API (defineController, defineModule) works alongside decorators, so you can use whichever style suits each part of an app.

Next ​

Back to the tutorial index.

Released under the MIT License.