Skip to main content

Middleware

Middleware runs before or after the RPC method. The call is the @grpc/grpc-js server call. For unary and client-streaming RPCs the response argument is the callback. For streaming responses it is the call again. next continues the chain.

interface Middleware<
TRequest = any,
TResponse = any,
TNextFunction = any,
TResult = any,
> {
execute(
request: TRequest,
response: TResponse,
next: TNextFunction,
): Promise<TResult> | TResult;
}

Call next() to continue. Throw a GrpcError to finish the call with that status. Pre-handler middleware runs before guards. Post-handler middleware runs after the RPC method returns, which is after a returned unary or client-streaming message has been sent.

Add @injectable() to the class and bind it. @ApplyMiddleware() selects the phase. A service identifier with no options runs in the pre-handler phase.

import { Metadata, type MetadataValue } from '@grpc/grpc-js';
import {
ApplyMiddleware,
type Middleware,
MiddlewarePhase,
RPC,
Service,
UnauthenticatedGrpcError,
} from '@inversifyjs/grpc-core';
import { injectable } from 'inversify';

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

interface HeroCall {
metadata: Metadata;
request: HeroRequest;
}

@injectable()
export class AuthorizationMiddleware implements Middleware {
public execute(call: HeroCall, _response: unknown, next: () => void): void {
const authorization: MetadataValue[] = call.metadata.get('authorization');

if (authorization.length === 0) {
throw new UnauthenticatedGrpcError();
}

next();
}
}

@injectable('Singleton')
export class AuditMiddleware implements Middleware {
public readonly ids: string[] = [];

public execute(call: HeroCall, _response: unknown, next: () => void): void {
this.ids.push(call.request.id);
next();
}
}

@Service(heroServiceDefinition)
@ApplyMiddleware(AuthorizationMiddleware)
@ApplyMiddleware({
middleware: AuditMiddleware,
phase: MiddlewarePhase.PostHandler,
})
export class MiddlewareHeroService {
@RPC('GetHero')
public getHero(call: { request: HeroRequest }): HeroResponse {
return {
name: call.request.id,
};
}
}

AuthorizationMiddleware runs before GetHero. AuditMiddleware runs after it and records the hero id. A call without authorization metadata ends with UNAUTHENTICATED, and the audit middleware does not run.

@ApplyMiddleware() on the class covers every RPC. The same decorator on a method covers that RPC. Global middleware uses applyGlobalMiddleware() before build(). Global pre-handler middleware runs before class and method middleware. Global post-handler middleware runs after them.