본문 바로가기
버전: 9.x

미들웨어

비공식 베타 번역

이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →

middleware() 메서드를 사용하면 전체 라우터에 미들웨어를 추가할 수 있습니다. 미들웨어는 프로시저 호출을 감싸고 반드시 해당 반환값을 전달해야 합니다.

인증

아래 예제에서 admin.*에 대한 모든 호출은 쿼리나 뮤테이션을 실행하기 전에 사용자가 "관리자"인지 확인합니다.

ts
trpc
.router<Context>()
.query('foo', {
resolve() {
return 'bar';
},
})
.merge(
'admin.',
trpc
.router<Context>()
.middleware(async (opts) => {
if (!opts.ctx.user?.isAdmin) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next();
})
.query('secretPlace', {
resolve() {
return 'a key';
},
}),
);
ts
trpc
.router<Context>()
.query('foo', {
resolve() {
return 'bar';
},
})
.merge(
'admin.',
trpc
.router<Context>()
.middleware(async (opts) => {
if (!opts.ctx.user?.isAdmin) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next();
})
.query('secretPlace', {
resolve() {
return 'a key';
},
}),
);

위 예제에서 발생하는 TRPCError에 대해 자세히 알아보려면 오류 처리 문서를 참조하세요.

로깅

아래 예제에서는 쿼리 실행 시간이 자동으로 기록됩니다.

ts
trpc
.router<Context>()
.middleware(async ({ path, type, next }) => {
const start = Date.now();
const result = await next();
const durationMs = Date.now() - start;
result.ok
? logMock('OK request timing:', { path, type, durationMs })
: logMock('Non-OK request timing', { path, type, durationMs });
return result;
})
.query('foo', {
resolve() {
return 'bar';
},
})
.query('abc', {
resolve() {
return 'def';
},
});
ts
trpc
.router<Context>()
.middleware(async ({ path, type, next }) => {
const start = Date.now();
const result = await next();
const durationMs = Date.now() - start;
result.ok
? logMock('OK request timing:', { path, type, durationMs })
: logMock('Non-OK request timing', { path, type, durationMs });
return result;
})
.query('foo', {
resolve() {
return 'bar';
},
})
.query('abc', {
resolve() {
return 'def';
},
});

컨텍스트 교체

미들웨어는 라우터의 컨텍스트를 교체할 수 있으며, 다운스트림 프로시저들은 새로운 컨텍스트 값을 받게 됩니다:

ts
interface Context {
// user is nullable
user?: {
id: string;
};
}
trpc
.router<Context>()
.middleware((opts) => {
if (!opts.ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...opts.ctx,
user: opts.ctx.user, // user value is known to be non-null now
},
});
})
.query('userId', {
async resolve({ ctx }) {
return ctx.user.id;
},
});
ts
interface Context {
// user is nullable
user?: {
id: string;
};
}
trpc
.router<Context>()
.middleware((opts) => {
if (!opts.ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...opts.ctx,
user: opts.ctx.user, // user value is known to be non-null now
},
});
})
.query('userId', {
async resolve({ ctx }) {
return ctx.user.id;
},
});

createProtectedRouter() 헬퍼

이 헬퍼는 앱 트리 어디에서나 사용하여 다운스트림 프로시저들이 인증되도록 강제할 수 있습니다.

server/createRouter.ts
tsx
import * as trpc from '@trpc/server';
import { Context } from './context';
export function createProtectedRouter() {
return trpc.router<Context>().middleware((opts) => {
if (!opts.ctx.user) {
throw new trpc.TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...opts.ctx,
// infers that `user` is non-nullable to downstream procedures
user: opts.ctx.user,
},
});
});
}
server/createRouter.ts
tsx
import * as trpc from '@trpc/server';
import { Context } from './context';
export function createProtectedRouter() {
return trpc.router<Context>().middleware((opts) => {
if (!opts.ctx.user) {
throw new trpc.TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...opts.ctx,
// infers that `user` is non-nullable to downstream procedures
user: opts.ctx.user,
},
});
});
}

원본 입력값

미들웨어는 프로시저에 전달될 원본 입력값(raw input)에 접근할 수 있습니다. 이는 프로시저 입력값에 대한 접근이 필요한 인증 또는 다른 전처리를 위해 사용될 수 있으며, 컨텍스트 교체와 함께 사용될 때 특히 유용합니다.

주의

미들웨어에 전달된 rawInput은 프로시저의 input 스키마/유효성 검사기로 아직 검증되지 않았으므로 사용 시 주의하세요! 이 때문에 rawInputunknown 타입을 가집니다. 자세한 내용은 #1059을 참조하세요.

ts
const inputSchema = z.object({ userId: z.string() });
trpc
.router<Context>()
.middleware(async ({ next, rawInput, ctx }) => {
const result = inputSchema.safeParse(rawInput);
if (!result.success) throw new TRPCError({ code: 'BAD_REQUEST' });
const { userId } = result.data;
// Check user id auth
return next({ ctx: { ...ctx, userId } });
})
.query('userId', {
input: inputSchema,
resolve({ ctx }) {
return ctx.userId;
},
});
ts
const inputSchema = z.object({ userId: z.string() });
trpc
.router<Context>()
.middleware(async ({ next, rawInput, ctx }) => {
const result = inputSchema.safeParse(rawInput);
if (!result.success) throw new TRPCError({ code: 'BAD_REQUEST' });
const { userId } = result.data;
// Check user id auth
return next({ ctx: { ...ctx, userId } });
})
.query('userId', {
input: inputSchema,
resolve({ ctx }) {
return ctx.userId;
},
});