# Providers
Basically, almost everything may be considered as a provider – service, factory, interceptors, and so on. All of them
can inject dependencies, meaning, they can create various relationships with each other. But in fact, a provider is
nothing else than just a simple class annotated with an @Injectable()
decorator.
In controllers chapter, we've seen how to build a Controller, handle a request and create a response. Controllers shall handle HTTP requests and delegate complex tasks to the providers.
The providers are plain javascript class and use one of these decorators on top of them. Here the list:
# Services
Let's start by creating a simple CalendarService provider.
import {Injectable} from "@tsed/di";
import {Calendar} from "../models/Calendar";
@Injectable()
export class CalendarsService {
private readonly calendars: Calendar[] = [];
create(calendar: Calendar) {
this.calendars.push(calendar);
}
findAll(): Calendar[] {
return this.calendars;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Note
Service
and
Injectable
have the same effect.
Injectable
accepts options,
Service
does not.
A Service is always configured as singleton
.
Example with @@Injectable@:
import {Injectable, ProviderScope, ProviderType} from "@tsed/di";
import {Calendar} from "../models/Calendar";
@Injectable({
type: ProviderType.SERVICE,
scope: ProviderScope.SINGLETON
})
export class CalendarsService {
private readonly calendars: Calendar[] = [];
create(calendar: Calendar) {
this.calendars.push(calendar);
}
findAll(): Calendar[] {
return this.calendars;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Now we have the service class already done, let's use it inside the CalendarCtrl
:
import {BodyParams} from "@tsed/platform-params";
import {Get, Post} from "@tsed/schema";
import {Controller} from "@tsed/di";
import {Calendar} from "../models/Calendar";
import {CalendarsService} from "../services/CalendarsService";
@Controller("/calendars")
export class CalendarsController {
constructor(private readonly calendarsService: CalendarsService) {}
@Post()
async create(@BodyParams() calendar: Calendar) {
this.calendarsService.create(calendar);
}
@Get()
async findAll(): Promise<Calendar[]> {
return this.calendarsService.findAll();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Finally, we can load the injector and use it:
import {Configuration} from "@tsed/di";
import {CalendarsCtrl} from "./controllers/CalendarsCtrl";
@Configuration({
mount: {
"/rest": [CalendarsCtrl]
}
})
export class Server {}
2
3
4
5
6
7
8
9
NOTE
You'll notice that we only import the CalendarsCtrl and not the CalendarsService as that would be the case with other DIs (Angular / inversify). Ts.ED will discover automatically services/providers as soon as it is imported into your application via an import ES6.
In most case, if a service is used by a controller or another service which is used by a controller, it's not necessary to import it explicitly!
# Dependency injection
Ts.ED is built around the dependency injection pattern. TypeScript emits type metadata on the constructor which will be exploited by the InjectorService to resolve dependencies automatically.
import {Injectable} from "@tsed/di";
@Injectable()
class MyInjectable {
constructor(private calendarsService: CalendarsService) {}
}
2
3
4
5
6
It's also possible to inject a service on a property by using Inject decorator:
import {Injectable, Inject} from "@tsed/di";
@Injectable()
class MyInjectable {
@Inject()
private calendarsService: CalendarService;
$onInit() {
console.log(this.calendarsService);
}
}
2
3
4
5
6
7
8
9
10
11
In this case, the service won't be usable in the constructor. If you have to do something with the injected service,
you can use the $onInit
hook.
# Scopes
All providers have a lifetime strictly dependent on the application lifecycle. Once the server is created, all providers have to be instantiated. Similarly, when the application shuts down, all providers will be destroyed. However, there are ways to make your provider lifetime request-scoped as well. You can read more about these techniques here.
# Binding configuration
All configurations set with Module or Configuration can be retrieved with Constant and Value decorators. These decorators can be used with:
Constant and Value accept an expression as parameter to inspect the configuration object and return the value.
import {Constant, Value} from "@tsed/di";
import {Env} from "@tsed/core";
export class MyClass {
@Constant("env")
env: Env;
@Value("swagger.path")
swaggerPath: string;
$onInit() {
console.log(this.env);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
WARNING
Constant returns an Object.freeze() value.
NOTE
The values for the decorated properties aren't available on constructor. Use $onInit() hook to use the value.
# Custom providers
The Ts.ED IoC resolves relationships providers for you, but sometimes, you want to tell to the DI how you want to instantiate a specific service or inject different kind of providers based on values, on asynchronous or synchronous factory or on external library. Look here to find more examples.
# Configurable provider
Sometimes you need to inject a provider with a specific configuration to another one.
This is possible with the combination of Opts and UseOpts decorators.
import {Injectable, Opts, UseOpts} from "@tsed/di";
@Injectable()
class MyConfigurableService {
source: string;
constructor(@Opts options: any = {}) {
console.log("Hello ", options.source); // log: Hello Service1 then Hello Service2
this.source = options.source;
}
}
@Injectable()
class MyService1 {
constructor(@UseOpts({source: "Service1"}) service: MyConfigurableService) {
console.log(service.source); // log: Service1
}
}
@Injectable()
class MyService2 {
constructor(@UseOpts({source: "Service2"}) service: MyConfigurableService) {
console.log(service.source); // log: Service2
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
WARNING
Using
Opts
decorator on a constructor parameter changes the scope of the provider
to ProviderScope.INSTANCE
.
# Inject many provider 6.129.0+
This feature lets to simplify the dependency management while working on multiple implementations of the same interface with type code.
If users use the same token when registering providers, the IoC container should exchange a token for a list of instances. Let's consider the following real example:
interface Bar {
type: string;
}
const Bar: unique symbol = Symbol("Bar");
@Injectable({type: Bar})
class Foo implements Bar {
private readonly type = "foo";
}
@Injectable({type: Bar})
class Baz implements Bar {
private readonly type = "baz";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Now as a user, I would like to create a registry (opens new window) and retrieve an appropriate instance by type:
@Controller("/some")
export class SomeController {
constructor(@Inject(Bar) private readonly bars: Bar[]) {}
@Post()
async create(@Body("type") type: "baz" | "foo") {
const bar: Bar | undefined = this.bars.find((x) => x.type === type);
}
}
2
3
4
5
6
7
8
9
or in the following way as well:
@Controller("/some")
export class SomeController {
constructor(private readonly injector: InjectorService) {}
@Post()
async create(@Body("type") type: "baz" | "foo") {
const bars: Bar[] = this.injector.getAll<Bar>(Bar);
const bar: Bar | undefined = bars.find((x) => x.type === type);
// your code
}
}
2
3
4
5
6
7
8
9
10
11
12
# Override an injection token 6.93.0+
By default, the @Injectable()
are register a class provider with an injection token obtained from the metadata generated by Typescript.
That means that you have to use a concrete class as a token to resolve a provider.
To override an injection token, that is needed to resolve an instance, use the @Injectable
decorator like this:
import {Inject, Injectable} from "@tsed/di";
export interface RetryPolicy {
retry<T extends (...args: unknown[]) => unknown>(task: T): Promise<ReturnType<T>>;
}
export const RetryPolicy: unique symbol = Symbol("RetryPolicy");
@Injectable({provide: RetryPolicy})
export class TokenBucket implements RetryPolicy {
public retry<T extends (...args: unknown[]) => unknown>(task: T): Promise<ReturnType<T>> {
// ...
}
}
@Injectable()
export class MyService {
constructor(@Inject(RetryPolicy) private readonly retryPolicy: RetryPolicy) {
// an instance of `TokenBucket`
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
An injection token may be either a string, a symbol, a class constructor.
Just don't forgot to import your provider in your project !
# Lazy load provider 6.81.0+
By default, modules are eagerly loaded, which means that as soon as the application loads, so do all the modules,
whether or not they are immediately necessary. While this is fine for most applications,
it may become a bottleneck for apps running in the serverless environment, where the startup latency ("cold start")
is crucial.
Lazy loading can help decrease bootstrap time by loading only modules required by the specific serverless function invocation. In addition, you could also load other modules asynchronously once the serverless function is "warm" to speed-up the bootstrap time for subsequent calls even further (deferred modules registration).
You can read more about these techniques here.
# Override provider
Any provider (Provider, Service, Controller, Middleware, etc...) already registered by Ts.ED or third-party can be overridden by your own class.
import {OriginalService} from "@tsed/common";
import {OverrideProvider} from "@tsed/di";
@OverrideProvider(OriginalService)
export class CustomMiddleware extends OriginalService {
public method() {
// Do something
return super.method();
}
}
2
3
4
5
6
7
8
9
10
Just don't forgot to import your provider in your project !
Last Updated: 2/5/2023, 1:16:22 PM
Other topics
- Session & cookies
- Passport.js
- Keycloak
- Prisma
- TypeORM
- MikroORM
- Mongoose
- GraphQL
- Socket.io
- Swagger
- AJV
- Multer
- Serve static files
- Templating
- Serverless HTTP
- Seq
- OIDC
- Stripe
- Agenda
- Terminus
- Serverless
- IORedis
- Controllers
- Providers
- Model
- JsonMapper
- Middlewares
- Pipes
- Interceptors
- Authentication
- Hooks
- Exceptions
- Throw HTTP Exceptions
- Cache
- Command
- Response Filter
- Injection scopes
- Custom providers
- Lazy-loading provider
- Custom endpoint decorator
- Testing
- Customize 404