Skip to content

Commit 98b62bf

Browse files
celeroncoderForestry.io
authored andcommitted
Update from Forestry.io
Khushal Bhardwaj updated content/posts/the-type-safe-guide-to-trpc.md
1 parent e1dfe25 commit 98b62bf

File tree

1 file changed

+335
-1
lines changed

1 file changed

+335
-1
lines changed

content/posts/the-type-safe-guide-to-trpc.md

Lines changed: 335 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,345 @@
22
ShowToc = true
33
date = 2022-07-27T18:30:00Z
44
description = "tRPC is a typescript library, so to say, that makes it easy to create type-safe APIs without schema or any sort of code generation."
5-
draft = true
65
tags = ["nextjs", "prisma", "tRPC"]
76
title = "The type-safe guide to tRPC"
87
[cover]
98
alt = "trpcBlogCover"
109
image = "/uploads/trpcblog.png"
1110

1211
+++
12+
Check out the original article \[here\]().
13+
14+
This isn't the best guide to use tRPC, probably there are better ways to do this, like \[create-t3-app\]([https://create.t3.gg/](https://create.t3.gg/ "https://create.t3.gg/")), the best I could find.
15+
16+
Most of what is here is from the \[tRPC's documentation\]([https://trpc.io/docs](https://trpc.io/docs "https://trpc.io/docs")), you can refer them, super helpful and easy to read.
17+
18+
\**What is tRPC?**
19+
20+
tRPC is a typescript library, so to say, that makes it easy to create type-safe APIs without schema or any sort of code generation.
21+
22+
\**Where to use?**
23+
24+
Create the *typed server* and then import its type and use it with an adaptor in the client side.
25+
26+
\**How does it implement type-safety?**
27+
28+
tRPC encourages using the \[zod\]([https://www.npmjs.com/package/zod](https://www.npmjs.com/package/zod "https://www.npmjs.com/package/zod")), a library for type validation of input and output arguments.
29+
30+
\**Is tRPC only limited to React?**
31+
32+
tRPC's core API is built to work with any client, but right now it supports **React** and can be used with **React Meta Frameworks** like **NextJS** or **SolidJS**, since it uses **React Query** under the hood to talk to the server and maintaining type-safety across the data-pipeline or data-flow.
33+
34+
For now, it has first-party adaptors for **React**, **NextJS**, **Express**, **Fastify**, **SolidJS**, and some community packages like for \[**tRPC for SveleteKit**\]([https://github.com/icflorescu/trpc-sveltekit](https://github.com/icflorescu/trpc-sveltekit "https://github.com/icflorescu/trpc-sveltekit"))
35+
36+
\**What are its features?**
37+
38+
\- Lightweight, a tiny bundle size for such a powerful library.
39+
40+
\- Type-safe to the max!
41+
42+
\- Support subscriptions with **websockets** library.
43+
44+
\- Request batching
45+
46+
- Request can be made simultaneously and then are batched into one.
47+
48+
\- Strong User base and helpful Community
49+
50+
\## tRPC x NextJS
51+
52+
Recommended file structure:
53+
54+
\`\`\`tree
55+
56+
.
57+
58+
├── prisma # <-- if prisma is added
59+
60+
│ └── \[..\]
61+
62+
├── src
63+
64+
│ ├── pages
65+
66+
│ │ ├── _app.tsx # <-- add \`withTRPC()\`-HOC here
67+
68+
│ │ ├── api
69+
70+
│ │ │ └── trpc
71+
72+
│ │ │ └── \[trpc\].ts # <-- tRPC HTTP handler
73+
74+
│ │ └── \[..\]
75+
76+
│ ├── server # <-- can be named backend or anything else
77+
78+
│ │ ├── routers
79+
80+
│ │ │ ├── app.ts # <-- main app router
81+
82+
│ │ │ ├── post.ts # <-- sub routers
83+
84+
│ │ │ └── \[..\]
85+
86+
│ │ ├── context.ts # <-- create app context
87+
88+
│ │ └── createRouter.ts # <-- router helper
89+
90+
│ └── utils
91+
92+
│ └── trpc.ts # <-- your typesafe tRPC hooks
93+
94+
└── \[..\]
95+
96+
\`\`\`
97+
98+
\### Components
99+
100+
\**Router**
101+
102+
This is the router where the actual business logic will reside, create a \`backend\` folder inside the \`src\` directory and put all this stuff there.
103+
104+
If using prisma otherwise this is optional,
105+
106+
\`src/server/utils/prisma.ts\`
107+
108+
\`\`\`ts
109+
110+
import { PrismaClient } from "@prisma/client";
111+
112+
declare global {
113+
114+
var prisma: PrismaClient | undefined;
115+
116+
};
117+
118+
export const prisma = global.prisma || new PrismaClient({
119+
120+
log: \["query"\]
121+
122+
});
123+
124+
if (process.env.NODE_ENV != 'production') global.prisma = prisma;
125+
126+
\`\`\`
127+
128+
\`src/server/router/context.ts\`
129+
130+
\`\`\`ts
131+
132+
import * as trpc from "@trpc/server";
133+
134+
import * as trpcNext from "@trpc/server/adapters/next";
135+
136+
import { prisma } from "@/server/utils/prisma"; // this is optional
137+
138+
export const createContext = async (
139+
140+
options?: trpcNext.CreateNextContextOptions
141+
142+
) => {
143+
144+
const req = options?.req;
145+
146+
const res = options?.res;
147+
148+
149+
150+
return {
151+
152+
req,
153+
154+
res,
155+
156+
prisma, // this is optional
157+
158+
};
159+
160+
};
161+
162+
type Context = trpc.inferAsyncReturnType<typeof createContext>;
163+
164+
export const createRouter = () => trpc.router<Context>();
165+
166+
\`\`\`
167+
168+
> **Using Contexts**
169+
170+
>
171+
172+
> We can create router without using the above context by just using \`trpc.router()\` that will work just fine. But if you are using some external API like in the above case we are using prisma, it's better to use pass in repeatedly used instances to context to avoid having multiple ones for every query we use that in, that may *affect our performance and can also be vulnerable*.
173+
174+
\`src/server/router/index.ts\`
175+
176+
\`\`\`ts
177+
178+
import {createRouter} from "./contex";
179+
180+
import {exampleRouter} from "./example.router";
181+
182+
export const appRouter = createRouter()
183+
184+
.merge("example.", exampleRouter)
185+
186+
.query("posts.count", {
187+
188+
async resolve({ctx}) {
189+
190+
return await ctx.prisma.patient.count();
191+
192+
}
193+
194+
});
195+
196+
export type AppRouter = typeof appRouter;
197+
198+
\`\`\`
199+
200+
\**API handler** aka NextJS adaptor:
201+
202+
> The exact filename is necessary to make this work!
203+
204+
\`src/pages/api/trpc/\[trpc\].ts\`
205+
206+
\`\`\`ts
207+
208+
import * as trpcNext from "@trpc/server/adapters/next";
209+
210+
import { appRouter, AppRouter } from "@/backend/router";
211+
212+
import { inferProcedureOutput } from "@trpc/server";
213+
214+
import { createContext } from "@/backend/router/context";
215+
216+
// export API handler
217+
218+
export default trpcNext.createNextApiHandler({
219+
220+
router: appRouter,
221+
222+
createContext: createContext,
223+
224+
});
225+
226+
\`\`\`
227+
228+
\**Hooks**
229+
230+
These are the React hooks necessary to maintain the type-safety, this will give you React Query like hooks to fetch the API.
231+
232+
\`src/utils/trpc.ts\`
233+
234+
\`\`\`ts
235+
236+
import { createReactQueryHooks } from "@trpc/react";
237+
238+
import type { AppRouter } from "@/backend/router";
239+
240+
import { inferProcedureOutput } from "@trpc/server";
241+
242+
export const trpc = createReactQueryHooks<AppRouter>();
243+
244+
export type TQuery = keyof AppRouter\["_def"\]\["queries"\];
245+
246+
// helper type to infer query output
247+
248+
export type InferQueryOutput<TRouteKey extends TQuery> = inferProcedureOutput<
249+
250+
AppRouter\["_def"\]\["queries"\]\[TRouteKey\]
251+
252+
>;
253+
254+
\`\`\`
255+
256+
\**Example query in React Component**
257+
258+
Now that tRPC is set up, this is how we use it inside react components.
259+
260+
\`src/pages/index.tsx\`
261+
262+
\`\`\`tsx
263+
264+
// we use the instance we created that has our router type definitions
265+
266+
import { trpc } from "@/utils/trpc";
267+
268+
export default SomePage() {
269+
270+
const { isLoading, data:postsCount } = trpc.useQuery(\["posts.count"\]);
271+
272+
return <div>...</div>
273+
274+
}
275+
276+
\`\`\`
277+
278+
\### SSG Helpers
279+
280+
SSG Helpers are helper functions that can be used to pre-fetch queries on the server upon request to reduce loading time.
281+
282+
They are to be used when working with SSR and SSG or ISR.
283+
284+
How to use it with \`getServideSideProps\` function of NextJS pages.
285+
286+
\`\`\`ts
287+
288+
// /pages/posts/\[id\].tsx
289+
290+
export function getServerSideProps(
291+
292+
context: GetServerSidePropsContext<{id: string}>
293+
294+
) {
295+
296+
const { id } = context.params;
297+
298+
const ssg = createSSGHelpers({
299+
300+
router: appRouter,
301+
302+
ctx: await createContext(), // { } if no context in your router
303+
304+
transformer: superjson
305+
306+
});
307+
308+
ssg.fetchQuery("posts.get", {id});
309+
310+
return {
311+
312+
props: {
313+
314+
trpcState: ssg.dehydrate(),
315+
316+
id
317+
318+
}
319+
320+
}
321+
322+
}
323+
324+
export default function PostPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
325+
326+
const {id} = props;
327+
328+
// this query will be fetched instantly because of the cached
329+
330+
// response of the query we fetching on server
331+
332+
const {isLoading, data} = trpc.useQuery(\["posts.get"\], {id})
333+
334+
return ...
335+
336+
}
337+
338+
\`\`\`
339+
340+
\**References**
341+
342+
\- Check out \[this\]([https://www.youtube.com/watch?v=I5tWWYBdlJo](https://www.youtube.com/watch?v=I5tWWYBdlJo "https://www.youtube.com/watch?v=I5tWWYBdlJo")) amazing talk by \[Theo\]([https://twitter.com/t3dotgg](https://twitter.com/t3dotgg "https://twitter.com/t3dotgg")) on tRPC vs GraphQL and their risks.
343+
344+
\- Check out Theo on YouTube or any other social media platform, he has a lot of content about tRPC
345+
346+
\- Follow \[Alex\]([https://twitter.com/alexdotjs](https://twitter.com/alexdotjs "https://twitter.com/alexdotjs")) aka Katt, the creator of tRPC.

0 commit comments

Comments
 (0)