IoC Container

The IoC Container (Inversion of Control Container) is the heart of Typetron, and many of the core modules are using it. It helps with managing and performing dependency injection.

Let’s look at an example:

import { Controller, Get } from '@Typetron/Router'
import { Request } from '@Typetron/Web'

@Controller('articles')
export class ArticleController {
    @Get()
    search(request: Request) {
        return request.query.search
    }
}

The ArticleController has the search method which returns the value of the query parameter search. A request made to /articles?search=”programming” returns “programming”, but the question is: how is the request: Request passed to the search method? Who is instantiating it? The answer is: the Ioc Container.

Behind the scenes, Typetron checks if there are any parameters needed by the method. In this case, the search method needs an instance of the Request class. When the Router calls the search method, it will automatically provide the dependency needed.

In fact, you can use any class as a dependency and Typetron will automatically resolve that dependency for you:

import { Controller, Get } from '@Typetron/Router'
import { Request } from '@Typetron/Web'

class Logger {
    log(uri: string) {
        console.log(`A request was made to ${uri}`)
    }
}

@Controller('articles')
export class ArticleController {
    @Get()
    search(request: Request, logger: Logger) {
        logger.log(request.uri)
        return "searching articles"
    }
}

In this example, whenever the search method is called, the IoC Container will pass the Request and Logger instances as parameters. Then you can use those instances to create your business logic.

You can also add dependencies to dependencies and Typetron will resolve them recursively using the @Inject() decorator:

import { Controller, Get } from '@Typetron/Router'
import { Request } from '@Typetron/Web'
import { Inject } from '@Typetron/Container'

class Logger {

    @Inject()
    request: Request

    log() {
        console.log(`A request was made to ${this.request.uri}`)
    }
}

@Controller('articles')
export class ArticleController {
    @Get()
    search(logger: Logger) {
        logger.log()
        return "searching articles"
    }
}

Binding

Binding is the process of registering classes in the container using the .set method. There is no need to manually bind a class in the container since Typetron can resolve them automatically. It is recommended to bind a class to the container if you want to program to interface, or you want to bind a class to a custom string key.

An example of binding to interface can be found inside AppProvider:

import { Provider } from '@Typetron/Framework'
import { ErrorHandlerInterface } from '@Typetron/Web'
import { AppErrorHandler } from 'App/Services/AppErrorHandler'

export class AppProvider extends Provider {

    async register() {
        this.app.set(ErrorHandlerInterface, this.app.get(ErrorHandler))
    }

}

Here we register a custom Errorhandler and bind it to the ErrorHandlerInterface. Typetron’s core modules don’t know anything about your custom ErrorHandler, but using the ErrorHandlerInterface you tell Typetron that he can use that custom handler. Internally, when Typetron will need an error handler, it will fetch the ErrorHandlerInterface and use the value you’ve set. In this case, it will use the custom ErrorHandler class instance to handle errors.

Container scopes

By default, the container creates singleton instances for the dependencies you use, but there are use cases when you want to create an instance every time you need one, or at every user request. This option is called the scope of the service. You can set the scope of a service by using the @Injectable() decorator and then set the necessary scope, just like in the examples below.

Singleton scope

Creating an instance that will be globally used in the app as a singleton:

@Injectable(Scope.SINGLETON)
class Cache {}
Request scope

Creating an instance with every user request:

@Injectable(Scope.REQUEST)
class Auth {}
Transient scope

Creating an instance every time you request the dependency:

@Injectable(Scope.TRANSIENT)
class MyService {}