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
| NestJS | nestrs | Notes |
|---|---|---|
@Module(...) | #[module(controllers = [...], providers = [...], imports = [...], exports = [...])] | Applied to a plain struct |
@Controller('prefix') | #[controller(prefix = "/prefix", version = "v1")] | Applied to a plain struct |
@Injectable() | #[injectable] | Applied to a struct; resolved as Arc<T> |
@Get(), @Post(), … | #[get("path")], #[post("path")], … | Inside a #[routes(state = S)] impl block |
@Body(), @Param(), @Query() | #[param::body], #[param::param], #[param::query] | With #[use_pipes(ValidationPipe)] for validated DTOs |
@UseGuards(G) | #[use_guards(G)] | G implements CanActivate |
@UseInterceptors(I) | #[use_interceptors(I)] | I implements Interceptor |
@UsePipes(ValidationPipe) | #[use_pipes(ValidationPipe)] | Validation is opt-in per route |
@UseFilters(F) | #[use_filters(F)] | F implements ExceptionFilter |
@SetMetadata('k', 'v') | #[set_metadata("k", "v")] | Stored in MetadataRegistry |
@Roles('admin') | #[roles("admin")] | Shorthand metadata for role guards |
@HttpCode(201) | #[http_code(201)] | Sets the response status code |
@Header('k', 'v') | #[response_header("k", "v")] | Adds a response header |
@Redirect(url) | #[redirect("url")] | Redirects the request |
NestFactory.create(AppModule) | NestFactory::create::<AppModule>() | Returns a NestApplication builder |
app.setGlobalPrefix('api') | .set_global_prefix("api") | Builder method on NestApplication |
app.enableVersioning(...) | .enable_uri_versioning("v") / .enable_header_versioning(...) | Multiple versioning strategies |
app.listen(3000) | .listen(3000).await | Async; also listen_graceful and listen_with_shutdown |
Side-by-side code: modules and controllers
- NestJS
- nestrs
Side-by-side code: injectable providers
- NestJS
- nestrs
Side-by-side code: guards
- NestJS
- nestrs
Side-by-side code: interceptors
- NestJS
- nestrs
Side-by-side code: DTO validation
- NestJS
- nestrs
Side-by-side code: metadata and roles
- NestJS
- nestrs
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. ReturningErr(GuardError::Forbidden(...))yields a 403 JSON response. - Interceptors (
Interceptor) wrap the handler withnext.run(req). Use them for logging, timing, and header injection. - Exception filters (
ExceptionFilter) rewrite responses that carry anHttpException. Register globally withNestApplication::use_global_exception_filteror per-route with#[use_filters].
DTOs and validation
NestJS usesclass-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 viareflect-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)
Pass options into a configurable module
Use
ConfigurableModuleBuilder::for_root or for_root_async so injectables receive ModuleOptions<O, M> from the registry.Testing
| NestJS (Jest / Supertest) | nestrs |
|---|---|
TestingModule | Build NestFactory::create::<M>().into_router() and use tower::ServiceExt::oneshot in tests |
| Isolated metadata / routes | Enable the test-hooks feature and use registry clear helpers between tests |
app.close() | listen_with_shutdown drains in-flight requests on signal |
Packages and dependencies
| NestJS / npm | Rust / Cargo |
|---|---|
package.json + lockfile | Cargo.toml + Cargo.lock (commit lock for binaries) |
@nestjs/platform-express | The nestrs crate (Axum is the only HTTP platform) |
@nestjs/swagger | nestrs-openapi crate + features = ["openapi"] |
@nestjs/microservices | nestrs-microservices crate + transport feature flags |
peerDependencies | Cargo workspace [patch] / version alignment in root Cargo.toml |
What is not yet in nestrs
What to read next
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