# Dependency Injection

7 min read (1263 words)

# Table of contents

# Introduction

Wikipedia describes dependency injection like this:

In software engineering, dependency injection is a technique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used (a service).

This describes dependency injection pretty well, but it's still a bit hard to understand what this means practical. At its core, dependency injection is a programming technique that makes a class independent of its dependencies. It does that, by decoupling the usage of an object from its creation. This will help you to write easy maintainable code and follow the single-responsibility principle (opens new window) (SOLID).

# The problem

Dependency injection by itself isn't a new programming technique, but quiet new to those who previously only worked with JavaScript. The question you might have is what problems it tries to solve and, of course, how ZenTS dependency injection mechanism can help you with that.

Lets start by taking a look at which problems dependency injection tries to solve. First we need to understand what dependency in programming in general means. Since you've chosen to use a Node.js framework, you've probably already used npm as your dependency manager and have a brief understanding how dependencies work. For example, ZenTS has some dependencies by itself, like nunjucks or typeorm, and ZenTS is on the other side a dependency of your project. Installing these dependencies allows you to import or require() these packages and use them. npm or yarn will take care about installing dependencies of dependencies for you. Thats all fine and good, but when we talk about dependency injection in ZenTS, we mean to inject a object that is created by the framework for you.

Simple spoken, when class A needs some functionality of class B, then its said that class A has a dependency of class B.

In a "traditional" JavaScript application, this is done by constructing the dependency object by yourself. Lets say we have a service that is called ReallyUsefulService. This service might be used in multiple controllers. A controller has to create its own instance of ReallyUsefulService by constructing it like this:

// not a ZenTS example!
const ReallyUsefulService = require('./ReallyUsefulService')

class MyController {
  constructor() {
    this.usefulService = new ReallyUsefulService()
  }
}
1
2
3
4
5
6
7
8

There is nothing wrong with that code above, you've probably already seen that many times in many source codes. Things get funny when ReallyUsefulService also has some global dependencies, like the connection to the database. The controller has to supply it somehow:

const ReallyUsefulService = require('./ReallyUsefulService')

class MyController {
  constructor() {
    const connection = this.getConnection()

    this.usefulService = new ReallyUsefulService(connection)
  }
}
1
2
3
4
5
6
7
8
9

Since our service is really useful (...), a lot of controllers will have to create its own instance of ReallyUsefulService. Then the next day, you've to attach a new dependency to ReallyUsefulService and you end up editing all the constructors by hand. That is not really fun and your colleges will hate you for that, because the same time they introduced another dependency, all constructors have to be edited again. Somebody might come up with using a factory to create all dependencies of a controller, and that might work in small applications, but in complex applications that becomes fast bloated, because every controller will get all dependencies, even if they don't need it. Furthermore, this kind of dependency management will cause a lot of headaches when it comes to unit testing.

Dependency injection solves this problem by transferring the task of creating the object to someone else. The responsibility of dependency injection is:

  • Create the object
  • Know which class has this object as dependency
  • Provide this dependency to the corresponding class

# How to use dependency injection

After we learned what problem dependency injection solves it's time to get our hands dirty and use it to write awesome and maintainable applications. In fact, if you've followed ZenTS's guides closely, you've already used dependency injection for example in the database- and service guide (if not, you should definitely read these guides). While injecting e.g. the EntityManager into a controller is really nice and convenient, the true power of ZenTS comes from the @inject annotation, which allows you to inject controller and services into other controller / services as a dependency. A typical example is a controller that has a service as a dependency:

First we create our service:

import { entityManager } from 'zents'
import type { EntityManager } from 'typeorm'

import { User } from '../entity/User'

export default class {
  @entityManager
  private em: EntityManager

  public async auth(username: string, password: string): boolean {
    const user = await this.em.getRepository(User).findOne({ username })
    let isAuth = false

    // ...

    return isAuth
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

This is a service (with pseudo-code) that checks if the user is authenticated or not. In order to do so, it needs to query the database using the EntityManager. The EntityManager is provided by dependency injection (using the @entityManager annotation).


 


 
 



 





import { Controller, post, inject } from 'zents'
import UserService from '../service/UserService'

export default class extends Controller {
  @inject
  private userService: UserService

  @post('/login')
  public async login({ body }) {
    const isAuth = await this.userService.auth(body.username, body.password)

    // ...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • line 2: We import our service like usually.
  • line 5: We using the @inject decorator to tell the dependency injector to construct the UserService for us (and all the dependencies the UserService maybe has).
  • line 6: Here we declare the private class member userService from type UserService. This way we overcome a disadvantage other dependency injection frameworks have, because we still have autocompletion and other tools supported in IDEs like Visual Studio Code (e.g. typing this.userService. will give you a auth() autocompletion result).
  • line 10: In the controller action we use the service like it's a first class citizen. You don't have to take care of initializing a UserService class by yourself.

Now the above controller and our UserService are totally independent of each other. The controller don't need to take care if the dependencies of the UserService has been changed and visa versa. And the good news is, that you still can have a constructor when using ZenTS's dependency injector:










 

 
 
 





 







import { entityManager, log } from 'zents'
import type { EntityManager } from 'typeorm'

import { User } from '../entity/User'

export default class {
  @entityManager
  private em: EntityManager

  protected foo: string

  constructor() {
    this.foo = 'bar'
  }

  public async auth(username: string, password: string): boolean {
    const user = await this.em.getRepository(User).findOne({ username })
    let isAuth = false

    log.info(this.foo) // logs "bar"

    // ...

    return isAuth
  }
}
1
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

Hint: The same counts also for controller constructors.

WARNING

Keep in mind, that a constructor will never be passed any arguments.

# Handling circular dependencies

Of course, no technology comes without a flaw, so does dependency injection. Problems can arise when service A depends on service B and service B depends on service A. This is called circular dependency and will cause your application to crash. You should better avoid these circular dependencies, but sometimes that isn't possible. In that case, you have to create a third service that acts as a facade and has both service A and B as a dependency, which allows them to communicate to each other over the facade service.

# Next steps

Congrats 🎉 🎉 🎉

You've just learned what is dependency injection and how to use it in a ZenTS web application. Want to dig deeper? Check out the sending emails guide and learn how to send responsive emails with ZenTS.