Khi phát triển ứng dụng full‑stack hiện đại, việc giữ đồng bộ kiểu dữ liệu giữa backend và frontend là thách thức lớn. tRPC kết hợp với React Query cho phép bạn định nghĩa router một lần trên server và tự động sinh hook type‑safe cho client, loại bỏ hoàn toàn boilerplate REST/GraphQL. ### 1. Cài đặt nền tảng - Khởi tạo dự án Next.js (App Router) với TypeScript. - Thêm các gói: `@trpc/server`, `@trpc/client`, `@trpc/react-query`, `@trpc/next`, `@tanstack/react-query`, `zod` để validate schema. ### 2. Định nghĩa router trên server ```ts // server/trpc/router.ts import { initTRPC } from '@trpc/server'; import { z } from 'zod'; const t = initTRPC.create(); export const appRouter = t.router({ getUser: t.procedure .input(z.object({ id: z.string().uuid() })) .query(async ({ input }) => { // giả lập gọi DB return { id: input.id, name: 'Nguyễn Văn A', email: 'a@example.com' }; }), createUser: t.procedure .input(z.object({ name: z.string().min(2), email: z.string().email() })) .mutation(async ({ input }) => { // lưu DB và trả về user mới return { id: crypto.randomUUID(), ...input }; }), }); export type AppRouter = typeof appRouter; ``` ### 3. Cung cấp tRPC cho Next.js Tạo file `app/api/trpc/[trpc]/route.ts` (App Router) để expose endpoint: ```ts import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { appRouter } from '@/server/trpc/router'; import { createContext } from '@/server/trpc/context'; export const GET = (req: Request) => fetchRequestHandler({ endpoint: '/api/trpc', req, router: appRouter, createContext }); export const POST = GET; ``` ### 4. Cấu hình client và React Query provider Trong `providers.tsx`: ```tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { httpBatchLink } from '@trpc/client'; import { createTRPCNext } from '@trpc/next'; import { trpc } from '@/utils/trpc'; const queryClient = new QueryClient(); const trpcClient = createTRPCNext<AppRouter>({ config: () => ({ links: [httpBatchLink({ url: '/api/trpc' })] }) }); export function Providers({ children }: { children: React.ReactNode }) { return ( <QueryClientProvider client={queryClient}> <trpc.Provider client={trpcClient} queryClient={queryClient}> {children} </trpc.Provider> </QueryClientProvider> ); } ``` ### 5. Sử dụng hook type‑safe trong component ```tsx import { trpc } from '@/utils/trpc'; export default function UserProfile({ userId }: { userId: string }) { const { data, isLoading, error } = trpc.getUser.useQuery({ id: userId }); const create = trpc.createUser.useMutation({ onSuccess: () => trpc.getUser.invalidate({ id: userId }), }); if (isLoading) return <p>Đang tải…</p>; if (error) return <p>Lỗi: {error.message}</p>; return ( <div> <h2>{data.name}</h2> <p>{data.email}</p> <button onClick={() => create.mutate({ name: 'B', email: 'b@example.com' })}> Tạo user mới </button> </div> ); } ``` ### 6. Lợi ích thực tế - **Zero‑runtime overhead**: chỉ TypeScript compile‑time, không thêm bundle size. - **Autocomplete hoàn chỉnh**: IDE gợi ý input/output của mọi procedure. - **Invalidation tự động**: React Query tự refetch khi mutation thành công. - **Dễ dàng mở rộng**: thêm middleware (auth, logging) một nơi áp dụng cho toàn bộ router. ### 7. Mẹo áp dụng ngay 1. Tách `context` riêng để inject session, prisma client – tránh import vòng. 2. Sử dụng `zod` cho cả input và output để đảm bảo schema đồng bộ. 3. Bật `superjson` serializer nếu cần truyền `Date`, `Map`, `Set`. 4. Với App Router, đặt provider ở `layout.tsx` gốc để toàn app dùng chung `QueryClient`. Kết luận: Kết hợp tRPC + React Query trong Next.js mang lại trải nghiệm phát triển type‑safe liền mạch, giảm lỗi runtime và tăng tốc độ delivery. Hãy thử áp dụng cho module tiếp theo của dự án để cảm nhận sự khác biệt.