Migrating from v6
InversifyJS v8 introduces several breaking changes. This guide will help you migrate your existing InversifyJS v6 code to v8.
Overview of Changesâ
The following table summarizes the key changes from v6 to v8:
| v6 | v8 | Comment |
|---|---|---|
autoBindInjectable option | autobind option | Renamed container constructor option |
container.resolve(X) | container.get(X, { autobind: true }) | resolve replaced by get with autobind option |
| Custom metadata and middlewares | No direct replacement | Removed to simplify the library and avoid exposing internal data structures |
container.isBoundNamed | container.isBound(X, { name: ... }) | Merged all isBound variants with an options parameter |
container.isBoundTagged | container.isBound(X, { tag: ... }) | Merged all isBound variants with an options parameter |
container.getNamed | container.get(X, { name: ... }) | Merged all get variants with an options parameter |
container.getTagged | container.get(X, { tag: ... }) | Merged all get variants with an options parameter |
container.tryGet | container.get(X, { optional: true }) | Added optional flag for optional get |
container.tryGetNamed | container.get(X, { name: ..., optional: true }) | Combined named and optional parameters |
container.tryGetTagged | container.get(X, { tag: ..., optional: true }) | Combined tagged and optional parameters |
container.createChild() | new Container({ parent: container }) | Child containers created via constructor |
.toAutoFactory(X) | .toFactory((ctx) => () => ctx.get(X)) | Use toFactory with ResolutionContext.get() |
.toAutoNamedFactory(X) | .toFactory((ctx) => (name) => ctx.get(X, { name })) | Use toFactory with named resolution |
.toConstructor(X) | .toConstantValue(X) | Bind constructors as constant values |
.toFunction(fn) | .toConstantValue(fn) | Bind functions as constant values |
interfaces.Context | ResolutionContext | Updated context parameter type in the binding API |
interfaces.Request | BindingConstraints | Updated binding constraint parameter type |
interfaces.Provider<T> | Removed | Use Factory<T> with toFactory and async functions instead |
interfaces.Factory<T> | Factory<T> | Directly exported, no longer under interfaces namespace |
interfaces.Newable<T> | Newable<T> | Directly exported, no longer under interfaces namespace |
| Implicit injection inheritance | @injectFromBase decorator | Explicit inheritance with a decorator |
import { interfaces } from "inversify"; | import { ... } from "inversify"; | Removed interfaces namespace, use direct imports |
| CJS/ESM dual package | ESM only | Package is now ESM only; Node.js LTS supports require(esm) |
.toProvider(...) | .toFactory(...) with async function | Provider type and toProvider binding removed; use toFactory instead |
Package Formatâ
InversifyJS v8 is now an ESM-only package. For most users this is a non-breaking change: all active Node.js LTS versions (20.x and above) support require(esm), meaning CommonJS projects can still consume the package without any changes.
Refer to the Migrating the ecosystem to ES modules blog post for a broader overview of the CJS â ESM migration landscape.
Container APIâ
Autobindingâ
In v6, autobinding was enabled by passing the autoBindInjectable option to the container constructor. In v8, this option has been renamed to autobind and can be passed either as part of the Container constructor options or Container.get options.
In v6, container.resolve automatically bound the resolved service to the container. In v8, this behavior has been removed. To enable it, pass the autobind option.
export class Katana {
public readonly damage: number = 10;
}
@injectable()
export class Samurai {
public readonly katana: Katana;
constructor(katana: Katana) {
this.katana = katana;
}
}
const container: Container = new Container();
const samurai: Samurai = container.get(Samurai, { autobind: true });
Custom Metadata and Middlewaresâ
This feature has been removed in v8 with no direct replacement. It was not widely used and contributed to the library's complexity. A better API may be introduced in the future.
isBound-like Methodsâ
Methods like Container.isBoundNamed and Container.isBoundTagged have been replaced by Container.isBound with an optional isBoundOptions parameter to handle named and tagged bindings.
Refer to the API documentation for isBound and isCurrentBound for more details.
get-like Methodsâ
The Container.getNamed, Container.getTagged, Container.tryGet, Container.tryGetNamed, and Container.tryGetTagged methods have been replaced by Container.get with an OptionalGetOptions parameter.
Similarly, Container.getAll, Container.getAllAsync, and Container.getAsync now accept a GetOptions object to specify names or tags.
const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana).whenNamed('Katana');
const katana: Weapon = container.get<Weapon>('Weapon', { name: 'Katana' });
Additionally, Container.getAll and Container.getAllAsync now enforce binding constraints. For example:
const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana);
container.bind<Weapon>('Weapon').to(Shuriken).whenNamed('ranged');
const weapons: Weapon[] = container.getAll<Weapon>('Weapon');
In v6, container.getAll returned all bindings matching the service identifier. In v8, it only returns bindings that match both the service identifier and binding constraints.
If you need to simulate the old behavior, you can use custom constraints to accomplish this. Refer to this issue for more information.
Parent and Child Containersâ
In v6, child containers were created using the createChild method. In v8, this method has been removed. Instead, pass the parent container to the constructor of the child container.
class Katana {}
const parentContainer: Container = new Container();
parentContainer.bind(weaponIdentifier).to(Katana);
const childContainer: Container = new Container({ parent: parentContainer });
const katana: Katana = childContainer.get(weaponIdentifier);
ContainerModule APIâ
Container module load options are now passed as an object.
const warriorsModule: ContainerModule = new ContainerModule(
(options: ContainerModuleLoadOptions) => {
options.bind<Ninja>('Ninja').to(Ninja);
},
);
const weaponsModule: ContainerModule = new ContainerModule(
(options: ContainerModuleLoadOptions) => {
options.bind<Katana>('Weapon').to(Katana).whenNamed('Melee');
options.bind<Shuriken>('Weapon').to(Shuriken).whenNamed('Ranged');
},
);
container.load(warriorsModule, weaponsModule);
const ninja: Ninja = container.get('Ninja');
BindingFluentSyntax APIâ
ResolutionContext Instead of interfaces.Contextâ
The Context class has been replaced by ResolutionContext to simplify the API and hide internal data structures.
@injectable()
class Katana {
public use(): string {
return 'hit!';
}
}
container
.bind<Katana>('Katana')
.to(Katana)
.onActivation((_context: ResolutionContext, katana: Katana) => {
const handler: ProxyHandler<() => string> = {
apply: function (
target: () => string,
thisArgument: unknown,
argumentsList: [],
) {
console.log(`Starting: ${new Date().getTime().toString()}`);
const result: string = target.apply(thisArgument, argumentsList);
console.log(`Finished: ${new Date().getTime().toString()}`);
return result;
},
};
katana.use = new Proxy(katana.use.bind(katana), handler);
return katana;
});
BindingConstraints Instead of interfaces.Requestâ
The Request object has been replaced by BindingConstraints to simplify the API and hide internal data structures.
const ninjaId: symbol = Symbol.for('Ninja');
const weaponId: symbol = Symbol.for('Weapon');
@injectable()
class Ninja {
constructor(
@inject(weaponId)
@named('shuriken')
public readonly weapon: Weapon,
) {}
}
container.bind<Ninja>(ninjaId).to(Ninja);
const whenTargetNamedConstraint: (
name: string,
) => (bindingconstraints: BindingConstraints) => boolean =
(name: string) =>
(bindingconstraints: BindingConstraints): boolean =>
bindingconstraints.name === name;
container
.bind<Weapon>(weaponId)
.to(Katana)
.when(whenTargetNamedConstraint('katana'));
container
.bind<Weapon>(weaponId)
.to(Shuriken)
.when(whenTargetNamedConstraint('shuriken'));
const ninja: Ninja = container.get(ninjaId);
// Returns 5
const ninjaDamage: number = ninja.weapon.damage;
Removed Binding Methodsâ
Several binding methods have been removed in v8 to simplify the API. Here's how to migrate each one:
toAutoFactoryâ
The toAutoFactory method has been removed. Use toFactory with ResolutionContext.get() instead:
@injectable()
export class Ninja {
readonly #katana: Katana;
constructor(@inject('Factory<Katana>') katanaFactory: () => Katana) {
this.#katana = katanaFactory();
}
public fight(): string {
return this.#katana.hit();
}
}
export const container: Container = new Container();
container.bind('Katana').to(Katana);
// v6: container.bind('Factory<Katana>').toAutoFactory<Katana>('Katana');
container
.bind<() => Katana>('Factory<Katana>')
.toFactory(
(context: ResolutionContext) => () => context.get<Katana>('Katana'),
);
container.bind(Ninja).toSelf();
toAutoNamedFactoryâ
Similarly, toAutoNamedFactory has been removed. Use toFactory with named resolution:
@injectable()
export class Ninja {
readonly #katana: Katana;
readonly #shuriken: Shuriken;
constructor(
@inject('Factory<Weapon>')
weaponFactory: (name: MetadataName) => Weapon,
) {
this.#katana = weaponFactory('katana') as Katana;
this.#shuriken = weaponFactory('shuriken') as Shuriken;
}
public fight(): string {
return this.#katana.hit();
}
public sneak(): string {
return this.#shuriken.throw();
}
}
export const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana).whenNamed('katana');
container.bind<Weapon>('Weapon').to(Shuriken).whenNamed('shuriken');
// v6: container.bind('Factory<Weapon>').toAutoNamedFactory<Weapon>('Weapon');
container
.bind<(name: MetadataName) => Weapon>('Factory<Weapon>')
.toFactory(
(context: ResolutionContext) => (name: MetadataName) =>
context.get<Weapon>('Weapon', { name }),
);
container.bind(Ninja).toSelf();
toConstructorâ
The toConstructor method has been removed. Since a constructor is just a value (a callable one), use toConstantValue instead:
const container: Container = new Container();
// v6: container.bind('WeaponConstructor').toConstructor(Katana);
container.bind<Newable<Weapon>>('WeaponConstructor').toConstantValue(Katana);
export const weaponClass: Newable<Weapon> =
container.get<Newable<Weapon>>('WeaponConstructor');
export const weapon: Weapon = new weaponClass();
toFunctionâ
The toFunction method has been removed. Since a function is just a value, use toConstantValue instead:
function greet(name: string): string {
return `Hello, ${name}!`;
}
const container: Container = new Container();
// v6: container.bind('greet').toFunction(greet);
container.bind<typeof greet>('greet').toConstantValue(greet);
export const greetFn: (name: string) => string = container.get('greet');
export const greeting: string = greetFn('World');
toProviderâ
The toProvider binding method and the Provider type have been removed. Providers were factories that returned a Promise<T>, so the same behavior can be achieved with toFactory using an async function:
export const container: Container = new Container();
container.bind<Sword>('Sword').to(Katana);
// v6: container.bind<SwordProvider>('SwordProvider').toProvider<Sword>((context: interfaces.Context) => { ... });
container
.bind<SwordProvider>('SwordProvider')
.toFactory((context: ResolutionContext) => {
return async (material: string, damage: number): Promise<Sword> => {
return new Promise<Sword>(
(resolve: (value: Sword | PromiseLike<Sword>) => void) => {
setTimeout(() => {
const sword: Sword = context.get<Sword>('Sword');
sword.material = material;
sword.damage = damage;
resolve(sword);
}, 10);
},
);
};
});
Decorators APIâ
Inheritanceâ
Injection inheritance is now explicit using the @injectFromBase decorator. This provides more control and avoids edge cases related to constructor argument mismatches.
For more details, refer to the Inheritance documentation.