Services and Dependency injection

status: working in progress

What is Dependency Injection?

// TODO

Hooks

useInjected

The useInjected hook resolves items from the request-scoped container. Services, configurations, constants, everything available inside the request scope.


useInjected resolutions are required. If no registration is found at request level for the specified key, the resolution goes up to the global scope. If still no registration is found, useInjected throws an error.


The are several ways to resolve something:

Service by Constructor

You can resolve a service instance by passing its class.

const orders = useInjected(OrderService);

Service by Alias

You can resolve a service instance with an alias if previously set.

const logger = useInjected("ApplicationLogger");

You can type-check aliases with custom service definitions.

Environment variables

You can resolve environment variables with the KEY helper.

const secret = useInjected(KEY("ENV", "SERVICE-SECRET"));

You can type-check environment variables with explicit definitions.

Multiple injection

You can resolve multiple instances at the same time, and destructure the result to assign each one. You can even mix different requesting keys.

const [orders, tracking, logger] = useInjected(OrderService, TrackingService, "ApplicationLogger");

Returning instances have defined types, so everything is type checked and safe.

useTransaction

// TODO

Decorators

@httpc/kit uses the tsyringe library as dependency container.


httpc/kit offers additional decorators to cover some lacks tsyringe leaves, like optional to specify a non-required dependency, or alias to register a class with a specific name.


Other decorators like logger and options, enable new use cases or ease the usage for repetitive or boilerplate code.

optional

optional is a parameter decorator. It allows optional dependencies in constructor injection.

class MyService {
    constructor(
        @optional() dependency: AnotherService | unauthorized
    ) {
    }
}

You can define a default value when the dependency is missing.

class MyService {
    constructor(
        @optional({ field: "value" }) configuration: Configuration
    ) {
    }
}

env

env is a parameter decorator. It inject an environment variable while instantiating a class.

class MyService {
    constructor(
        @env("LOG_LEVEL") logLevel: string
    ) {
    }
}

The specified environment variable is required to be defined. A missing variable will cause a resolution error.


You can specify a default value, when the variable is missing. In that case, the environment variable became optional as it fallbacks to the default value specified.

class MyService {
    constructor(
        @env("LOG_LEVEL", "info") logLevel: string
    ) {
    }
}

options / optionsOf

options is a parameter decorator. It’s part of the service-options pattern, where a class needs a configuration object.

type MyServiceOptions = {
  field: string
}

class MyService {
    constructor(
        @options() options: MyServiceOptions
    ) {
    }
}

By default, the options are required, meaning the container must resolve the options.


You can make options resolution optional:

class MyService {
    constructor(
        @options(undefined) options: MyServiceOptions | undefined
    ) {
    }
}

Or you can set a default value:

class MyService {
    constructor(
        @options({ field: "value" }) options: MyServiceOptions
    ) {
    }
}

The options can be registered in two ways: with a class or manually.


You can use a class with the decorator optionsOf:

@optionsOf(MyService)
class Options implements MyServiceOptions {
    field: "value"
}

You can register options manually with an helper:

REGISTER_OPTIONS<MyServiceOptions>(MyService, {
    field: "value"
});

logger

logger is a parameter decorator. It creates a specific logger when the class will be instantiated.

class MyService {
    constructor(
        @logger() private logger: ILogger
    ) {
    }
}

Detailed information and advanced usage on the Logging page.

noInject

noInject is a parameter decorator. It allows the resolution to skip a parameter.

class MyService {
    constructor(
        dependency: AnotherService,
        @noInject() param: string | undefined,
    ) {
    }
}

In the above example, the param argument will be not resolved and set to undefined when resolved.


noInject is useful when the class needs be manually instantiated.

const service = new MyService(dependencyInstance, "value");

alias

alias is a class decorator. It allows to resolve the decorating class with the string specified.

@alias("Payments")
class MyPaymentProvider {

}

Now, you can resolve the provider with the string:

const payments = useInjected("Payments");

The alias decorator is used to register a class to an expected implementation. For example, the bearer authentication expects a "BearerAuthentication" service to be registered to customize advanced behaviors.

initializer

initializer is a class decorator. It signals the class has an initializer method, which will run during the application startup.

@initializer()
class MyService implement IInitialize {
    async initialize(): Promise<void> {
        // do initialization work
    }
}

You can implement the IInitialize interface to have type checking. This is optional, just for convenience while developing.

Helpers

KEY

// TODO