Service
A service is a class decorated with @Service(). The decorator records the gRPC service definition and applies @injectable(), so you do not add @injectable() yourself.
function Service(
definition: GrpcServiceDefinition,
options?: ServiceOptions,
): ClassDecorator
definition is the object you would pass to server.addService(). Each key is an RPC name. @RPC() on the class, including inherited methods, must cover every key, and every @RPC() name must be one of those keys.
options accepts:
scope:'Singleton','Transient', or'Request'.container.bind(Service).toSelf()andcontainer.bind(id).to(Service)use this scope. When you omit it, the container default applies.serviceIdentifier: the identifier the adapter resolves. The default is the class.
Bind that identifier before build(). Unbound @Service() classes are skipped. When the process has no @Service() classes at all, build() throws.
Dependency injection
Constructor injection works the same way as in any Inversify service. Bind the dependencies and the service.
import { RPC, Service } from '@inversifyjs/grpc-core';
import { inject, injectable } from 'inversify';
import {
type HeroRequest,
type HeroResponse,
heroServiceDefinition,
} from './loadHeroServiceDefinition.js';
@injectable()
export class HeroRepository {
public findName(id: string): string {
return id;
}
}
@Service(heroServiceDefinition)
export class RepositoryHeroService {
readonly #heroRepository: HeroRepository;
constructor(@inject(HeroRepository) heroRepository: HeroRepository) {
this.#heroRepository = heroRepository;
}
@RPC('GetHero')
public getHero(call: { request: HeroRequest }): HeroResponse {
return {
name: this.#heroRepository.findName(call.request.id),
};
}
}
Scope and service identifier
import { RPC, Service } from '@inversifyjs/grpc-core';
import {
type HeroRequest,
type HeroResponse,
heroServiceDefinition,
} from './loadHeroServiceDefinition.js';
export const heroServiceIdentifier: symbol = Symbol.for(
'@example/hero-service',
);
@Service(heroServiceDefinition, {
scope: 'Singleton',
serviceIdentifier: heroServiceIdentifier,
})
export class SingletonHeroService {
public static instanceCount: number = 0;
constructor() {
SingletonHeroService.instanceCount += 1;
}
@RPC('GetHero')
public getHero(call: { request: HeroRequest }): HeroResponse {
return {
name: call.request.id,
};
}
}
Bind the custom identifier to the class:
container.bind(heroServiceIdentifier).to(SingletonHeroService);
scope: 'Singleton' makes that binding a singleton, so every RPC shares one instance.
Inheritance
@RPC() methods are inherited. A subclass method with the same RPC name replaces the parent method. Put @Service() on the class you bind.
import { RPC, Service } from '@inversifyjs/grpc-core';
import {
type HeroRequest,
type HeroResponse,
heroServiceDefinition,
} from './loadHeroServiceDefinition.js';
class ParentHeroService {
@RPC('GetHero')
public getHero(_call: { request: HeroRequest }): HeroResponse {
return {
name: 'parent',
};
}
}
@Service(heroServiceDefinition)
export class InheritedHeroService extends ParentHeroService {}
@Service(heroServiceDefinition)
export class OverridingHeroService extends ParentHeroService {
@RPC('GetHero')
public override getHero(call: { request: HeroRequest }): HeroResponse {
return {
name: call.request.id,
};
}
}
InheritedHeroService serves GetHero with the parent method. OverridingHeroService serves it with the subclass method. Bind only one of them in a container. Both classes use the same service definition, and @grpc/grpc-js accepts that definition once per server.