Dependency Injection
Bigsby uses ts-injection under the hood to provide an
easy to use, intuitive and clean dependency injection interface. The main building blocks of DI in a
Bigsby app are the InjectionContainer
and Injectable
classes.
InjectionContainer
An InjectionContainer
is an encapsulated DI environment where Injectables can be stored and retrieved.
Each Bigsby instance is created with its own InjectionContainer and will have its own instances of
Injectable classes and objects.
Injectable
The @Injectable decorator lets the framework know that you intend for the class to be handled by the injection container. Common use cases for injectable classes are re-usable components and service providers. Consider a simplified version of handler class we created in the previous section getting started.
@Api()
class ArnyQuotesHandler implements ApiHandler {
constructor(private readonly service: ArnyService) {}
public async invoke(): Promise<string> {
return this.service.getQuote();
}
}
The class relies on a service provider to retrieve a famous quote by Arnold Schwarzenegger. We can enable
automatic injection of the ArnyService
class by defining it as an @Injectable
and listing it as a
constructor argument on the ArnyQuotesHandler
.
@Injectable()
class ArnyService {
public getQuote(): string {
const quotes = [
"Hasta la vista, baby!",
"If it bleeds, we can kill it.",
"Come with me if you want to live.",
];
return getRandomItem(quotes);
}
}
Injectable objects
You may come across use cases where manually registering objects or classes in the DI is
required. In this scenario you can access the DI container via the Bigsby instance and call the register
method. An InjectableItem
is returned including a unique token that can be used to reference the
injectable in the future.
const di = bigsby.getInjectionContainer();
const config: Config = {
printResponse: true,
};
const { token: configToken } = di.register(config).successOrThrow();
warning
Make sure all of your injectable registrations are completed before calling createApiHandler
,
otherwise they may not be available to your handler functions.
Types of injection
The above example is a specific type of constructor injection that doesn't rely on any decorators.
In addition to this type of injection, you can use the @Autowire
decorator to perform different
injection types.
🌾 Field injection
If we preferred, the ArnyService
class could instead be injected into the ArnyQuotesHandler
class,
using field injection. The config object we manually injected can also be injected using its injection
token.
@Api()
class ArnyQuotesHandler implements ApiHandler {
@Autowire(ArnyService)
private readonly service!: ArnyService;
@Autowire(configToken)
private readonly config!: Config;
constructor() {
// I don't have access to my injectables here
}
public async invoke(): Promise<string> {
const response = this.service.getQuote();
if (this.config.printResponse) {
console.log(response);
}
return response;
}
}
🔨 Constructor injection
The same can be achieved via constructor injection, which is the only injection type which allows access to the injectables inside the class constructor.
@Api()
class ArnyQuotesHandler implements ApiHandler {
constructor(
@Autowire(configToken)
private readonly config: Config,
@Autowire(ArnyService) // <- Not mandatory (see below)
private readonly service: ArnyService
) {
// I have access to my injectables here
}
public async invoke(): Promise<string> {
const response = this.service.getQuote();
if (this.config.printResponse) {
console.log(response);
}
return response;
}
}
tip
The @Autowire
decorator isn't strictly required for classes that have been registered with
the @Injectable
decorator. The injection framework is smart enough to infer what should be injected by
adding the class as a parameter (as seen in the first example).
Environment Variables
You can inject environment variables into class members using the @Env
annotation.
process.env.MY_STR = "test";
@Api()
class ArnyQuotesHandler implements ApiHandler {
@Env("MY_STR")
private myString: string;
constructor() {
console.log(this.myString);
// "test"
}
}
For a more in-depth look at all the DI features available in ts-injection, head over and check out the official documentation.