Skip to main content
Version: 7.x

Strongly Typed Container

The @inversifyjs/strongly-typed package adds TypeScript type definitions that bring compile-time type safety to your InversifyJS Container and @inject() decorators. With this package, you define a binding map that serves as a contract for your container, ensuring that bindings, retrievals, and injections are all type-checked at compile time.

When to use it​

Use the strongly-typed container when you want to catch binding and injection errors at compile time instead of runtime. You'll get IDE autocomplete for service identifiers and ensure type safety across your entire dependency injection setup. The binding map serves as living documentation of available services and prevents accidental injection of wrong types.

If you prefer maximum flexibility or are working with highly dynamic binding scenarios, the standard container may be more appropriate.

Key features​

Type-safe bindings​

Bind services with compile-time validation that ensures you're registering the correct types for each service identifier.

Type-safe retrieval​

Get services with automatic type inference so your IDE knows exactly what type you're working with.

Type-safe injection​

Use strongly-typed @inject() decorators for constructor and property injection that verify the injected type matches the parameter or property type.

Promise support​

Distinguish between sync and async bindings at the type level, ensuring you use the correct retrieval method.

Zero runtime overhead​

All type checking happens at compile time, so there's no performance penalty in production.

Flexible usage​

Can be used as a direct import or as type assertions to keep the library out of your final dependency tree.

Installation​

npm install @inversifyjs/strongly-typed

Quick start​

Define your binding map​

Start by defining a binding map that describes all the services your container will provide:

interface Foo {
foo: string;
}

interface Bar {
bar: string;
}

interface BindingMap {
foo: Foo;
bar: Bar;
}

Create a typed container​

You can instantiate a TypedContainer directly:

import { TypedContainer } from '@inversifyjs/strongly-typed';

const container = new TypedContainer<BindingMap>();

Or use type assertions to keep the library out of your dependency tree:

import { Container } from 'inversify';
import type { TypedContainer } from '@inversifyjs/strongly-typed';

const container = new Container() as TypedContainer<BindingMap>;

Type-safe bindings​

All bindings are now type-checked:

// ✅ Valid - correct type
container.bind('foo').toConstantValue({ foo: 'abc' });

// ❌ Compilation error - wrong type
container.rebind('foo').toConstantValue({ unknown: 'uh-oh' });

// ❌ Compilation error - unknown identifier
container.bind('unknown').toConstantValue({ foo: 'xyz' });

Type-safe retrieval​

Service retrieval is fully type-checked with automatic type inference:

// ✅ Valid - inferred type is Foo
const foo = container.get('foo');

// ❌ Compilation error - bar is not assignable to Foo
const wrongType: Foo = container.get('bar');

// ❌ Compilation error - unknown identifier
const invalid = container.get('unknown-identifier');

Strongly-typed injection​

To use strongly-typed decorators, re-export the inject and multiInject decorators with type assertions:

import { inject, multiInject } from 'inversify';
import type { TypedInject, TypedMultiInject } from '@inversifyjs/strongly-typed';

export const $inject = inject as TypedInject<BindingMap>;
export const $multiInject = multiInject as TypedMultiInject<BindingMap>;

Constructor injection​

Use the typed decorator for constructor parameters:

import { injectable } from 'inversify';

@injectable()
class MyService {
constructor(
@$inject('foo') // ✅ Valid
foo: Foo,

@$inject('foo') // ❌ Compilation error - foo is not assignable to Bar
bar: Bar,
) {}
}

Property injection​

Property injection works for public properties:

@injectable()
class MyService {
@$inject('foo') // ✅ Valid
public foo: Foo;

@$inject('foo') // ❌ Compilation error - wrong type
public bar: Bar;
}
note

Private properties cannot be strongly typed due to TypeScript decorator limitations. Use public properties (optionally prefixed with underscore) or fall back to the regular @inject() decorator for private properties.

Advanced usage​

Promise bindings​

InversifyJS allows binding Promises, but they must be retrieved using getAsync(). The type system enforces this:

interface BindingMap {
number: number;
asyncNumber: Promise<number>;
}

const container = new TypedContainer<BindingMap>();

// ✅ Valid - sync binding with sync method
const num = container.get('number'); // number

// ❌ Compilation error - can't use get() for Promise bindings
const asyncNum = container.get('asyncNumber');

// ✅ Valid - async binding with async method
const asyncNumCorrect = await container.getAsync('asyncNumber'); // number

Container hierarchies​

When creating child containers with parents, manually merge the binding maps:

type ParentMap = {
parentService: ParentService;
};

type ChildMap = {
childService: ChildService;
};

const parent = new TypedContainer<ParentMap>();
const child = new TypedContainer<ParentMap & ChildMap>({ parent });

// Child can access both parent and child bindings
const parentSvc = child.get('parentService');
const childSvc = child.get('childService');

Container modules​

Create strongly-typed container modules:

import { TypedContainerModule } from '@inversifyjs/strongly-typed';

const myModule = new TypedContainerModule<BindingMap>(
(bind) => {
bind('foo').toConstantValue({ foo: 'value' });
bind('bar').toConstantValue({ bar: 'value' });
}
);

await container.load(myModule);

Known limitations​

Private properties​

Due to TypeScript decorator limitations, private properties cannot be strongly typed with injection decorators:

@injectable()
class MyService {
@$inject('foo')
private foo: Foo; // ❌ Compilation error
}

You can work around this by making the property public (consider using an underscore prefix as a convention like public _foo) or by using the regular @inject() decorator from inversify for private properties.

Constructor error messages​

When constructor injection types are incorrect, TypeScript produces generic error messages like:

Unable to resolve signature of parameter decorator when called as an expression.
Argument of type '2' is not assignable to parameter of type 'undefined'.

This indicates that the constructor parameter at index 2 has the wrong type for the injected identifier.

Benefits​

Earlier error detection​

Catch type mismatches during development, not in production. The TypeScript compiler becomes your first line of defense against injection mistakes.

Better IDE support​

Get autocomplete for service identifiers and automatic type inference. Your editor knows exactly what types are available and what each service returns.

Self-documenting​

The binding map serves as documentation of available services. New team members can look at the type definition to understand what services the container provides.

Refactoring safety​

The type system catches broken references when refactoring. Rename a service identifier and the compiler will show you everywhere that needs updating.

Zero runtime cost​

All benefits come from compile-time type checking. There's no additional JavaScript generated and no performance overhead at runtime.

Package information​