DEV Community

Cover image for ELI5: Dependency Injection
Sean Tansey
Sean Tansey

Posted on • Edited on

ELI5: Dependency Injection

"Tell me about Dependency Injection"... this was the first technical question I was ever asked in a job interview. At the time, as a fresh coding bootcamp grad I honestly had no idea... I instantly felt like a fool, and had to admit I didn't know a thing about it. The interviewer was nice and explained it to me, but at that point I was barely listening, I already knew I wasn't going to get the job. Years later I understand the concept, and to be honest despite all the fancy computer science-y words people use when they talk about it, its really not that complex of an idea.

What is it?

Let's start with a dependency. A dependency is something that a piece of code depends on to function properly. Dependency Injection then, is simply when we pass (inject) that functionality in from the outside, rather than building it inside. It's a separation of responsibilities.

Example

Say we have a database with a table of users, and from our server we want to GET one of those users by their ID. Before dependency injection that code might look something like this:

// imports database functionality
import dbConnection from 'dbConnection'

// router code
router.get('/:id', (request) => {

  // connects to our database
  const mockDatabaseConnection = new dbConnection({
    user: 'mockUsername',
    host: 'mockHost',
    database: 'mockDatabase',
    password: 'mockPassword',
    port: 'mockPort'
})

  // grabs the id from our request
  const { id } = request.params

  // builds a query object
  const query = `SELECT * FROM users WHERE id = ${id}`

  // queries our database
  mockDatabaseConnection.query(query)
    .then((response) => {
      // handles response
    })
})
Enter fullscreen mode Exit fullscreen mode

You can see there a quite a few things going on here. And with this architecture, if we wanted to make another route to a different table, we'd have to duplicate a lot of this code. So maybe theres a way we could split this code up and make it more modular? Looking at the code theres a few different groupings we could make:

  1. Code that handles the request, and builds a query object from it
  2. Code that handles anything related to the database (connection and query)
  3. Code that handles what to do with the response or error thats returned

Lets try extracting the database code into another file so it can be shared across all of our requests:

DATABASE FILE

// imports database functionality
import dbConnection from 'dbConnection'

// connects to our database
const mockDatabaseConnection = new dbConnection({
    user: 'mockUsername',
    host: 'mockHost',
    database: 'mockDatabase',
    password: 'mockPassword',
    port: 'mockPort'
})

// a simple query function that we can export
const queryFunction = (query) => {
  return mockDatabaseConnection.query()
}

export default queryFunction
Enter fullscreen mode Exit fullscreen mode

Now lets inject that code into our routing function rather than building it inside:

// imports database functionality
import queryFunction from 'DatabaseFile'

// GET user by id
router.get('/:id', (request) => {

  // grabs the id from our request
  const { id } = request.params

  // builds a query object
  const query = `SELECT * FROM users WHERE id = ${id}`

  // queries our database
  queryFunction(query)
    .then((response) => {
      //handles response
    })
})

Enter fullscreen mode Exit fullscreen mode

Man thats a lot cleaner looking... and it's an example of dependency injection! We are injecting the database code into the route code, to which the route code is dependent. Beyond being cleaner visually, what are some of the benefits of this?

  • It's much modular and reusable. Now every time we want to write a new route we can simply inject the database into the route rather than rebuilding it each time.
  • It's much cleaner and easier to test. Our database code lives in one file and can be tested all at once. Before we'd have to test our database code in every route! (As someone who loathes writing unit tests this is huge)
  • It separate's the logic in a way that makes sense, each piece doing what it needs to independent of the other. Why should the router code be involved with our database connection? What if we wanted to change databases? Using dependency injection we can just update the database file and we're good to go. Before we'd have to change it in every single route... yikes.
  • The code is DRY (don't repeat yourself)

TL;DR

Dependency Injection occurs when we separate code into smaller reusable classes/objects and then inject that functionality into other objects, rather than rebuilding it each time. This makes our code more reusable, testable and maintainable.

Top comments (0)