Interceptors & Middleware

gRPC — 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.