Docs
Start
Middleware

Middleware

What is Middleware?

Middleware는 createServerFn으로 생성된 서버 함수의 동작을 사용자 정의할 수 있도록 합니다. 이를 통해 공통된 검증, 컨텍스트 제공, 그리고 다양한 작업을 수행할 수 있습니다. Middleware는 계층적으로 실행되는 작업 체인을 생성하기 위해 다른 Middleware에 의존할 수도 있습니다.

What kinds of things can I do with Middleware?

  • Authentication: 서버 함수 실행 전에 사용자의 신원을 확인합니다.
  • Authorization: 사용자가 서버 함수를 실행할 권한이 있는지 확인합니다.
  • Logging: 요청, 응답 및 에러를 로깅합니다.
  • Observability: 메트릭, 트레이스 및 로그를 수집합니다.
  • Provide Context: 다른 Middleware나 서버 함수에서 사용할 데이터를 요청 객체에 첨부합니다.
  • Error Handling: 에러를 일관된 방식으로 처리합니다.
  • 그리고 더 많은 작업이 가능합니다! 가능성은 여러분의 상상에 달려 있습니다.

Defining Middleware

Middleware는 createMiddleware 함수를 사용하여 정의됩니다. 이 함수는 middleware, validator, server, client와 같은 메서드로 Middleware를 계속 사용자 정의할 수 있는 Middleware 객체를 반환합니다.

import { createMiddleware } from "@tanstack/start";
 
const loggingMiddleware = createMiddleware().server(async ({ next, data }) => {
  console.log("Request received:", data);
  const result = await next();
  console.log("Response processed:", result);
  return result;
});

Middleware Methods

Middleware를 사용자 정의하기 위해 여러 메서드를 사용할 수 있습니다. TypeScript를 사용하는 경우, 이러한 메서드의 순서는 타입 시스템에 의해 강제되어 최대한의 추론 및 타입 안전성을 보장합니다.

  • middleware: 체인에 Middleware를 추가합니다.
  • validator: 데이터를 수정하여 이 Middleware 및 하위 Middleware로 전달합니다.
  • server: 하위 Middleware 및 서버 함수 실행 전에 서버 측 논리를 정의하고, 결과를 다음 Middleware로 전달합니다.
  • client: 하위 Middleware 및 최종 RPC 함수 실행 전에 클라이언트 측 논리를 정의하고, 결과를 다음 Middleware로 전달합니다.

The middleware method

middleware 메서드는 현재 Middleware 이전에 실행될 Middleware를 체인에 추가하는 데 사용됩니다. middleware 메서드에 Middleware 객체 배열을 전달합니다.

const loggingMiddleware = createMiddleware().middleware([
  authMiddleware,
  loggingMiddleware,
]);

상위 Middleware에서 타입 안전한 컨텍스트와 페이로드 검증도 상속됩니다!

The validator method

validator 메서드는 데이터를 수정하여 이 Middleware, 하위 Middleware, 그리고 최종적으로 서버 함수로 전달합니다. 이 메서드는 데이터 객체를 받아 검증된(그리고 선택적으로 수정된) 데이터 객체를 반환하는 함수를 받아야 합니다. 일반적으로 zod와 같은 검증 라이브러리를 사용합니다. 예제는 다음과 같습니다:

import { z } from "zod";
 
const mySchema = z.object({
  workspaceId: z.string(),
});
 
const workspaceMiddleware = createMiddleware()
  .validator(zodValidator(mySchema))
  .server(({ next, data }) => {
    console.log("Workspace ID:", data.workspaceId);
    return next();
  });

The server method

server 메서드는 Middleware가 하위 Middleware 및 서버 함수 실행 전후에 실행할 서버 측 논리를 정의하는 데 사용됩니다. 이 메서드는 다음 속성을 가진 객체를 받습니다:

  • next: 호출 시 체인의 다음 Middleware를 실행하는 함수입니다.
  • data: 서버 함수에 전달된 데이터 객체입니다.
  • context: 상위 Middleware에서 전달된 데이터를 저장하는 객체입니다. 추가 데이터를 확장하여 하위 Middleware로 전달할 수 있습니다.

Returning the required result from next

next 함수는 체인의 다음 Middleware를 실행하는 데 사용됩니다. 체인의 실행을 계속하려면 제공된 next 함수의 결과를 반드시 대기(await)하고 반환(return)해야 합니다.

const loggingMiddleware = createMiddleware().server(async ({ next }) => {
  console.log("Request received");
  const result = await next();
  console.log("Response processed");
  return result;
});

Providing context to the next middleware via next

next 함수는 선택적으로 context 속성을 가진 객체를 호출할 수 있습니다. 이 context 값에 전달된 속성은 상위 context에 병합되어 다음 Middleware에 제공됩니다.

const awesomeMiddleware = createMiddleware().server(({ next }) => {
  return next({
    context: {
      isAwesome: Math.random() > 0.5,
    },
  });
});
 
const loggingMiddleware = createMiddleware().server(
  async ({ next, context }) => {
    console.log("Is awesome?", context.isAwesome);
    return next();
  }
);

Client-Side Logic

서버 함수가 주로 서버 측 작업이지만, 클라이언트에서 RPC 요청을 둘러싼 많은 클라이언트 측 논리가 있습니다. 이는 클라이언트 측 Middleware에서 논리를 정의하여 하위 Middleware와 최종 RPC 함수 및 클라이언트 응답을 실행할 수 있다는 것을 의미합니다.

Client-side Payload Validation

기본적으로 Middleware 검증은 클라이언트 번들의 크기를 작게 유지하기 위해 서버에서만 수행됩니다. 그러나 createMiddleware 함수에 validateClient: true 옵션을 전달하여 클라이언트에서 데이터를 검증하도록 선택할 수 있습니다. 이렇게 하면 데이터를 서버로 전송하기 전에 검증하여 네트워크 왕복을 절약할 수 있습니다.

왜 클라이언트에서 다른 검증 스키마를 전달할 수 없나요?

클라이언트 검증 스키마는 서버 검증 스키마에서 파생됩니다. 이는 클라이언트 검증 스키마가 데이터를 서버로 보내기 전에 이를 검증하는 데 사용되기 때문입니다. 클라이언트 검증 스키마가 서버 검증 스키마와 다르면 서버가 예상하지 못한 데이터를 수신할 수 있으며, 이는 예기치 않은 동작을 초래할 수 있습니다.

const workspaceMiddleware = createMiddleware({ validateClient: true })
  .validator(zodValidator(mySchema))
  .server(({ next, data }) => {
    console.log("Workspace ID:", data.workspaceId);
    return next();
  });

The client method

클라이언트 Middleware 논리는 Middleware 객체에서 client 메서드를 사용하여 정의됩니다. 이 메서드는 하위 Middleware 및 최종 RPC 함수(또는 서버 함수)가 실행되기 전후에 클라이언트 측 논리를 정의하는 데 사용됩니다.

클라이언트 Middleware 논리는 server 메서드로 생성된 논리와 유사한 API를 공유하지만, 클라이언트 측에서 실행됩니다.

  • 체인을 계속하기 위해 next 함수가 호출되어야 합니다.
  • next 함수를 통해 다음 클라이언트 Middleware에 컨텍스트를 제공할 수 있습니다.
  • 데이터를 수정하여 다음 클라이언트 Middleware로 전달할 수 있습니다.
const loggingMiddleware = createMiddleware().client(async ({ next }) => {
  console.log("Request sent");
  const result = await next();
  console.log("Response received");
  return result;
});

Sending client context to the server

클라이언트 컨텍스트는 기본적으로 서버로 전송되지 않습니다. 클라이언트에서 서버로 컨텍스트를 보내려면 next 함수에 sendContext 속성을 전달해야 합니다.

const requestLogger = createMiddleware()
  .client(async ({ next, context }) => {
    return next({
      sendContext: {
        workspaceId: context.workspaceId,
      },
    });
  })
  .server(async ({ next, data, context }) => {
    console.log("Workspace ID:", context.workspaceId);
    return next();
  });

Client-Sent Context Security

클라이언트가 서버로 보낸 데이터는 런타임에 검증되지 않을 수 있습니다. 동적 데이터를 전송할 경우 서버 Middleware에서 이를 검증하세요:

const requestLogger = createMiddleware()
  .client(async ({ next, context }) => {
    return next({
      sendContext: {
        workspaceId: context.workspaceId,
      },
    });
  })
  .server(async ({ next, data, context }) => {
    const workspaceId = zodValidator(z.number()).parse(context.workspaceId);
    console.log("Workspace ID:", workspaceId);
    return next();
  });

Sending server context to the client

서버 컨텍스트를 클라이언트로 보내려면 next 함수에 sendContext 속성을 전달합니다.

const requestLogger = createMiddleware()
  .client(async ({ next, context }) => {
    const result = next();
    console.log("Time from the server:", result.context.timeFromServer);
  })
  .server(async ({ next }) => {
    return next({
      sendContext: {
        timeFromServer: new Date(),
      },
    });
  });

Middleware Execution Order

Middleware는 의존성 우선 순서로 실행됩니다. 아래 예제는 a, b, c, d 순으로 실행됩니다:

const a = createMiddleware().server(async ({ next }) => {
  console.log("a");
  return next();
});
 
const b = createMiddleware()
  .middleware([a])
  .server(async ({ next }) => {
    console.log("b");
    return next();
  });
 
const c = createMiddleware()
  .middleware()
  .server(async ({ next }) => {
    console.log("c");
    return next();
  });
 
const d = createMiddleware()
  .middleware([b, c])
  .server(async () => {
    console.log("d");
  });
 
const fn = createServerFn()
  .middleware([d])
  .server(async () => {
    console.log("fn");
  });

Environment Tree Shaking

Middleware 기능은 생성된 각 번들의 환경에 따라 tree-shaken 됩니다.

  • 서버에서는 tree-shaking이 발생하지 않으므로 Middleware에서 사용된 모든 코드가 서버 번들에 포함됩니다.
  • 클라이언트에서는 모든 서버 전용 코드가 클라이언트 번들에서 제거됩니다. 이는 server 메서드에서 사용된 모든 코드가 항상 클라이언트 번들에서 제거된다는 것을 의미합니다. 만약 validateClienttrue로 설정되어 있으면 클라이언트 측 검증 코드는 클라이언트 번들에 포함됩니다. 그렇지 않으면 data 검증 코드도 제거됩니다.