Interceptors & Middleware

gRPC
Chapter 6 ยท Interceptors & Middleware

๐Ÿงฉ Interceptors & Middleware

Chapter 5's auth-metadata example checked a token by hand, once, in one method. This chapter shows how to avoid repeating that in every method โ€” interceptors, gRPC's direct structural equivalent to the Express middleware chain (express1-3).

The Problem: Repeating Logic in Every Method

Without interceptors, every single RPC method implementation would need to manually check auth metadata, log the call, and handle retries โ€” repetitive, error-prone, and one new method away from someone forgetting the auth check entirely. This is exactly why Express middleware exists in the first place (express1-3): cross-cutting concerns don't belong duplicated inside every route handler.

Server Interceptors

A server interceptor wraps around every incoming call before it reaches the actual method handler โ€” the same role app.use() middleware plays before Express route handlers run.

// A server interceptor checking auth ONCE, for every method function authInterceptor(call, next) { const token = call.metadata.get('authorization')[0]; if (!isValidToken(token)) { throw new Error(grpc.status.UNAUTHENTICATED); } return next(call); }

Every method handler defined in Chapter 3 now runs behind this check โ€” none of them need to repeat the token-parsing logic Chapter 5's example wrote inline.

Client Interceptors

A client interceptor wraps around every outgoing call before it's actually sent โ€” common uses include automatically attaching auth metadata to every call, retries with backoff, and logging.

// A client interceptor auto-attaching auth to every outgoing call function authClientInterceptor(options, nextCall) { return new InterceptingCall(nextCall(options), { start(metadata, listener, next) { metadata.add('authorization', `Bearer ${getToken()}`); next(metadata, listener); } }); }

Client code calling getProduct() no longer needs to manually build and pass a Metadata object with a token on every call site the way Chapter 5's raw example did โ€” the interceptor attaches it automatically, exactly once, in one place.

Express Middleware vs. gRPC Interceptors

Express Middleware

app.use() registers a chain that runs before route handlers, each calling next() to pass control along โ€” one direction, server-side only.

gRPC Interceptors

The same wrap-before-the-real-handler concept, but on both sides โ€” server interceptors wrap incoming calls, client interceptors wrap outgoing ones.

Use CaseServer or Client?Example
Central auth checkServerReject unauthenticated calls before any handler runs
Auto-attaching auth metadataClientEvery outgoing call gets a token automatically
Request/response loggingEitherLog every call attempt, regardless of outcome
Automatic retriesClientRetry a call on UNAVAILABLE with backoff

Server Interceptor Responsibilities

Anything that should apply uniformly to every incoming call before business logic runs โ€” auth, rate limiting, request logging.

Client Interceptor Responsibilities

Anything that should apply to every outgoing call automatically โ€” attaching credentials, retry logic, response-time logging.

๐Ÿ’ป Coding Challenges

Challenge 1: Refactor Chapter 5's Auth Check

Chapter 5's getProduct handler manually checked auth metadata inline. Rewrite this as a server interceptor instead, so the handler itself no longer needs any auth-checking code.

Goal: Practice moving repeated per-method logic into a centralized interceptor.

โ†’ Solution

Challenge 2: Server or Client Interceptor?

For each, say whether it belongs as a server interceptor or a client interceptor: (a) logging how long every call took to complete, (b) rejecting calls that exceed a rate limit, (c) automatically retrying a failed call up to 3 times.

Goal: Practice distinguishing responsibilities that belong on the receiving side from ones that belong on the calling side.

โ†’ Solution

Challenge 3: Explain Interceptor Ordering

A team places their auth interceptor before their logging interceptor. Explain what this means for calls that get rejected by the auth interceptor, and propose a reordering if they want to log every attempt, including rejected ones.

Goal: Practice reasoning about interceptor order the same way you'd reason about Express middleware order.

โ†’ Solution

โš ๏ธ Gotcha: Interceptor Order Matters, Just Like Express Middleware Order

Interceptors run in a chain, and โ€” exactly like Express middleware โ€” the order they're registered in determines what each one sees. If an auth interceptor runs first and rejects a call, any interceptor registered after it (a logging interceptor, say) never runs at all for that call, since the chain short-circuits at the rejection. This means a logging interceptor placed after auth silently misses every rejected/unauthenticated attempt โ€” often exactly the calls worth logging most. If visibility into every attempt (including rejected ones) matters, logging needs to run before auth in the chain, not after โ€” the same ordering discipline Express developers already apply to their own middleware stacks.

๐ŸŽฏ What's Next

The next chapter is Authentication & Security in gRPC โ€” TLS by default, token-based auth via metadata in practice, and mutual TLS for service-to-service calls.