Skip to main content

InversifyGrpcJsAdapter

InversifyGrpcJsAdapter connects an Inversify container to a @grpc/grpc-js Server. Import it from @inversifyjs/grpc-js.

constructor(
container: Container,
options?: InversifyGrpcJsAdapterOptions,
customServer?: Server,
)
  • container holds the services, guards, middleware, interceptors, pipes, and error filters.
  • options configures logging and, when the adapter creates the server, the Server options.
  • customServer is a Server you constructed. The adapter registers services on that instance and returns it from build().
interface InversifyGrpcJsAdapterOptions {
logger?: boolean | Logger;
serverOptions?: ServerOptions;
}

Omit logger to use a ConsoleLogger, which prints each service and its RPC kind during build(). Set logger to false to turn that logging off, including logs for unhandled errors. Set it to a Logger to use your own.

serverOptions is passed to new Server(serverOptions) when you omit customServer. A server you pass in keeps the options from its own constructor.

import { type Server } from '@grpc/grpc-js';
import { InversifyGrpcJsAdapter } from '@inversifyjs/grpc-js';
import { ConsoleLogger } from '@inversifyjs/logger';
import { type Container } from 'inversify';

const maxReceiveMessageLength: number = 1_048_576;

export function createAdapter(container: Container): InversifyGrpcJsAdapter {
return new InversifyGrpcJsAdapter(container, {
logger: new ConsoleLogger('hero'),
serverOptions: {
'grpc.max_receive_message_length': maxReceiveMessageLength,
},
});
}

export function createCustomServerAdapter(
container: Container,
server: Server,
): InversifyGrpcJsAdapter {
return new InversifyGrpcJsAdapter(
container,
{
logger: false,
},
server,
);
}

build​

build(): Promise<Server>

Binds the server in the container, registers every bound @Service() class, and returns the Server. Call it once. A second call throws. Bind services and register global handlers before this call.

build() does not bind a port. Call server.bindAsync() with the credentials you want. See Getting started.

The server in the container​

After build(), the server is available as grpcServerServiceIdentifier from @inversifyjs/grpc-core. Inject it into services that are constructed while handling an RPC.

import { Server } from '@grpc/grpc-js';
import {
grpcServerServiceIdentifier,
RPC,
Service,
} from '@inversifyjs/grpc-core';
import { inject } from 'inversify';

import {
type HeroRequest,
type HeroResponse,
heroServiceDefinition,
} from './loadHeroServiceDefinition.js';

@Service(heroServiceDefinition)
export class ServerAwareHeroService {
readonly #server: Server;

constructor(@inject(grpcServerServiceIdentifier) server: Server) {
this.#server = server;
}

@RPC('GetHero')
public getHero(call: { request: HeroRequest }): HeroResponse {
return {
name: this.#server instanceof Server ? call.request.id : 'missing',
};
}
}

Binding your own value to grpcServerServiceIdentifier before build() throws. The adapter owns that binding.

Global handlers​

These methods register handlers for every RPC. Call them before build(). Routes you add on the Server yourself are outside this chain.

applyGlobalMiddleware(
...middlewareList: (ServiceIdentifier<Middleware> | ApplyMiddlewareOptions)[]
): void

applyGlobalGuards(
...guardList: ServiceIdentifier<Guard>[]
): void

useGlobalInterceptors(
...interceptorList: ServiceIdentifier<Interceptor>[]
): void

useGlobalPipe(
...pipeList: (ServiceIdentifier<Pipe> | Pipe)[]
): void

useGlobalFilters(
...errorFilterList: Newable<ErrorFilter>[]
): void

A service identifier with no phase runs as pre-handler middleware. Pass { middleware, phase: MiddlewarePhase.PostHandler } for post-handler middleware. See Middleware, Guard, Interceptor, Pipe, and Error filter for the order and the call types.

import { type InversifyGrpcJsAdapter } from '@inversifyjs/grpc-js';

import { HeroNotFoundErrorFilter } from './errorFilter.js';
import { HeroIdGuard } from './guard.js';
import { SuffixInterceptor } from './interceptor.js';
import { AuthorizationMiddleware } from './middleware.js';
import { UppercasePipe } from './pipe.js';

export function registerGlobalHandlers(adapter: InversifyGrpcJsAdapter): void {
adapter.applyGlobalMiddleware(AuthorizationMiddleware);
adapter.applyGlobalGuards(HeroIdGuard);
adapter.useGlobalInterceptors(SuffixInterceptor);
adapter.useGlobalPipe(UppercasePipe);
adapter.useGlobalFilters(HeroNotFoundErrorFilter);
}

The service below has a single decorated parameter, so the global UppercasePipe only sees the hero id. With an authorization metadata value, GetHero('abc') returns { name: 'hero:ABC!' }. forbidden ends with PERMISSION_DENIED. missing ends with NOT_FOUND. A call without authorization ends with UNAUTHENTICATED.

import { RPC, Service } from '@inversifyjs/grpc-core';

import { HeroNotFoundError } from './errorFilter.js';
import {
type HeroResponse,
heroServiceDefinition,
} from './loadHeroServiceDefinition.js';
import { heroId } from './pipe.js';

@Service(heroServiceDefinition)
export class GlobalHeroService {
@RPC('GetHero')
public getHero(@heroId id: string): HeroResponse {
if (id.includes('MISSING')) {
throw new HeroNotFoundError(id);
}

return {
name: id,
};
}
}

Bind AuthorizationMiddleware, HeroIdGuard, SuffixInterceptor, UppercasePipe, PrefixPipe, HeroNotFoundErrorFilter, and GlobalHeroService before build().