Skip to main content
Moving an HTTP API from NestJS to nestrs means trading TypeScript decorators for Rust proc macros, class-based providers for Arc-wrapped structs, and the Node.js runtime for Axum and Tower. The architectural ideas carry over almost directly—modules, controllers, guards, interceptors, pipes, and filters all have first-class analogues—but the implementation language is Rust, so some patterns look different even when they serve the same purpose.

Concept mapping at a glance

Side-by-side code: modules and controllers

Side-by-side code: injectable providers

Side-by-side code: guards

Side-by-side code: interceptors

Side-by-side code: DTO validation

Side-by-side code: metadata and roles

Guards, interceptors, and filters — semantics

All three cross-cutting concerns work before or around the route handler, just like NestJS:
  • Guards (CanActivate) answer “may this request proceed?” and run before the handler. Returning Err(GuardError::Forbidden(...)) yields a 403 JSON response.
  • Interceptors (Interceptor) wrap the handler with next.run(req). Use them for logging, timing, and header injection.
  • Exception filters (ExceptionFilter) rewrite responses that carry an HttpException. Register globally with NestApplication::use_global_exception_filter or per-route with #[use_filters].
The pipeline order per route is fixed: global layers → guards → interceptors → filters → handler. See the middleware pipeline page for the complete contract.

DTOs and validation

NestJS uses class-validator and class-transformer for runtime validation. nestrs uses the #[dto] macro (which derives serde::Deserialize and validator::Validate) combined with ValidatedBody<T> or #[use_pipes(ValidationPipe)] on a handler.
Unknown JSON fields are rejected by default#[dto] emits #[serde(deny_unknown_fields)] automatically. Use #[dto(allow_unknown_fields)] to opt out when you intentionally accept forward-compatible clients.

Dependency injection differences

NestJS uses TypeScript constructor injection detected at runtime via reflect-metadata. nestrs constructs providers at compile time via the Injectable::construct method generated by #[injectable]. Each injectable receives a &ProviderRegistry and returns an Arc<Self>, pulling its own dependencies with registry.get::<DepType>(). There is no async constructor. Perform async I/O in on_module_init (called by the framework before listen starts serving traffic) or use ConfigurableModuleBuilder::for_root_async.

Configuration (ConfigModule → Rust patterns)

1

Parse environment variables at startup

Use std::env, dotenvy, or confy in main before calling NestFactory::create.
2

Pass options into a configurable module

Use ConfigurableModuleBuilder::for_root or for_root_async so injectables receive ModuleOptions<O, M> from the registry.
3

Use a typed settings injectable per bounded context

There is no global ConfigService token; create one #[injectable] settings struct per module if that suits your team’s style.

Testing

Packages and dependencies

What is not yet in nestrs

The following NestJS features have no nestrs equivalent at this time:
  • Interactive TypeScript playground — nestrs is a compiled Rust crate; there is no in-browser REPL.
  • TypeScript-first API teaching tools — the CLI (nestrs-scaffold) handles code generation but does not produce TypeScript stubs.
  • reflect-metadata-style decorator introspection — all metadata in nestrs is compile-time; there is no runtime annotation system for arbitrary fields.
  • GraphQL subscriptions over WebSockets — nestrs-graphql and nestrs-ws ship separately; parity is partial.

NestFactory API

Bootstrap methods including microservice transports

NestApplication API

Full reference for every builder method

Routing macros

Controller, routes, and HTTP method macros

DI macros

Module, injectable, and metadata macros