2021-07-23
2182
#typescript
Gregory Pabian
60076
Jul 23, 2021 ⋅ 7 min read

Top 5 TypeScript dependency injection containers

Gregory Pabian Full-stack software developer who loves building products.

Recent posts:

How to Use React Router v6 in React Apps

How to use React Router v7 in React apps

A practical guide to React Router v7 that walks through declarative routing, nested layouts, dynamic routes, navigation, and protecting routes in modern React applications.

Aman Mittal
Jan 16, 2026 ⋅ 15 min read

TanStack AI vs. Vercel AI SDK: Choosing the right AI library for React

TanStack AI vs. Vercel AI SDK for React: compare isomorphic tools, type safety, and portability to pick the right SDK for production.

Ikeh Akinyemi
Jan 16, 2026 ⋅ 8 min read
Authentication With React Router V6: A Complete Guide

Authentication with React Router v7: A complete guide

Handle user authentication with React Router v7, with a practical look at protected routes, two-factor authentication, and modern routing patterns.

Vijit Ail
Jan 15, 2026 ⋅ 15 min read

A developer’s guide to designing AI-ready frontend architecture

AI now writes frontend code too. This article shows how to design architecture that stays predictable, scalable, and safe as AI accelerates development.

Nelson Michael
Jan 15, 2026 ⋅ 9 min read
View all posts

5 Replies to "Top 5 TypeScript dependency injection containers"

  1. This post misses the most important part of DI that most DI in java does which is overrides.

    Using Guice as an example.
    production:
    Injector injector = Guice.createInjector(new ProductionModule());
    App appTree = injector.createClass(App.class);

    testing
    Injector injector = Guice.createInjector(Modules.override(new ProductionModule()).with(new TestOverrides());
    App appTreeWithMocks = injector.createClass(App.class)

    which of the above DI frameworks do this? This is a critical piece of the usefulness of DI.

    1. If I understood you correctly, you want to inject a different class at different runtimes (production and test).

      For Next.js, we can check how their did that for their testing, for instance in this test suite:
      https://github.com/nestjs/nest/blob/master/integration/hello-world/e2e/guards.spec.ts

      For others DI libraries, we can use child injectors or child containers:
      * https://github.com/nicojs/typed-inject#-child-injectors
      * https://github.com/nicojs/typed-inject#-child-injectors

      In general, the TS/JS ecosystem is less mature compared to Java’s one, also based on my experience DI is not a vastly used pattern in TS codebases.

  2. All five of these rely on `public static` members or decorators – meaning all of these rely on your designing code for the DI container. DI containers should be able to work in general – and with third party code in particular.

    They should not require a footprint in the code outside of the DI bootstrap code, near the entry point of the program – domain code should not need to concern itself with dependency injection at all, it should be handled centrally, in one location, not all over the place.

    A few other containers have tried to get around the issue by adding reflection to the language – usually, this relies on plugins for the official TS compiler, which almost no one uses in practice (the compilation space is largely dominated by Babel, ESBuild, SWC, or other tools that integrate these) and with the plugin API itself remaining an unofficial/internal feature of the official compiler, so that doesn’t really work for me either.

    Here’s one container I’ve seen that doesn’t rely on decorators or compilation:

    https://brandi.js.org/

    I wrote an article about doing dependency injection without a DI container, which is also an option:

    https://dev.to/mindplay/a-successful-ioc-pattern-with-functions-in-typescript-2nac

    This article talks about functions, but the pattern is applicable to classes or anything else as well – here’s an example with classes:

    https://tinyurl.com/2t93furt

    I’ve also tried to design a semi-declarative DI container – this should look more familiar:

    https://tinyurl.com/2p8e9jn2

    This approach isn’t fully declarative – you do need to define a service map separately from the actual service definitions. You might actually enjoy this kind of separation, or you may find it overly ceremonious – that seems to be an individual thing.

    Both approaches are type-safe, but neither approach is “modular” – meaning you can’t have multiple reusable “providers” or “modules”, and you will have to bootstrap everything in one central location. Again, some will think that’s a good thing, others will find it annoying – it comes down to opinions.

    I would love it if somebody would try to develop this idea further. I’ve attempted a number of things myself, and just getting to this point with type-safety took a lot of hard work, and it’s still not clear to me whether this concept can expand to incorporate modules, e.g. with type-checking happening at the call site where you bootstrap your providers/modules into a container.

    I don’t know whether the language can do it all.

    If it can, I’m afraid I’m not smart enough to beat the type-system. 😅

    1. I will try to address all your points, Rasmus 🙂

      I believe the general approach (based on my experience alone) to DI in JS/TS should be as follows:

      1. If the application doesn’t require direct function calls between different components, don’t use DI. This happens when maintaining separation of concerns is crucial due to application design. This usually means you use message buses in your application to convey information.
      2. If your application has a small number of components and each component requires up to 3-4 dependencies, you can still construct the components’ instances it manually in a reasonable number of lines.
      3. When the number of components starts growing, you need to start using DI to maintain code readability.

      The choice of the DI framework should depend on the specific runtime. For backends, I don’t think anyone minds relying on a footprint from decorators (especially that we chose to use JS/TS there). For frontends, bundle size matters, but bundles can be split and the footprint can be calculated. Also, a frontend application with a lot of components might not work efficiently after all.

      Code using DI frameworks with footprint is generally easier to read compared to ones’ without. Basically, if I use DI, I don’t want to construct instances myself, I want the DI to do it for me.

      Type safety in TS is a fickle thing. TS is just a superset of JS that compiles code to the latter. There is a a number of problems (e.g. the higher-kinded types) that cannot be expressed in TS code without some “tricks”. A vast number of TS libraries uses type tricks under the hood so we have a notion of type safety in userland code.

Leave a Reply

Hey there, want to help make our blog better?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now