Inject Services
Make a class injectable, take dependencies in the constructor, and keep logic out of handlers.
Dependency injection in Base is deliberately quiet: mark a class @Injectable(), ask for what you need in the constructor, and the container does the rest. You've been using it since your first worker.
The basic pattern
A plain service holds the logic:
Any other class asks for it by type:
That's it. No registration list for injectables — being decorated and imported is enough. (Classes with a dispatch decorator (@HttpService, @GqlResolver, @RpcService) still go in services, but that lists what your worker exposes, not what it can inject.)
Two rules keep this predictable:
- A class that injects needs
@Injectable()(or one of the lifetime decorators from Lifetimes and Scopes). A class with no constructor dependencies (likeRouterTestServicein the framework's own examples) can skip it. - Tokens are always typed. You inject by class, by
TypedInjectionKey<T>, or by aTypedBinding— never by bare string. Base's lint rules statically check that the parameter's type matches what the token resolves to, so a miswired injection is a compile-time error, not a runtime surprise.
Optional and lazy injection
Two useful variants, same shape:
@InjectAll(token) injects every registration of a token as an array — the standard plugin pattern.
Why services, not fat handlers
Handlers (HTTP routes, resolvers, RPC procedures) should orchestrate; injectable services should do the work. The payoff is the one the scaffold demonstrates on day one: GreetingService is reusable from any handler and unit-testable with new GreetingService() — no worker, no container, no HTTP.
Module membership (the moduleKey argument)
Every injectable-family decorator accepts an optional BaseModuleKey: @Injectable(FileStorageModuleKey). This declares which module the class belongs to, and it's what routes token-less @InjectRepository/@InjectDatabase to that module's database (Multiple Databases). If your worker has one database, you'll never need it.