truxie
v0.1.1
Published
NestJS-like structural backend library with DI, modules, guards, interceptors, pipes, and exception filters
Maintainers
Readme
truxie
A NestJS-shaped backend core — modules, DI, controllers, guards, interceptors, pipes, filters — that runs inside whatever HTTP framework you already have. There is no truxie.listen(): an adapter hands the framework's request to the application, and the application answers it.
npm install truxie reflect-metadataNode 20 or newer — the SDK builds on globalThis.crypto, File and
Headers.getSetCookie(), none of which exist on Node 18.
import 'reflect-metadata'
import {Module, Controller, Get, Param, Injectable, Inject, TruxieFactory} from 'truxie'
@Injectable()
class UserService {
find(id: string) { return {id} }
}
@Controller('users')
@Inject(UserService) // constructor deps, in order
class UserController {
constructor(private readonly users: UserService) {}
@Get('/:id')
one(@Param('id') id: string) { return this.users.find(id) }
}
@Module({controllers: [UserController], providers: [UserService]})
class AppModule {}
export const app = await TruxieFactory.create(AppModule)Then mount it — @truxie/express, @truxie/next, @truxie/nitro, @truxie/sveltekit — or drive it yourself with matchRoute + handleRequest.
@Inject on the class, not just the parameter
design:paramtypes is emitted by tsc and dropped by esbuild/swc, which is what most projects actually build with. So the dependency list is declared explicitly:
@Injectable()
@Inject(UserService, LOGGER_TOKEN) // class form: full list, constructor order
class Thing {
constructor(private users: UserService, @Inject(OTHER) private other: Other) {}
}Both forms work, and the parameter form wins for the parameter it names.
The request pipeline
One pipeline, in this order, for every adapter:
middlewares → guards → pipes → interceptors → handler → filtersEach layer can be declared globally (on TruxieFactory.create) and per route (@UseMiddleware, @RouteGuards, @UsePipes, @UseInterceptors, @UseFilters). Both sets run: global first, then the route's.
const app = await TruxieFactory.create(AppModule, {
globalGuards: [AuthGuard],
globalInterceptors: [TimingInterceptor],
globalFilters: [new HttpErrorFilter()],
trustProxy: false,
})Throwing is how a handler fails: NotFoundError, ValidationError, UnauthorizedError, ForbiddenError, ConflictError, or any AppError with a status. A filter decorated @Catch(SomeError) sees only what it declared; an undecorated filter sees everything.
Application options
| Option | Default | What it does |
|---|---|---|
| globalGuards / globalInterceptors / globalMiddlewares / globalPipes / globalFilters | none | Run for every controller route. |
| trustProxy | isServerless() | Whether X-Forwarded-For / X-Real-IP / CF-Connecting-IP are believed. Off, the address comes from the connection. Turn it on only behind a proxy that overwrites those headers — otherwise anything keyed on the client address is chosen by the client. |
| strictModuleBoundaries | false | Fail at boot when a provider depends on something its module neither provides nor imports. |
| eventBus | — | onError for @OnEvent handlers. |
Scopes
Scope.SINGLETON (default), Scope.REQUEST — one instance per request, built in a child container the application disposes when the response is done — and Scope.TRANSIENT. A singleton is always built on the container it was registered on, so it can never capture a request-scoped dependency and keep it forever.
Modules
@Global() // exports visible everywhere
@Module({imports: [], controllers: [], providers: [], exports: []})
class CoreModule {}Dynamic modules (forRoot / forRootAsync / forFeature) come from ConfigurableModuleBuilder, or write them by hand and use createAsyncOptionsProviders for the useFactory / useClass / useExisting triad. forwardRef(() => X) closes a genuine circular import.
Everything else in the box
- Events —
EventEmitter2and@OnEvent('user.created'), fan-out, error-isolated. - CQRS (
truxie/cqrs) —CommandBus,QueryBus,EventBus,AggregateRoot,@CommandHandler,@QueryHandler,@EventsHandler. - WebSockets (
truxie/ws) —@WebSocketGateway,@SubscribeMessage, adapter-agnostic peers. - Testing (
truxie/testing) —Test.createTestingModule(...)withoverrideProvider, running the same lifecycle the application runs. - Introspection —
app.getRoutes(),@ApiDoc,describeRouteInput, consumed by@truxie/openapiand@truxie/mcp.
const testingModule = await Test.createTestingModule(UserModule)
.overrideProvider(UserRepository)
.useValue(mockRepository)
.compile()Lifecycle
onModuleInit → onApplicationBootstrap → … → onModuleDestroy → onApplicationShutdown. Each hook runs once per instance, whether the application or the container gets there first.
The rest of the family
@truxie/express · @truxie/next · @truxie/nitro · @truxie/sveltekit — HTTP adapters.
@truxie/zod · @truxie/openapi · @truxie/mcp — validation, documentation, agents.
@truxie/jwt · @truxie/rbac · @truxie/throttle — authentication, authorization, rate limiting.
@truxie/cache · @truxie/schedule · @truxie/bullmq · @truxie/nats · @truxie/health · @truxie/logger — the usual infrastructure.
MIT.
