Node.js Backend Development Bootcamp · 강의

인터셉터, 데드라인 및 메타데이터

인터셉터로 인증과 로깅 같은 공통 관심사를 추가하고 데드라인을 적용하며 메타데이터를 전달합니다.

레슨 3/413개 단계

인터셉터, 데드라인 및 메타데이터은(는) CoddyKit의 무료 Node.js Backend Development Bootcamp 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Node.js Backend Development Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Node.js Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Cross-Cutting Concerns in gRPC

Once your gRPC service grows, you'll repeat the same logic in every handler: authentication, logging, timing, error shaping. Copy-pasting that into each method is fragile.

gRPC gives you three tools to handle this cleanly:

  • Interceptors — middleware that wraps every call (client or server side).
  • Metadata — key/value headers that travel alongside the request, perfect for auth tokens and request IDs.
  • Deadlines — an absolute time by which a call must complete, propagated across services.

In this lesson we'll wire all three together with @grpc/grpc-js in Node.js.

What Metadata Actually Is

Metadata is a multimap of string keys to values sent with a gRPC call, similar to HTTP headers. Keys are case-insensitive. Values are usually ASCII strings; keys ending in -bin carry binary Buffer values.

On the client you attach metadata; on the server you read it from the call object. Use it for things that aren't part of the business payload — auth tokens, trace IDs, locale.

const grpc = require('@grpc/grpc-js');

// Build metadata on the client
const md = new grpc.Metadata();
md.set('authorization', 'Bearer abc123');
md.set('x-request-id', 'req-42');

// Reading is case-insensitive
console.log(md.get('Authorization')); // [ 'Bearer abc123' ]
console.log(md.get('x-request-id')); // [ 'req-42' ]

Sending Metadata With a Unary Call

Every generated client method accepts an optional Metadata argument before the callback (or options). The token you set here arrives at the server before your handler runs.

Keep auth concerns out of your protobuf messages — put the token in metadata so the same auth logic works for every RPC.

const grpc = require('@grpc/grpc-js');

function callWithAuth(client, token) {
  const md = new grpc.Metadata();
  md.set('authorization', 'Bearer ' + token);

  client.GetUser({ id: '7' }, md, (err, res) => {
    if (err) return console.error('RPC failed:', err.message);
    console.log('User:', res);
  });
}

Reading Metadata on the Server

On the server, the call object exposes metadata via call.metadata.get(key), which returns an array (a key can repeat). For auth, read the authorization header and validate it before doing work.

If validation fails, return an error with grpc.status.UNAUTHENTICATED so clients can react correctly.

const grpc = require('@grpc/grpc-js');

function getUser(call, callback) {
  const auth = call.metadata.get('authorization')[0];
  if (!auth || !auth.startsWith('Bearer ')) {
    return callback({
      code: grpc.status.UNAUTHENTICATED,
      message: 'Missing or invalid token',
    });
  }
  const token = auth.slice('Bearer '.length);
  // ... verify token, then respond
  callback(null, { id: call.request.id, name: 'Ada' });
}

Server Interceptors: The Idea

Reading and validating tokens inside every handler is repetitive. A server interceptor lets you run logic once for all RPCs.

In @grpc/grpc-js, server-side interception is provided through the interceptors option on server.addService (and via ServerInterceptingCall in newer versions). The classic, widely-supported pattern is the client interceptor; on the server, many teams wrap handlers with a small higher-order function for auth and logging.

Let's start with that handler-wrapping pattern — it's portable and easy to test.

An Auth Wrapper for Handlers

A higher-order function takes a handler and returns a new handler that first checks auth, then delegates. This is a clean, framework-free way to add a cross-cutting concern.

The wrapper short-circuits with UNAUTHENTICATED when the token is bad, otherwise it calls through.

const grpc = require('@grpc/grpc-js');

function withAuth(handler) {
  return (call, callback) => {
    const auth = call.metadata.get('authorization')[0];
    if (auth !== 'Bearer good-token') {
      return callback({
        code: grpc.status.UNAUTHENTICATED,
        message: 'Unauthorized',
      });
    }
    return handler(call, callback);
  };
}

// Usage when registering the service:
// server.addService(svc, { GetUser: withAuth(getUser) });

A Logging Wrapper You Can Compose

The same pattern works for logging and timing. Because each wrapper takes a handler and returns a handler, you can compose them: withLogging(withAuth(getUser)).

Here the logger records the method label, latency, and the request id from metadata. Notice it's a complete, standalone program you can run to see the composition in action with a fake call object.

function withLogging(name, handler) {
  return (call, callback) => {
    const start = Date.now();
    const reqId = call.metadata.reqId || 'none';
    handler(call, (err, res) => {
      const ms = Date.now() - start;
      console.log(`[${name}] req=${reqId} ${err ? 'ERR' : 'OK'} ${ms}ms`);
      callback(err, res);
    });
  };
}

function getUser(call, callback) {
  callback(null, { id: call.request.id, name: 'Ada' });
}

const handler = withLogging('GetUser', getUser);
handler(
  { request: { id: '7' }, metadata: { reqId: 'req-42' } },
  (err, res) => console.log('Response:', res)
);

Client Interceptors

The official extension point on the client is the interceptor: a function that receives options and a nextCall, returning an InterceptingCall. You override lifecycle methods like start to inject metadata, or onReceiveStatus to observe results.

Below, every outgoing call automatically gets an authorization header — callers never have to remember it.

const grpc = require('@grpc/grpc-js');

function authInterceptor(token) {
  return (options, nextCall) =>
    new grpc.InterceptingCall(nextCall(options), {
      start(metadata, listener, next) {
        metadata.set('authorization', 'Bearer ' + token);
        next(metadata, listener);
      },
    });
}

// const client = new UserService(addr, creds, {
//   interceptors: [authInterceptor('abc123')],
// });

Understanding Deadlines

A deadline is an absolute point in time by which a call must finish. It is NOT a per-hop timeout — it's a wall-clock instant that propagates downstream, so a chain of services shares one budget.

Set it in the call options as deadline: either a Date or epoch milliseconds. The idiom is "now plus N ms".

  • If the deadline passes, the call fails with DEADLINE_EXCEEDED (status 4).
  • Servers can check the remaining time and abandon work early.
const grpc = require('@grpc/grpc-js');

function callWithDeadline(client) {
  const deadline = new Date(Date.now() + 2000); // 2s budget
  client.GetUser({ id: '7' }, { deadline }, (err, res) => {
    if (err && err.code === grpc.status.DEADLINE_EXCEEDED) {
      return console.error('Timed out');
    }
    if (err) return console.error(err.message);
    console.log(res);
  });
}

Deadlines on the Server Side

The server receives the deadline as call.getDeadline(). Before starting expensive work, compute the remaining budget and bail out early if it's already gone — this avoids wasting CPU on a call the client has given up on.

When you fan out to downstream services, pass the SAME deadline through so the whole tree respects one budget.

const grpc = require('@grpc/grpc-js');

function slowHandler(call, callback) {
  const deadline = call.getDeadline(); // ms or Date
  const remaining = Number(deadline) - Date.now();
  if (remaining <= 0) {
    return callback({ code: grpc.status.DEADLINE_EXCEEDED, message: 'No time left' });
  }
  // Forward the same deadline downstream:
  // downstream.Fetch(req, { deadline }, cb);
  callback(null, { ok: true, budgetMs: remaining });
}

Putting It Together: A Budgeted Pipeline

Here's the mental model for one request: the client sets a deadline and auth metadata. A client interceptor injects the token; the call carries a request id. On the server, an auth wrapper validates, a logging wrapper times it, and handlers check the remaining deadline before fanning out — propagating both metadata and the deadline downstream.

This standalone simulation shows the full chain of wrappers and a deadline check without any server, so you can run it directly.

function withAuth(h) {
  return (call, cb) =>
    call.metadata.authorization === 'Bearer good'
      ? h(call, cb)
      : cb({ code: 16, message: 'UNAUTHENTICATED' });
}
function withDeadline(h) {
  return (call, cb) =>
    call.deadline - Date.now() <= 0
      ? cb({ code: 4, message: 'DEADLINE_EXCEEDED' })
      : h(call, cb);
}
function getUser(call, cb) {
  cb(null, { id: call.request.id, name: 'Ada' });
}

const pipeline = withAuth(withDeadline(getUser));
const call = {
  request: { id: '7' },
  metadata: { authorization: 'Bearer good' },
  deadline: Date.now() + 1000,
};
pipeline(call, (err, res) =>
  console.log(err ? 'Error ' + err.code : 'OK', res || '')
);

Quick Check

A client sets deadline = Date.now() + 3000 and calls service A, which then calls service B. What is the correct behavior for the deadline?

Recap

You now have the toolkit for production-grade gRPC calls:

  • Metadata carries out-of-band data like authorization and x-request-id; read it server-side with call.metadata.get(key) (returns an array, case-insensitive keys).
  • Interceptors centralize cross-cutting logic: client interceptors use InterceptingCall to inject metadata in start; on the server, composable handler wrappers add auth and logging once.
  • Deadlines are absolute instants set via call options.deadline, propagated unchanged downstream so a whole chain shares one budget; exceeding it yields DEADLINE_EXCEEDED (status 4).

Combine them: inject auth + request id via an interceptor, wrap handlers for auth/logging, and always pass the deadline through when fanning out.

무료로 시작

AI 튜터와 함께 JavaScript을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
22
레슨
92

자주 묻는 질문

“인터셉터, 데드라인 및 메타데이터” 강의는 무료인가요?

네 — “인터셉터, 데드라인 및 메타데이터” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Node.js Backend Development Bootcamp 강의 전체를 잠금 해제할 수 있습니다. Node.js Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

“인터셉터, 데드라인 및 메타데이터”에서 뭘 배우나요?

인터셉터로 인증과 로깅 같은 공통 관심사를 추가하고 데드라인을 적용하며 메타데이터를 전달합니다. 브라우저에서 직접 실행하는 실습 코드로 Node.js Backend Development Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Node.js Backend Development Bootcamp을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Node.js Backend Development Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“인터셉터, 데드라인 및 메타데이터” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Node.js Backend Development Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Node.js Backend Development Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Protobuf IDL로 서비스 및 메시지 정의하기
  2. 단항, 서버, 클라이언트 및 양방향 스트리밍 RPC
  3. 인터셉터, 데드라인 및 메타데이터
  4. Proto 진화와 하위 호환성
← Node.js Backend Development Bootcamp(으)로 돌아가기