Skip to main content

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() and container.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.