Contexte
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 →
Votre contexte contient des données accessibles par toutes vos procédures tRPC, et constitue l'endroit idéal pour placer des éléments tels que les informations d'authentification.
La configuration du contexte se fait en 2 étapes : définir le type lors de l'initialisation, puis créer le contexte d'exécution pour chaque requête.
Définition du type de contexte
Lors de l'initialisation de tRPC avec initTRPC, vous devez chaîner .context<TContext>() à la fonction de construction initTRPC avant d'appeler .create(). Le type TContext peut être inféré à partir du type de retour d'une fonction ou être explicitement défini.
Cela garantira que votre contexte soit correctement typé dans vos procédures et middlewares.
tsimport {initTRPC } from '@trpc/server';import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';export constcreateContext = async (opts :CreateHTTPContextOptions ) => {// Example: extract a session token from the request headersconsttoken =opts .req .headers ['authorization'];return {token ,};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;constt =initTRPC .context <Context >().create ();t .procedure .use ((opts ) => {opts .ctx ;returnopts .next ();});
tsimport {initTRPC } from '@trpc/server';import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';export constcreateContext = async (opts :CreateHTTPContextOptions ) => {// Example: extract a session token from the request headersconsttoken =opts .req .headers ['authorization'];return {token ,};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;constt =initTRPC .context <Context >().create ();t .procedure .use ((opts ) => {opts .ctx ;returnopts .next ();});
Création du contexte
La fonction createContext() doit être transmise au gestionnaire qui monte votre appRouter. Ce gestionnaire peut utiliser HTTP ou un appel côté serveur.
createContext() est appelée une fois par requête, donc toutes les procédures d'une même requête groupée partagent le même contexte.
ts// 1. HTTP requestimport {createHTTPHandler } from '@trpc/server/adapters/standalone';import {createContext } from './context';import {appRouter } from './router';consthandler =createHTTPHandler ({router :appRouter ,createContext ,});
ts// 1. HTTP requestimport {createHTTPHandler } from '@trpc/server/adapters/standalone';import {createContext } from './context';import {appRouter } from './router';consthandler =createHTTPHandler ({router :appRouter ,createContext ,});
ts// 2. Server-side callimport {createContext } from './context';import {createCaller } from './router';constcaller =createCaller (awaitcreateContext ());
ts// 2. Server-side callimport {createContext } from './context';import {createCaller } from './router';constcaller =createCaller (awaitcreateContext ());
ts// 3. Server-side helpers (Next.js-specific, see /docs/client/nextjs/pages-router/server-side-helpers)import {createServerSideHelpers } from '@trpc/react-query/server';import {createContext } from './context';import {appRouter } from './router';consthelpers =createServerSideHelpers ({router :appRouter ,ctx : awaitcreateContext (),});
ts// 3. Server-side helpers (Next.js-specific, see /docs/client/nextjs/pages-router/server-side-helpers)import {createServerSideHelpers } from '@trpc/react-query/server';import {createContext } from './context';import {appRouter } from './router';consthelpers =createServerSideHelpers ({router :appRouter ,ctx : awaitcreateContext (),});
Exemple de code
ts// -------------------------------------------------// @filename: context.ts// -------------------------------------------------import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';/*** Creates context for an incoming request* @see https://trpc.io/docs/v11/context*/export async functioncreateContext (opts :CreateHTTPContextOptions ) {consttoken =opts .req .headers ['authorization'];// In a real app, you would verify the token and look up the userconstuser =token ? {return {user ,};}export typeContext =Awaited <ReturnType <typeofcreateContext >>;// -------------------------------------------------// @filename: trpc.ts// -------------------------------------------------import {initTRPC ,TRPCError } from '@trpc/server';import {Context } from './context';constt =initTRPC .context <Context >().create ();export constrouter =t .router ;/*** Unprotected procedure*/export constpublicProcedure =t .procedure ;/*** Protected procedure*/export constprotectedProcedure =t .procedure .use (functionisAuthed (opts ) {if (!opts .ctx .user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `user` as non-nullableuser :opts .ctx .user ,},});});
ts// -------------------------------------------------// @filename: context.ts// -------------------------------------------------import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';/*** Creates context for an incoming request* @see https://trpc.io/docs/v11/context*/export async functioncreateContext (opts :CreateHTTPContextOptions ) {consttoken =opts .req .headers ['authorization'];// In a real app, you would verify the token and look up the userconstuser =token ? {return {user ,};}export typeContext =Awaited <ReturnType <typeofcreateContext >>;// -------------------------------------------------// @filename: trpc.ts// -------------------------------------------------import {initTRPC ,TRPCError } from '@trpc/server';import {Context } from './context';constt =initTRPC .context <Context >().create ();export constrouter =t .router ;/*** Unprotected procedure*/export constpublicProcedure =t .procedure ;/*** Protected procedure*/export constprotectedProcedure =t .procedure .use (functionisAuthed (opts ) {if (!opts .ctx .user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `user` as non-nullableuser :opts .ctx .user ,},});});
Contexte interne et externe
Dans certains scénarios, il peut être judicieux de scinder votre contexte en fonctions "interne" et "externe".
Le contexte interne est l'endroit où vous définissez les éléments indépendants de la requête, comme votre connexion à la base de données. Vous pouvez utiliser cette fonction pour les tests d'intégration ou les appels côté serveur, lorsque vous n'avez pas d'objet requête. Tout ce qui est défini ici sera toujours disponible dans vos procédures.
createContextInnerPlacer un client de base de données comme prisma dans createContextInner est pratique et courant, mais les gros clients générés (comme Prisma) peuvent augmenter la surcharge de vérification de types car ils deviennent partie intégrante de votre type de contexte dans toutes les procédures.
Si cette surcharge devient perceptible, une alternative consiste à garder le contexte plus petit et à importer le client directement aux endroits où il est nécessaire.
Le contexte externe définit les éléments dépendants de la requête, comme la session utilisateur. Ce qui est défini ici n'est disponible que pour les procédures appelées via HTTP.
Exemple de contexte interne et externe
tsimport type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';import {getSessionFromCookie , typeSession } from './auth';import {db } from './db';/*** Defines your inner context shape.* Add fields here that the inner context brings.*/interfaceCreateInnerContextOptions {session :Session | null;}/*** Inner context. Will always be available in your procedures, in contrast to the outer context.** Also useful for:* - testing, so you don't have to mock `req`/`res`* - server-side calls where we don't have `req`/`res`** @see https://trpc.io/docs/v11/context#inner-and-outer-context*/export async functioncreateContextInner (opts ?:CreateInnerContextOptions ) {return {db ,session :opts ?.session ,};}/*** Outer context. Used in the routers and will e.g. bring `req` & `res` to the context as "not `undefined`".** @see https://trpc.io/docs/v11/context#inner-and-outer-context*/export async functioncreateContext (opts :CreateHTTPContextOptions ) {constsession =getSessionFromCookie (opts .req );constcontextInner = awaitcreateContextInner ({session });return {...contextInner ,req :opts .req ,res :opts .res ,};}export typeContext =Awaited <ReturnType <typeofcreateContextInner >>;// The usage in your router is the same as the example above.
tsimport type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';import {getSessionFromCookie , typeSession } from './auth';import {db } from './db';/*** Defines your inner context shape.* Add fields here that the inner context brings.*/interfaceCreateInnerContextOptions {session :Session | null;}/*** Inner context. Will always be available in your procedures, in contrast to the outer context.** Also useful for:* - testing, so you don't have to mock `req`/`res`* - server-side calls where we don't have `req`/`res`** @see https://trpc.io/docs/v11/context#inner-and-outer-context*/export async functioncreateContextInner (opts ?:CreateInnerContextOptions ) {return {db ,session :opts ?.session ,};}/*** Outer context. Used in the routers and will e.g. bring `req` & `res` to the context as "not `undefined`".** @see https://trpc.io/docs/v11/context#inner-and-outer-context*/export async functioncreateContext (opts :CreateHTTPContextOptions ) {constsession =getSessionFromCookie (opts .req );constcontextInner = awaitcreateContextInner ({session });return {...contextInner ,req :opts .req ,res :opts .res ,};}export typeContext =Awaited <ReturnType <typeofcreateContextInner >>;// The usage in your router is the same as the example above.
Il est crucial d'inférer votre Context à partir du contexte interne, car seul ce qui y est défini est véritablement toujours disponible dans vos procédures.
Si vous souhaitez éviter de vérifier constamment si req ou res sont undefined dans vos procédures, vous pouvez créer une petite procédure réutilisable :
tsexport constapiProcedure =publicProcedure .use ((opts ) => {if (!opts .ctx .req || !opts .ctx .res ) {throw newError ('You are missing `req` or `res` in your call.');}returnopts .next ({ctx : {// We overwrite the context with the truthy `req` & `res`, which will also overwrite the types used in your procedure.req :opts .ctx .req ,res :opts .ctx .res ,},});});
tsexport constapiProcedure =publicProcedure .use ((opts ) => {if (!opts .ctx .req || !opts .ctx .res ) {throw newError ('You are missing `req` or `res` in your call.');}returnopts .next ({ctx : {// We overwrite the context with the truthy `req` & `res`, which will also overwrite the types used in your procedure.req :opts .ctx .req ,res :opts .ctx .res ,},});});