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:

6 React Server Component performance pitfalls in Next.js

6 React Server Component performance pitfalls in Next.js

React Server Components and the Next.js App Router enable streaming and smaller client bundles, but only when used correctly. This article explores six common mistakes that block streaming, bloat hydration, and create stale UI in production.

Temitope Oyedele
Feb 23, 2026 ⋅ 13 min read
podrocket 2 19

Making sense of web rendering patterns (SSR, CSR, static, islands)

Gil Fink (SparXis CEO) joins PodRocket to break down today’s most common web rendering patterns: SSR, CSR, static rednering, and islands/resumability.

PodRocket
Feb 23, 2026 ⋅ 48 sec read

CSS @container scroll-state: Replace JS scroll listeners now

CSS @container scroll-state lets you build sticky headers, snapping carousels, and scroll indicators without JavaScript. Here’s how to replace scroll listeners with clean, declarative state queries.

Jude Miracle
Feb 19, 2026 ⋅ 4 min read
Anti-libraryism 10 web APIs that replace modern JavaScript libraries

Anti-libraryism: 10 web APIs that replace modern JavaScript libraries

Explore 10 Web APIs that replace common JavaScript libraries and reduce npm dependencies, bundle size, and performance overhead.

Chizaram Ken
Feb 19, 2026 ⋅ 15 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