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,
)
containerholds the services, guards, middleware, interceptors, pipes, and error filters.optionsconfigures logging and, when the adapter creates the server, theServeroptions.customServeris aServeryou constructed. The adapter registers services on that instance and returns it frombuild().
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().