Skip to main content

Interceptor

Interceptors wrap the RPC method. Code before await next() runs after guards and before the method. Code after await next() runs after the method returns and before post-handler middleware.

interface Interceptor<TRequest = any, TResponse = any> {
intercept(
request: TRequest,
response: TResponse,
next: () => Promise<InterceptorTransformObject>,
): Promise<void>;
}

interface InterceptorTransformObject {
push: (transform: (value: unknown) => unknown) => void;
}

request is the @grpc/grpc-js call. response is the callback for unary and client-streaming RPCs, and the call for streaming responses.

transform.push() changes the value the method returns before the adapter sends it. That applies to unary and client-streaming methods that return a message and do not use @Callback(). Streamed messages and callback replies stay as the method wrote them. Skip next() to skip the method. Throw a GrpcError to finish the call with that status.

Add @injectable() and bind the interceptor.

import {
type Interceptor,
type InterceptorTransformObject,
RPC,
Service,
UseInterceptor,
} from '@inversifyjs/grpc-core';
import { injectable } from 'inversify';

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

function isHeroResponse(value: unknown): value is HeroResponse {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
typeof value.name === 'string'
);
}

@injectable()
export class SuffixInterceptor implements Interceptor {
public async intercept(
_call: unknown,
_response: unknown,
next: () => Promise<InterceptorTransformObject>,
): Promise<void> {
const transform: InterceptorTransformObject = await next();

transform.push((value: unknown): unknown => {
if (!isHeroResponse(value)) {
return value;
}

return {
name: `${value.name}!`,
};
});
}
}

@Service(heroServiceDefinition)
@UseInterceptor(SuffixInterceptor)
export class InterceptedHeroService {
@RPC('GetHero')
public getHero(call: { request: HeroRequest }): HeroResponse {
return {
name: call.request.id,
};
}
}

GetHero('hero-1') returns { name: 'hero-1!' }.

@UseInterceptor() on the class wraps every RPC. On a method, it wraps that RPC. Class interceptors are outside method interceptors. Global interceptors, registered with useGlobalInterceptors() before build(), sit inside both.