Aller au contenu principal
Version : 9.x

Middlewares

Traduction Bêta Non Officielle

Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →

Vous pouvez ajouter des middlewares à un routeur entier avec la méthode middleware(). Ces middlewares encapsuleront l'appel de la procédure et doivent transmettre sa valeur de retour.

Autorisation

Dans l'exemple ci-dessous, tout appel à admin.* vérifiera que l'utilisateur est un "admin" avant d'exécuter une requête ou mutation.

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';
},
}),
);
astuce

Consultez Gestion des erreurs pour en savoir plus sur l'erreur TRPCError levée dans l'exemple ci-dessus.

Journalisation

Dans l'exemple ci-dessous, les temps d'exécution des requêtes sont journalisés automatiquement.

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';
},
});

Échange de contexte

Un middleware peut remplacer le contexte du routeur, et les procédures en aval recevront la nouvelle valeur de contexte :

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;
},
});

Helper createProtectedRouter()

Cet helper peut être utilisé n'importe où dans votre arbre d'application pour imposer une autorisation aux procédures en aval.

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,
},
});
});
}

Entrée brute

Un middleware peut accéder à l'entrée brute qui sera passée à une procédure. Cela est utile pour l'authentification ou d'autres prétraitements nécessitant l'accès aux entrées de la procédure, particulièrement lorsqu'utilisé avec l'échange de contexte.

attention

L'rawInput passé à un middleware n'a pas encore été validé par le schéma input de la procédure, soyez donc prudent ! Pour cette raison, rawInput est de type unknown. Plus d'infos dans #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;
},
});