--- url: https://needle-di.io/what-is-needle-di.md description: >- Introduction to Needle DI: why it exists, why to use dependency injection, its design principles and when (not) to use it. --- # What is Needle DI? Needle DI is a small, lightweight JavaScript library for dependency injection. ## Why another library? There are many existing dependency injection libraries. We're certainly not claiming to be unique. However, we hope that our combination of [design principles](#design-principles) may be exactly what you're looking for. If you have been using [Angular](https://angular.dev/guide/di) or [NestJS](https://docs.nestjs.com/providers#dependency-injection) before, this library might look familiar. That's because we took a lot of inspiration from existing frameworks, but we also made some different decisions. Make sure to refer to the documentation. ## Why dependency injection? Dependency Injection (DI) is a design pattern in (object-oriented) programming where an object's or function's dependencies are provided to it externally rather than the object creating them itself. In simpler terms, instead of an object creating its own resources, they are "injected" into the object, usually through its constructor. Using dependency injection will lead to: * **Loose coupling**: By injecting dependencies, classes depend on abstractions (like interfaces) instead of specific implementations. This makes code more flexible and easier to maintain or extend. * **Easier testing**: Since dependencies can be injected, you can easily swap real dependencies with mock or fake objects when unit testing, allowing for isolated testing. * **Better code organization**: DI promotes separation of concerns, meaning each class has a specific role and does not have to manage its own dependencies, leading to cleaner and more organized code. * **Reusability**: When dependencies are provided externally, classes become more modular and can be reused in different contexts with different dependencies. * **Improved maintainability**: As your codebase grows, the ability to swap dependencies without needing to change core logic becomes critical. DI allows you to update, change, or replace dependencies with minimal impact. In essence, DI helps in managing the complexity of large systems, improves code quality, and makes it easier to adapt to changing requirements. ## Design principles ### Lightweight Needle DI is specifically designed for apps with a small footprint (e.g. serverless functions like AWS lambdas), by minimizing bundle size by enabling tree-shaking and by not depending on any reflection metadata. ### Type-safe Needle DI is written in TypeScript. You don't have to use TypeScript to use Needle DI, but when you do, all type definitions are included and will make sure your code is consistent. ### Modern Needle DI is an ESM-only package. This makes it suitable for modern Node.js and web projects. It also uses stage 3 decorators, to push for ECMAScript standards. ## When to use Needle DI? * **If your framework doesn't offer a built-in solution.** Most application frameworks for web or Node.js (such as [Angular](https://angular.dev/guide/di) or [NestJS](https://docs.nestjs.com/providers#dependency-injection)), offer a dependency injection solution which is already included. Therefore, Needle DI is mainly intended for smaller ("vanilla") projects. * **If you deeply care about type-safety.** So if you're using TypeScript, you can use this as an opportunity to prevent mistakes in your dependency injection. * **If you care about bundle-size**. Needle DI has only ~40 kB of unpacked size, but also supports tree-shaking. Besides that, no reflection or decorator metadata is needed. * **If you want less dependencies**. Needle DI has no further (peer) dependencies. ## Why is feature X not included? There is currently no roadmap, but we will consider every feature request. Please [submit a ticket](https://github.com/needle-di/needle-di/issues/new), and we'd like to discuss a fitting solution. --- --- url: https://needle-di.io/getting-started.md description: >- Install @needle-di/core from npm or JSR, configure your transpiler for native ECMAScript decorators, and bootstrap your first container. --- # Getting started ## Installation Just install it using your favorite package manager. Needle DI is published to [NPM](https://www.npmjs.com/package/@needle-di/core) and [JSR](https://jsr.io/@needle-di/core), and is also compatible with [Deno](https://deno.com/). ::: code-group ```bash [npm] npm install @needle-di/core ``` ```bash [yarn] yarn add @needle-di/core ``` ```bash [pnpm] pnpm install @needle-di/core ``` ```bash [deno] deno add jsr:@needle-di/core ``` ::: ## Transpiler settings Needle DI uses native [ECMAScript decorators](https://github.com/tc39/proposal-decorators), which are currently in [stage 3] of the TC39 standardization process. [stage 3]: https://github.com/tc39/proposals#stage-3 If you're using Deno, you can run your code as-is. However, when running on Node.js or in a browser, you might need to transpile your code first, as your runtime might [not have implemented it yet](https://github.com/tc39/proposal-decorators/issues/476). Make sure to use `ES2022` or lower as target: ::: code-group ```json [tsc (tsconfig.json)] { "compilerOptions": { "target": "ES2022" } } ``` ```javascript [vite (vite.config.mjs)] export default defineConfig({ esbuild: { target: 'es2022', // ... }, // ... }); ``` ```bash [esbuild] esbuild app.js --target=es2022 ``` ::: ## Basic example Here’s a simple example using constructor injection to inject one service into another. ```ts twoslash import { injectable, inject } from "@needle-di/core"; @injectable() class FooService { // ... } @injectable() class BarService { constructor(private fooService = inject(FooService)) {} // ^? } ``` As you can see, Needle DI uses default parameter values for constructor injection. The `@injectable` decorator eliminates the need to register services manually. ## Bootstrapping To bootstrap the `BarService`, you have to create a new dependency injection container, and use the `.get()` method on it to retrieve it by its token: ```ts twoslash import { injectable, inject, Container } from "@needle-di/core"; import { BarService } from "./bar.service"; const container = new Container(); // you can use container.bind() to register more services. const barService = container.get(BarService); // ^? ``` That's it! ## What's next? Learn how you can use [binding](/concepts/binding) to register services. --- --- url: https://needle-di.io/concepts/binding.md description: >- Register services in a container, using auto-binding with the @injectable() decorator or manual binding with container.bind() and defineProviders(). --- # Binding **Binding** is the registration of your services into your dependency injection (DI) container. ## Auto-binding The easiest way to register your class for automatic dependency injection, is by applying the `@injectable()` decorator to your class: ```ts twoslash import { injectable } from "@needle-di/core"; @injectable() class FooService { // ... } ``` This will automatically bind `FooService` as a singleton service. To request it from your service, you can use the `.get()` method on the [container](./containers): ```ts twoslash import { Container } from "@needle-di/core"; import { FooService } from "./foo.service"; const container = new Container(); const fooService = container.get(FooService); // ^? ``` * Its construction is **lazy**: it will only be created when you request it from the container. * It is also a **singleton**: the first time a `FooService` is injected, a new instance is constructed, but it will reuse this instance whenever it needs to be injected again. > \[!NOTE] > Since Needle DI uses native [ECMAScript decorators](https://github.com/tc39/proposal-decorators) > (which are currently in [stage 3](https://github.com/tc39/proposals#stage-3)), you will need to transpile your code in > order to use it in a browser or in Node.js. > > All modern transpilers (including [TypeScript], [esbuild], [Webpack], [Babel]) do have support for stage 3 decorators. > If you > don't want to depend on transpilation, you can bind your services [manually](#manual-binding) instead, without using > decorators. [TypeScript]: https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#decorators [esbuild]: https://github.com/evanw/esbuild/releases/v0.21.0 [Webpack]: https://stackoverflow.com/a/37616418/1116452 [Babel]: https://stackoverflow.com/a/37616418/1116452 ## Manual binding If you don't want to use the `@injectable()` decorator, for example if you don't want to use decorators or you want to bind a class that you cannot decorate (from another library), you can manually register your service with the `.bind()` method: ```ts twoslash import { Container } from "@needle-di/core"; import { FooService } from "./foo.service"; const container = new Container(); container.bind(FooService); const fooService = container.get(FooService); // ^? ``` This is the same as applying a decorator to `FooService`. ### Binding multiple providers To bind more than one provider at once, use the `.bindAll()` method. It accepts any number of providers, either individually or as (nested) arrays: ```ts twoslash import { Container, InjectionToken } from "@needle-di/core"; import { FooService } from "./foo.service"; import { BarService } from "./bar.service"; import { MyConfig } from "./my-config"; const MY_CONFIG = new InjectionToken("MY_CONFIG"); const container = new Container(); container.bindAll(FooService, BarService, [ { provide: MY_CONFIG, useValue: { foo: "bar" } }, ]); ``` Every provider is type-checked individually, so the value you provide must always match the type of its token: ```ts twoslash // @errors: 2322 import { Container, InjectionToken } from "@needle-di/core"; import { MyConfig } from "./my-config"; const MY_CONFIG = new InjectionToken("MY_CONFIG"); const container = new Container(); // ---cut--- container.bindAll({ provide: MY_CONFIG, useValue: { foo: 42 } }); ``` ### Defining providers upfront Sometimes you want to declare a list of providers separately from the container, for example to group them per feature and share them between containers. Annotating such a list as `Provider[]` would throw away the relation between a token and the value it provides, so no type-checking would happen at all. Use the `defineProviders()` function instead, which validates every provider and returns them as a single, flat array: ```ts twoslash import { Container, InjectionToken, defineProviders } from "@needle-di/core"; import { FooService } from "./foo.service"; import { BarService } from "./bar.service"; import { MyConfig } from "./my-config"; const MY_CONFIG = new InjectionToken("MY_CONFIG"); const commonProviders = defineProviders(FooService, BarService); const testProviders = defineProviders(commonProviders, [ { provide: MY_CONFIG, useValue: { foo: "test" } }, ]); const container = new Container().bindAll(testProviders); ``` > \[!TIP] > Since `defineProviders()` flattens its arguments, you can freely compose lists of providers by > nesting them, without having to spread them yourself. ## Clear binding To clear a binding, you can use the `.unbind()` or `.unbindAll()` method. This will also remove any instances of the service from the container. The `.unbind()` method takes the token you want to unbind: ```ts twoslash import { Container, InjectionToken } from "@needle-di/core"; import { FooService } from "./foo.service"; import { MyConfig } from "./my-config"; const MY_CONFIG = new InjectionToken("MY_CONFIG"); const container = new Container(); // ---cut--- container.unbind(FooService); container.unbind(MY_CONFIG); ``` > \[!NOTE] > Unbinding a token removes *all* providers for that token, including any > [multi-providers](../advanced/multi-injection.md). If you unbind a token while an [asynchronous construction](../advanced/async-injection.md) for it is still in progress, that construction is abandoned: its result is discarded instead of being stored as an instance. Any pending `getAsync()` call that started it still resolves with the constructed value. *** There are many different ways to bind services, check out the section about [providers](./providers) to learn more. --- --- url: https://needle-di.io/concepts/providers.md description: >- The provider types of Needle DI: class providers, existing providers, factory providers, value providers, and multi-providers. --- # Providers There are many ways to register your services for dependency injection. ## Terminology It's important to understand the terminology here: * A **service** (or **value**) is the actual thing that the DI container should create; * A **token** is the unique reference to that service; * A **provider** states how the service should be created. ::: details Check an example ```ts twoslash import { Container } from "@needle-di/core"; import { FooService } from "./foo.service"; const container = new Container(); container.bind({ provide: FooService, useFactory: () => new FooService(), }); ``` In this case, `FooService` is the **token**, `new FooService()` is the **service** (or **value**), and the `useFactory` function is the **provider** that states how this service will be created. ::: ## Types of providers There are different types of providers. ### Class providers A class provider refers to a class constructor, which will be used construct a new instance. ```ts container.bind({ provide: Logger, useClass: Logger, }); ``` This example can also be written with the shorthand: ```ts container.bind(Logger); ``` This will register a singleton for `Logger` that gets lazily constructed. Note that `useClass` may also refer to a child class of `Logger`: ```ts container.bind({ provide: Logger, useClass: FileLogger, }); ``` Check out [inheritance support](/advanced/inheritance) for more information. ### Value providers A value provider refers to a static value. ```ts container.bind({ provide: MyService, useValue: new MyService(), }); ``` This will bind the provided value to the token. This value will act as a singleton and will be reused. Note that this value is created, regardless whether it is used. ### Factory providers A factory provider refers to a factory function, which will only be invoked when this token gets injected for its first time. This makes it ideal for lazy evaluation. ```ts container.bind({ provide: MyService, useFactory: () => new MyService(), }); ``` The value returned by the function will act as a singleton and will be reused. Note that you can use the `inject()` function inside this factory function, allowing you to inject other dependencies: ```ts container.bind({ provide: MyService, useFactory: () => new MyService(inject(FooService), inject(BarService)), }); ``` It is also possible to access the container, which is passed to the `useFactory` function: ```ts container.bind({ provide: MyService, useFactory: (container) => new MyService(container.get(FooService), container.get(BarService)), }); ``` ### Existing providers An existing provider is a special provider that refers to another provider, by specifying its token. This basically works like an alias. This can be useful for inheritance or [injection tokens](/concepts/tokens). ```ts container.bind({ provide: MyValidator, useClass: MyValidator, }); container.bind({ provide: VALIDATOR, useExisting: MyValidator, }); ``` In this case, both `inject(MyValidator)` and `inject(VALIDATOR)` would inject the same instance. --- --- url: https://needle-di.io/concepts/containers.md description: >- Create a Container, bind services to it, bootstrap your application with container.get(), run functions in an injection context and create child containers. --- # Containers ## Creating a container The dependency injection (DI) container will keep track of all bindings and hold the actual instances of your services. To create it, simply construct one: ```ts twoslash import { Container } from "@needle-di/core"; const container = new Container(); ``` Every DI container keeps track of its own service instances separately. ## Binding services You can bind services using the `.bind()` or `.bindAll()` methods: ```ts container .bind(FooService) .bind({ provide: BarService, useFactory: () => new BarService(), }); container.bindAll( { provide: Logger, useFactory: () => new FileLogger(), }, { provide: AppConfig, useValue: someConfig, }, ); ``` Learn more about the different types of [providers](./providers) you can use for binding. ## Bootstrapping ### Using `.get()` To request a service from the container, you can use the `.get()` method: ```ts twoslash import { Container } from "@needle-di/core"; import { FooService } from "./foo.service"; const container = new Container(); const fooService = container.get(FooService); // ^? ``` This will either create a new `FooService`, or return the existing one if requested before. ### Using `bootstrap()` If you don't need to interact with the DI container at all, you can also use the `bootstrap()` shorthand function instead. This will internally create a new container and return the requested service directly: ```ts twoslash import { bootstrap } from "@needle-di/core"; import { BarService } from "./bar.service"; const barService = bootstrap(BarService); // ^? ``` This is useful if you solely depend on [auto-binding](/concepts/binding#auto-binding) and/or [tree-shakeable injection tokens](/advanced/tree-shaking) and therefore don't need to register anything manually into your container. Similarly, there is a `bootstrapAsync()` method when using [async providers](../advanced/async-injection.md). > \[!WARNING] > Calling `bootstrap()` or `bootstrapAsync()` creates a new container everytime, leading to the creation > of new instances for your singleton services. Make sure to only call it once in the lifecycle of your > application to use it efficiently. ## Running functions in an injection context Besides `.get()`, you can also hand a function to the container using `.runInInjectionContext()`, so that it can use `inject()` and `injectAsync()` instead of a container reference: ```ts twoslash import { Container, inject } from "@needle-di/core"; import { FooService } from "./foo.service"; const container = new Container(); const fooService = container.runInInjectionContext(() => inject(FooService)); // ^? ``` See [running in an injection context](./injection#running-in-an-injection-context) for more information, including its limitations around `async` functions. ## Creating child containers You can also create a child container, which can be used to override a provider without affecting its parent. To do so, use the `.createChild()` method: ```ts twoslash import { Container } from "@needle-di/core"; import { LOGGER, MyLogger, OtherLogger } from "./logger"; const parent = new Container(); parent.bind({ provide: LOGGER, useClass: MyLogger }); const child = parent.createChild(); child.bind({ provide: LOGGER, useClass: OtherLogger }); ``` See [child containers](../advanced/child-containers.md) for more information. --- --- url: https://needle-di.io/concepts/injection.md description: >- Inject dependencies with the inject() and injectAsync() functions, using constructor injection, initializer injection, manual injection or an injection context you enter yourself. --- # Injection In most cases you may want to inject your dependencies inside a class. There are several ways to do this. ## Constructor injection Needle DI strongly recommends **constructor injection**, since it makes the dependencies of your class explicit, more type-safe, and allows for easier unit testing. Instead of using `container.get(token)`, you can use the `inject(token)` function here, so no reference to an actual container is needed. ```ts twoslash import { inject, injectable } from "@needle-di/core"; import { FooService } from "./foo.service"; import { BarService } from "./bar.service"; @injectable() class MyService { constructor( private fooService = inject(FooService), private barService = inject(BarService), // ^? ) { } // ... } ``` > \[!TIP] > If you don't know what a **token** is: consider it the unique reference for your binding. In this case, its just the > class reference, but there are many more tokens possible. > > Learn more about [tokens](./tokens). > \[!NOTE] > Needle DI uses **default parameter values** for constructor injection. This maximizes type-safety and removes the need > for parameter decorators, which [aren't yet standardized][parameter decorators] in ECMAScript. > > Although experimental parameter decorators allow for static analysis, > this design was chosen on purpose to reduce complexity and bundle size. ## Initializer injection Alternatively, you can also initialize your dependencies as (private) fields: ```ts twoslash import { inject, injectable } from "@needle-di/core"; import { FooService } from "./foo.service"; import { BarService } from "./bar.service"; @injectable() class MyService { private fooService = inject(FooService); private barService = inject(BarService); // ^? Type will be inferred as `BarService` // ... } ``` Although this is less verbose, this will not allow you to pass in those dependencies when you construct the class manually, e.g. in unit tests. ## About the `inject()` and `injectAsync()` functions Note that the `inject()` and `injectAsync()` functions are only available in the "injection context": * During construction of a class being instantiated by the DI container; * In the initializer for fields of such classes; * In a synchronous factory function specified for `useFactory` of a provider; * In the `factory` function specified for an `InjectionToken`; * In a function you run yourself using [`container.runInInjectionContext()`](#running-in-an-injection-context). If you try to use this function outside this context, it will throw an error. This is because Needle DI needs a reference to a DI container when constructing services globally. ::: warning When using an asynchronous factory provider, you cannot use the `inject()` / `injectAsync()` functions. Please use the provider `container` instance instead. So instead of: ```ts { provide: LOGGER, useFactory: async () => { // ... return MyLogger(inject(OTHER_DEP)); }, async: true } ``` Please use: ```ts { provide: LOGGER, useFactory: async (container) => { // ... return MyLogger(container.get(OTHER_DEP)); }, async: true } ``` ::: ## Manual Injection Situations where classes aren't used but rather functions can still benefit from dependency injection. In these cases, manually passing the singleton container instance as an argument to the function and using the `.get()` function within provides the same practical functionality as using classes with decorators, just with reduced ergonomics. ```ts twoslash import type { Container } from "@needle-di/core"; import { FooService } from "./foo.service"; import { BarService } from "./bar.service"; const createMyService = (container: Container) => { const fooService = container.get(FooService); const barService = container.get(BarService); // ... } ``` Since the dependencies are explicit and no hidden state is involved, this is still the most predictable option, and it keeps working across `await` boundaries. ## Running in an injection context If threading the container through every function gets in the way, you can also enter an injection context yourself, using the `.runInInjectionContext()` method. Everything that runs inside can then use `inject()` and `injectAsync()`, no matter how deeply nested, and the return value of your function is passed through: ```ts twoslash import { Container, inject } from "@needle-di/core"; import { FooService } from "./foo.service"; import { BarService } from "./bar.service"; const container = new Container(); const createMyService = () => ({ fooService: inject(FooService), barService: inject(BarService), }); const myService = container.runInInjectionContext(createMyService); // ^? ``` Your function also receives the container as its first argument, so you can still fall back to `.get()` where that reads better. Services are resolved from the container you started the context on, so a [child container](../advanced/child-containers) will resolve its own overrides first and only fall back to its parent afterwards. ::: warning The injection context is only active while your function runs **synchronously**. As soon as it awaits something, the context is restored and `inject()` will throw again. This is deliberate: keeping the context active across an `await` would leak it to unrelated code that happens to run while your function is suspended, which would then resolve from the wrong container. So when passing an async function, make sure that every `inject()` and `injectAsync()` call happens **before its first `await`**: ```ts twoslash import { Container, inject, injectAsync } from "@needle-di/core"; import { FooService } from "./foo.service"; import { BarService } from "./bar.service"; const container = new Container(); await container.runInInjectionContext(async () => { const fooService = inject(FooService); // ✅ still synchronous const barService = injectAsync(BarService); // ✅ started synchronously await barService; inject(FooService); // ❌ throws: no longer in an injection context }); ``` If you need dependencies after an `await`, either inject them up front, or use [manual injection](#manual-injection) instead. ::: [parameter decorators]: https://github.com/tc39/proposal-class-method-parameter-decorators --- --- url: https://needle-di.io/concepts/tokens.md description: >- Injection tokens in Needle DI: class constructor references, strings, symbols and typed InjectionToken instances. --- # Tokens An injection token is a reference to a service in the dependency injection (DI) container. This token is used to bind something to the container, and to obtain something from the container. Needle DI allows you to use many different types of tokens. ## Class constructor reference When the service that you provide is a class, you can use its constructor reference as a token. ```ts container.bind({ provide: FooService, useValue: new FooService(), }); ``` However, this is not always a viable option. For example, if you want to provide a primitive value or an object literal, using a class reference as token is not allowed. Therefore, Needle DI offers some alternatives. > \[!NOTE] > Note that TypeScript interfaces only exist compile-time, and therefore **cannot** be used as an injection token. ## `string` and `symbol` You can also use any `string` or `symbol` as injection token: ```ts twoslash import { Container } from "@needle-di/core"; import { MyConfig } from "./my-config"; // create some tokens const MY_CONFIG = "my-config"; const MY_MAGIC_NUMBER = Symbol("my-magic-number"); const container = new Container(); // bind some values using providers container.bind({ provide: MY_CONFIG, useValue: { foo: "bar", }, }); container.bind({ provide: MY_MAGIC_NUMBER, useValue: 42, }); // retrieve the values by their tokens const myConfig = container.get(MY_CONFIG); const myNumber = container.get(MY_MAGIC_NUMBER); ``` > \[!WARNING] > When using a `string` or `symbol` as token, Needle DI will not be able to infer its associated type, unless you > provide the generic type yourself (as shown in the example above). > > Note that this can easily lead to inconsistency and mistakes. ## `InjectionToken` Instead of `string` or `symbol`, a better alternative is to construct an instance of `InjectionToken`. This is basically a unique token object, that is used by reference. > \[!TIP] > When using TypeScript, this token can also hold a generic type. This enables better type-checking. ```ts twoslash import { Container, InjectionToken } from "@needle-di/core"; import { MyConfig } from "./my-config"; // create some injection tokens const MY_NUMBER = new InjectionToken("MY_NUMBER"); const MY_CONFIG = new InjectionToken("MY_CONFIG"); const container = new Container(); // bind some values using providers container.bind({ provide: MY_NUMBER, useValue: 42, // should satisfy `number` }); container.bind({ provide: MY_CONFIG, useValue: { foo: "bar" }, // should satisfy `MyConfig` }); // retrieve the values by their tokens const myNumber = container.get(MY_NUMBER); // ^? Type will be inferred as `number` const myConfig = container.get(MY_CONFIG); // ^? Type will be inferred as `MyConfig` // ``` This maximizes type-safety since both `container.bind()`, `container.get()` and `inject()` will check and infer the types associated with the injection token. This is not the only benefit: it also enables [tree-shakable injection tokens](/advanced/tree-shaking). --- --- url: https://needle-di.io/advanced/optional-injection.md description: >- Use inject(token, { optional: true }) to get undefined instead of an error when no binding exists. --- # Optional injection By default, when you try to inject something that isn't provided, Needle DI will throw an error. Alternatively, you can use optional injection, by passing `{ optional: true }`. Instead of throwing an error, it will now return the requested service, or `undefined` if not found: ```ts twoslash import { inject } from "@needle-di/core"; import { FooService } from "./foo.service"; import { BarService } from "./bar.service"; class MyService { constructor( private fooService = inject(FooService), private barService = inject(BarService, { optional: true }), // ^? Type will be inferred as `BarService | undefined` ) {} } ``` ## Outside the injection context When you construct an instance of `MyService` manually outside the injection context, and you don't pass any argument for an optional dependency, the `inject()` function will not throw an error, but gracefully return `undefined` instead: ```ts twoslash import { inject } from "@needle-di/core"; import { BarService } from "./bar.service"; class MyService { constructor( private barService = inject(BarService, { optional: true }), ) {} } const myService = new MyService(); // "barService" will be undefined. ``` --- --- url: https://needle-di.io/advanced/multi-injection.md description: >- Bind multiple values to the same token with { multi: true } and inject them all as an array. --- # Multi-injection By default, when you bind an existing token again, it will overwrite any previous binding. However, it is also possible to register multiple values for the same token: ```ts twoslash import { Container } from "@needle-di/core"; import { FooService } from "./foo.service"; const container = new Container(); container.bind({ provide: FooService, multi: true, useFactory: () => new FooService(), }); container.bind({ provide: FooService, multi: true, useFactory: () => new FooService(), }); ``` To inject both instances, you can pass `{ multi: true }` to the `inject()` function: ```ts twoslash import { inject } from "@needle-di/core"; import { FooService } from "./foo.service"; class MyService { constructor( private fooServices = inject(FooService, { multi: true }), // ^? Type will be inferred as `FooService[]` ) {} } ``` ## Behaviour & limitations There are some rules associated with multi-providers: * It is not allowed to combine multiple providers for the same token with both `multi: false` and `multi: true`. * When you specify only one provider with `multi: true`, it is still allowed to inject it as a single instance. * When you specify multiple providers with `multi: true`, it will throw an error when you try to inject a single instance. * When you try to inject with `multi: true` and `optional: true`, and there are no providers, it will still return `undefined` instead of an empty array. --- --- url: https://needle-di.io/advanced/async-injection.md description: >- Use asynchronous factory providers with getAsync() and injectAsync(), and how async dependencies can be injected synchronously afterwards. --- # Async injection It is also possible to use a provider with an asynchronous factory function. ## Async factory providers All you have to do, is passing `async: true`. This will require you to return a `Promise` in your factory function. This allows you to also use an `async` function: ```ts container.bind({ provide: FooService, async: true, useFactory: async () => { // ... returning some `FooService` here }, }); ``` ## `getAsync()` and `injectAsync()` When you want to obtain this from the DI container, you will have to use `container.getAsync(token)` or `injectAsync(token)`. Since this returns a `Promise`, you can use `await` here. ```ts twoslash import { container } from "./container"; import { FooService } from "./foo.service"; const fooService = await container.getAsync(FooService); // ^? // ``` If you try to use `container.get()` or `inject()` for an async provider, an error will be thrown, as these methods only support synchronous injection. This restriction also applies if any of the indirect dependencies is async. ## Synchronous constructor injection You can inject your async dependencies synchronously, as long as you're in an async context. ```ts @injectable() class MyService { constructor( private foo = inject(FOO_TOKEN), private bar = inject(BAR_TOKEN), ) {} public printTokens(): string { return `${this.foo} and ${this.bar}`; } } const FOO_TOKEN = new InjectionToken("FOO_TOKEN"); const BAR_TOKEN = new InjectionToken("BAR_TOKEN"); const container = new Container(); container.bindAll( { provide: FOO_TOKEN, async: true, useFactory: () => new Promise((resolve) => { setTimeout(() => resolve("Foo"), 100); }), }, { provide: BAR_TOKEN, async: true, useFactory: () => new Promise((resolve) => { setTimeout(() => resolve("Bar"), 100); }), }, ); const myService = await container.getAsync(MyService); myService.printTokens() // will return "Foo and Bar"; ``` > \[!NOTE] > Async dependencies are resolved sequentially. We may remove this restriction in a later version. --- --- url: https://needle-di.io/advanced/lazy-injection.md description: >- Defer service construction with { lazy: true }, which returns a function that creates the service on demand and enables circular dependencies. --- # Lazy injection Lazy injection allows you to defer the creation of services, by returning a function instead. When invoked, this function will create the service on demand. Lazy injection could aso be a solution to enable circular dependencies. ## Usage In order to use lazy injection, just pass `{ lazy: true }`: ```ts twoslash import { inject } from "@needle-di/core"; import { FooService } from "./foo.service"; class MyService { constructor( private fooService = inject(FooService, { lazy: true }), // ^? Type will be inferred as `() => FooService` ) {} public doSomething() { // invoking the function will trigger the creation of `FooService` this.fooService().someMethod(); } } ``` ## Rules and behaviour * Lazy injection can be combined with [optional injection](./optional-injection.md). In that case, it would the function above would be `() => FooService | undefined`; * Lazy injection can also be combined with [async injection](./async-injection.md). In that case, it would the function above would be `() => Promise`; --- --- url: https://needle-di.io/advanced/inheritance.md description: >- Bind and inject services through abstract classes and inheritance, and how to work with interfaces. --- # Inheritance support Needle DI offers extensive support for inheritance, allowing for abstractions and interfaces. ## Auto-binding using `@injectable()` > \[!NOTE] > Auto-binding with `@injectable()` has some limitations, see the note below. Given the following class structure: ```ts abstract class ExampleService { /* ... */ } @injectable() class FooService extends ExampleService { /* ... */ } @injectable() class BarService extends ExampleService { /* ... */ } ``` This will automatically bind `FooService` and `BarService`, but it will also automatically bind two multi-providers for the token `ExampleService`: ```ts twoslash import { container } from "./container"; import { ExampleService, FooService, BarService } from "./example.service"; const fooService = container.get(FooService); const barService = container.get(BarService); // Will be the same instances as "fooService" and "barService': const myServices = container.get(ExampleService, { multi: true }); // ^? ``` > \[!IMPORTANT] > If you inject something using a parent class as token, make sure your subclasses are referenced somewhere. Otherwise, the auto-binding might not work since their > decorators are not invoked. Even worse, your subclasses might not even appear in your final bundle due to > [tree-shaking](/advanced/tree-shaking). > > To prevent this, consider to register your subclasses explicitly using [manual binding](#manual-binding): > > ```ts > container.bindAll(FooService, BarService); > ``` ## Manual binding If you bind something that has a parent class, a multi-provider for the parent class will be registered automatically. Given the following example: ```ts twoslash import { Container } from "@needle-di/core"; abstract class ExampleService { /* ... */ } class FooService extends ExampleService { /* ... */ } class BarService extends ExampleService { /* ... */ } const container = new Container(); container.bindAll( FooService, BarService, ); ``` The container will automatically register the following bindings internally: ```ts container.bindAll( { provide: ExampleService, useExisting: FooService, multi: true, }, { provide: ExampleService, useExisting: BarService, multi: true, } ); ``` This enables you to inject all instances of `ExampleService` using multi-injection: ```ts twoslash import { container } from "./container"; import { ExampleService, FooService, BarService } from "./example.service"; const fooService = container.get(FooService); const barService = container.get(BarService); // Will be the same instances as "fooService" and "barService': const myServices = container.get(ExampleService, { multi: true }); // ^? ``` This even works with multiple levels of inheritance. ## What about interfaces? If you're using TypeScript interfaces instead, you should use [injection tokens](/concepts/tokens#injectiontoken-t) instead. This is because TypeScript interfaces don't exist at runtime and therefore cannot be used as tokens. ```ts twoslash import { Container, InjectionToken } from "@needle-di/core"; interface Logger { info(): void; } class FileLogger implements Logger { info(): void { // } } class ConsoleLogger implements Logger { info(): void { // } } const LOGGER = new InjectionToken('LOGGER'); const container = new Container(); container.bindAll( { provide: LOGGER, multi: true, useClass: FileLogger, }, { provide: LOGGER, multi: true, useClass: ConsoleLogger, }, ); const loggers = container.get(LOGGER, { multi: true }); // ^? ``` --- --- url: https://needle-di.io/advanced/tree-shaking.md description: >- Optimize production bundles with tree-shakeable injection tokens, using the factory option of InjectionToken. --- # Tree-shaking Tree-shaking is an optimization technique used in JavaScript bundlers (like [esbuild], [Webpack] or [Rollup]) to remove unused or dead code from the final bundle. Needle DI has been designed with this in mind. [esbuild]: https://esbuild.github.io/api/#tree-shaking [Webpack]: https://webpack.js.org/guides/tree-shaking/ [Rollup]: https://rollupjs.org/introduction/#tree-shaking ## Tree-shaking: how does it work? It works by analyzing the `import` and `export` statements of modules and ensuring that only the parts of code actually used in the application are included. By "shaking off" unused code, tree-shaking reduces the bundle size, improves load times, and enhances overall performance of the application. ## Tree-shakeable injection tokens Let's imagine a library with a class `SomeHeavyClass` that depends on a lot of code, resulting in a larger bundle size. Since we cannot decorate this class, the alternative is to bind it using an [injection token](/concepts/tokens#injectiontoken-t): ```ts import { InjectionToken, Container } from "@needle-di/core"; import { SomeHeavyClass } from "./some-heavy-library"; const MY_TOKEN = new InjectionToken("MY_TOKEN"); const container = new Container(); container.bind({ provide: MY_TOKEN, useFactory: () => new SomeHeavyClass() }); ``` However, this will **NOT** be tree-shaken; even when there are no references to `MY_TOKEN` and `SomeHeavyClass`. This is because it is still referred by the container itself. However, there also an option to provide a `factory` function in your `InjectionToken`, removing the need to manually bind it to your container: ```ts import { InjectionToken } from "@needle-di/core"; const MY_TOKEN = new InjectionToken( "MY_TOKEN", { // [!code ++] factory: () => new SomeHeavyClass(), // [!code ++] } // [!code ++] ); const container = new Container(); // [!code --] // [!code --] container.bind({ // [!code --] provide: MY_TOKEN, // [!code --] useFactory: () => new SomeHeavyClass() // [!code --] }); // [!code --] ``` This effectively enables [auto-binding](/concepts/binding#auto-binding): since the token holds a factory function, the container will automatically construct it when you obtain it for the first time. But more importantly, this will make your token **tree-shakeable**: when there are no references to your injection token, everything associated with your token will be removed from your bundle. ## When to use? This is mainly relevant if you have multiple [entry points](https://esbuild.github.io/api/#entry-points) in the same codebase, and you create separate bundles for each of them. Or, if you use code splitting (e.g. lazy loaded modules/chunks) for dynamic imports. However, using factory functions in injection tokens can also help you to organize your code in a more modular way. --- --- url: https://needle-di.io/advanced/child-containers.md description: >- Create child containers that inherit providers and singletons from their parent, and override or extend them. --- # Child containers A **child container** is a DI container that inherits all **providers** and **singletons** from its parent (or any ancestor). However, it also allows you to **override specific providers** or define new ones independently. ## Example ```ts twoslash import { Container } from "@needle-di/core"; import { LOGGER, MyLogger, OtherLogger } from "./logger"; const parent = new Container(); const child1 = parent.createChild(); const child2 = parent.createChild(); parent.bind({ provide: LOGGER, useClass: MyLogger }); child2.bind({ provide: LOGGER, useClass: OtherLogger }); const loggerA = parent.get(LOGGER); // `MyLogger` const loggerB = child1.get(LOGGER); // `MyLogger` (same instance as parent) const loggerC = child2.get(LOGGER); // `OtherLogger` ``` ## Rules and behaviour * **Singletons are shared** with child containers (or any descendant) **unless explicitly overridden**. * **Singletons are created in the container where they were first bound**, even if they are accessed from a child container. > \[!NOTE] > If you bind a multi-provider in a child container, its singletons will not be merged with those from the parent. This > is a current limitation, but if you have a strong use case, feel free to [submit an issue]. [submit an issue]: https://github.com/needle-di/needle-di/issues/new --- --- url: https://needle-di.io/ai/agents.md description: >- How to use Needle DI with AI coding agents: LLM-friendly documentation sources and guidance for generating Needle DI code. --- # AI agent instructions Needle DI ships machine-readable documentation, so AI coding agents can work with it without guessing APIs. ## Documentation sources When working on Needle DI-related code, always verify against the current Needle DI documentation. Prefer current documentation over assumptions from memory. | Source | Use it for | | ----------------------------------------------------- | --------------------------------------------------- | | [`/llms.txt`](/llms.txt) | Compact index of all documentation pages | | [`/llms-full.txt`](/llms-full.txt) | The complete documentation as a single Markdown file | | `/.md` (e.g. [`/concepts/binding.md`](/concepts/binding.md)) | The Markdown source of a single page | For external agents, use the absolute URLs: * * Every documentation page also has Copy as Markdown and Download as Markdown buttons, so you can paste a page straight into a chat. Additional sources: * [`@needle-di/core` on npm](https://www.npmjs.com/package/@needle-di/core) * [`@needle-di/core` on JSR](https://jsr.io/@needle-di/core/doc), which contains the generated API reference * [The source code on GitHub](https://github.com/needle-di/needle-di) ## Guidance for agents The following rules cover the mistakes LLMs most often make with Needle DI. They also apply when generating code based on knowledge of other DI libraries such as Angular, NestJS or InversifyJS. ### Do * Use `inject(Token)` as a default parameter value in constructors, initializers or factory functions. * Use `@injectable()` for [auto-binding](/concepts/binding#auto-binding) services with a zero-argument constructor signature. * Use `bind()` with a [provider](/concepts/providers) when a service needs explicit configuration. * Use [`InjectionToken`](/concepts/tokens#injectiontoken-t) for values that are not classes, and pass a `factory` when the token should be [tree-shakeable](/advanced/tree-shaking). * Use [`injectAsync()` and `getAsync()`](/advanced/async-injection) for asynchronous factory providers. * Use [`container.runInInjectionContext()`](/concepts/injection#running-in-an-injection-context) to call `inject()` from a plain function that the container did not construct. * Use [`{ optional: true }`](/advanced/optional-injection), [`{ multi: true }`](/advanced/multi-injection) and [`{ lazy: true }`](/advanced/lazy-injection) instead of hand-rolled alternatives. ### Don't * Don't install or import `reflect-metadata` or any other reflection library. * Don't enable `experimentalDecorators` or `emitDecoratorMetadata`, these are legacy TypeScript decorators. * Don't use parameter decorators such as `@Inject()` or `@Injectable()` from other frameworks, Needle DI has no parameter decorators. * Don't call `inject()` outside an injection context, use `container.get()` or `container.runInInjectionContext()` there instead. * Don't call `inject()` after an `await` inside `container.runInInjectionContext()`, the injection context is only active while the given function runs synchronously. * Don't create a new `Container` per service, bootstrap a single container (or use [child containers](/advanced/child-containers) for scoping). ### Canonical example ```typescript import { Container, inject, injectable } from "@needle-di/core"; @injectable() class FooService {} @injectable() class BarService { constructor(private fooService = inject(FooService)) {} } const container = new Container(); const barService = container.get(BarService); ```