Jump to content

tRPC

ุชุณู…ุญ ู„ูƒ tRPC ุจูƒุชุงุจุฉ type safe api ุฏููˆู† ุงู„ุญูŽุงุฌุฉ ุฅู„ู‰ ุชูŽูˆู„ูŠุฏ ูƒูˆุฏ ูุชูู†ุญูŠ ุนู†ูƒ ุญูŽุฏูˆุซ ุฃุฎุทุงุก ู…ูุงุฌุฆุฉ ุฃุซู†ุงุก ุงู„ู€ runtimeุŒ ุญูŠุซ ุฅู†ู‡ุง ุชูŽุณุชุบู„ ุฎุงุตูŠุฉ ุงู„ู€ inference ููŠ Typescript ุญุชู‰ ุชุถู…ู† ุงู„ู€ type safety ุนูู†ุฏ ู†ุฏุงุก ุงู„ู€ Api ู…ู† ุงู„ู€ Frontend

I built tRPC to allow people to move faster by removing the need of a traditional API-layer, while still having confidence that our apps won't break as we rapidly iterate.

Avatar of @alexdotjs
Alex - creator of tRPC @alexdotjs

Files

ู„ุณูˆุก ุงู„ุญุธ ูุฅู† tRPC ุชุชุทู„ุจ ู‚ู„ูŠู„ุงู‹ ู…ู† ุงู„ู€ boilerplate ูˆู„ูƒู† ู„ุญุณู† ุงู„ุญุธ ูุงู† create-t3-app ุชุญู…ู„ ุนู†ูƒ ู‡ุฐุง ุงู„ุนุจุก.

๐Ÿ“„ ู…ู„ู pages/api/trpc/[trpc].ts

ู‡ุฐุฉ ู‡ูŠ ู†ู‚ุทุฉ ุฏุฎูˆู„ูƒ ุงู„ูŠ tRPC ApiุŒ ููŠ ุงู„ุฃูˆุถุงุน ุงู„ุทุจูŠุนูŠุฉ ู„ู† ุชุญุชุงุฌ ุงู„ูŠ ุฃู† ุชูŽู…ุณ ู‡ุฐุง ุงู„ู…ู„ู ูƒุซูŠุฑุง. ููŠู…ูƒู†ูƒ ุชุบูŠูŠุฑู‡ ุนู†ุฏ ุชูุนูŠู„ CORS Middleware ุงูˆ ุดุฆ ู…ู† ู‡ุฐุง ุงู„ู‚ุจูŠู„ ูˆูŠู‚ูˆู… ุจุนู…ู„ export ู„ู€ createNextHandler Next.js API handlerโ†— ูˆุงู„ุฐูŠ ูŠู‚ุจู„ requestโ†— ูˆ responseโ†—

ู…ู…ุง ูŠุนู†ูŠ ุฃู†ูƒ ู‚ุงุฏุฑ ุนู„ู‰ ุงุณุชุฎุฏุงู… createNextApiHandler ููŠ ุฃูŠ middleware ุชุฑูŠุฏู‡ุŒ ุฅู‚ุฑุฃ example snippet

ู…ู„ู ๐Ÿ“„ server/trpc/context.ts

ููŠ ู‡ุฐุง ุงู„ู…ู„ู ุชู‚ูˆู… ุจุงู†ุดุงุก ุงู„ู€ Context ุงู„ุชูŠ ุณูŠุชู… ุชู…ุฑูŠุฑู‡ ุงู„ูŠ tRPC Procedure ุŒ ุงู„ู€ Context ู‡ูˆ ุนุจุงุฑุฉ ุนู† ุงู„ุจูŠุงู†ุงุช ุงู„ุชูŠ ุณูŠูƒูˆู† ู„ูƒู„ ุงู„ู€ Procedures ูˆุตูˆู„ ู„ู‡ุง ูˆู‡ูŠ ู…ูƒุงู† ู…ูู†ุงุณุจ ู„ุชุถุน ุฃุดูŠุงุก ู…ุซู„ database connections ูˆู…ุนู„ูˆู…ุงุช ุงู„ู…ุตุงุฏู‚ุฉ ูˆุบูŠุฑู‡ุง.

  • ู…ุง ู‡ูˆ createContextInner: ู‡ูู†ุง ุชูŽู‚ูˆู… ุจุฅู†ุดุงุก ุงู„ู€ Context ุงู„ุฐูŠ ู„ุง ูŠูŽุนุชู…ุฏ ุนูŽู„ู‰ ุงู„ู€ request ู…ูุซู„ ุฅุชุตุงู„ ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช. ูˆูŠู…ูƒู†ูƒ ุฅุณุชุฎุฏุงู… function ู„ู€ integration testing ุงูˆ ssg-helpersโ†—
  • ู…ุง ู‡ูˆ createContext ุŸ ู‡ูู†ุง ุญูŽูŠุซ ุชูŽู‚ูˆู… ุจุฅู†ุดุงุก ุงู„ู€ Context ุงู„ุฐูŠ ูŠุนุชู…ุฏ ุนู„ู‰ ุงู„ู€ request ููŠู…ูƒู†ูƒ ุงู„ูˆุตูˆู„ ุงู„ู‰ ุงู„ู€ req Object ุนู† ุทุฑูŠู‚ opts.req ูˆู…ู† ุซูู… ุชูŽู…ุฑูŠุฑุฉ ุงู„ูŠ createContextInnerู„ุฅู†ุดุงุก ุงู„ู€ Context ุงู„ู†ู‡ุงุฆูŠ

๐Ÿ“„ู…ู„ู server/trpc/trpc.ts

ููŠ ู‡ุฐุง ุญูŽูŠุซู ูŠู…ูƒู†ูƒ ุชุญุฏูŠุฏ ุงู„ู€ proceduresโ†— ูˆ middlewaresโ†—ุŒ ู…ู† ุงู„ุงูุถู„ ุงู† ู„ุง ุชู‚ูˆู… ุจุนู…ู„ export ู„ู€ t Object ูƒุงู…ู„ุง /middlewares) ุจู„ ู‚ู… ุจุชุตุฏูŠุฑ procedures ูˆ middlewares ุณุชู„ุงุญุธ ุฃู†ู†ุง ู†ุณุชุฎุฏู… superjson ูƒู€ data transformerโ†—ุŒ ุฐู„ูƒ ุญุชู‰ ู†ุญูุธ ุงู„ู€ Types ู„ุญูŠู† ุฅุณุชุฎุฏุงู…ู‡ุง ููŠ ููŠ ุงู„ู€ clientุŒ ูู…ุซู„ุง ุฅุฐุง ูƒุงู† ุงู„ู€ Type ู‡ูˆ Date ูุฅู† ุงู„ู€ client ุณูŽูŠูุนูŠุฏ Date ,gds string

๐Ÿ“„ ู…ู„ู server/trpc/router/*.ts

ู‡ู†ุง ูŠู…ูƒู†ูƒ ุชุญุฏูŠุฏ ุงู„ู€ route ,ูˆุงู„ู€ procedure ู„ู„ู€ APIุŒ ู…ู† ุงู„ุงูุถู„ ุฃู† ุชูู†ุดุฆ routersโ†— ู…ูู†ูุตู„ุฉ ู„ู„ู€ procedures ุงู„ู…ุชู‚ุงุฑุจุฉ ูˆู…ู† ุซูŽู… ุฏู…ุฌู‡ุงโ†— ููŠ router ูˆุงุญุฏ ููŠ server/trpc/router/_app.ts

๐Ÿ“„ ู…ู„ู utils/trpc.ts

ู‡ุฐู‡ ู‡ูŠ ู†ู‚ุทุฉ ุฏุฎูˆู„ ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฃู…ุงู…ูŠุฉ ู„ู€ tRPC. ู‡ุฐุง ู‡ูˆ ุงู„ู…ูƒุงู† ุงู„ุฐูŠ ุณุชู‚ูˆู… ููŠู‡ ุจุงุณุชูŠุฑุงุฏ type definition ุงู„ุฎุงุต ุจุงู„ู€ procedure ูˆุฅู†ุดุงุก tRPC client ุงู„ุฎุงุต ุจูƒ ุฌู†ุจู‹ุง ุฅู„ู‰ ุฌู†ุจ ู…ุน react query hooks. ู†ุธุฑู‹ุง ู„ุฃู†ู†ุง ู‚ู…ู†ุง ุจุชูุนูŠู„ โ€œsuperjsonโ€ ููŠ ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฎู„ููŠุฉ ูู†ุญู† ุจุญุงุฌุฉ ุฅู„ู‰ ุชูุนูŠู„ุฉ ุนู„ู‰ ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฃู…ุงู…ูŠุฉ ุฃูŠุถู‹ุง. ู‡ุฐุง ู„ุงู† ุงู„ุจูŠุงู†ุงุช ุงู„ุชูŠ ูŠุญุฏุซ ู„ู‡ุง serialized ููŠ ุงู„ู€ client ูŠุชู… ุนู…ู„ deserialized ู„ู‡ุง ููŠ ุงู„ู€ client.

ู‡ู†ุง ุชู‚ูˆู… ุจุชุญุฏูŠุฏ ุฑูˆุงุจุทโ†— ุงู„ู€ tRPC ุญูŠุซ ุชููุญุฏุฏ ุงู„ู…ุณุงุฑ ุงู„ุฐูŠ ุณูŠู…ุฑ ุจู‡ ุงู„ู€ request ู…ู† ุงู„ู€ client ุฅู„ู‰ ุงู„ู€ server ู†ุญู† ู†ุณุชุฎุฏู… httpBatchLinkโ†— ุจุดูƒู„ ุฅูุชุฑุงุถูŠ ู…ุน ุชูุนูŠู„ request batchingโ†— ูˆ loggerLinkโ†—

ูˆููŠ ุงู„ุงุฎูŠุฑ ู†ู‚ูˆู… ุจุชุตุฏูŠุฑ helper typeโ†— ุญุชู‰ ู†ุณุชุนู…ู„ ุงู„ู€ type infre ููŠ ุงู„ู€ frontend

ูƒูŠู ุฃุณุชุฎุฏู… tRPC ุŸ

ู†ู†ุตุญูƒ ุจู…ุดุงู‡ุฏุฉ ู‡ุฐุงa killer talk at Next.js Confโ†— ู…ู† trashh_devโ†—

ู…ุน tRPCุชูƒุชุจ Function ููŠ ุงู„ู€ backend ูˆุงู„ุชูŠ ูŠู…ูƒู† ู…ู†ุงุฏุงุชู‡ุง ู…ู† ุงู„ู€ frontend

server/trpc/router/user.ts
const userRouter = t.router({
  getById: t.procedure.input(z.string()).query(({ ctx, input }) => {
    return ctx.prisma.user.findFirst({
      where: {
        id: input,
      },
    });
  }),
});

ููŠ ู†ู‡ุงูŠุฉ ุงู„ุฃู…ุฑ ุชุชุญูˆู„ tRPC procedure ุงู„ูŠ backend ุนุงุฏูŠ ููŠู‚ูˆู… ุจูุญุต ุงู„ู€ input ูˆูŠู…ุฑุฑ ุงู„ู€ request ุฅุฐุง ูƒุงู† ุตุญูŠุญุงู‹ูˆูŠุนูŠุฏ ุฑุณุงู„ุฉ ุฎุทุฃ ุฅุฐุง ูƒุงู†ุช ุงู„ู…ุฏุฎู„ุงุช ุบูŠุฑ ุตุญูŠุญุฉ. ุจุนุฏ ุงู„ุชุฃูƒุฏ ู…ู† ุตุญุฉ ุงู„ุจูŠุงู†ุงุช ูŠุชู… ู†ุฏุงุก function ูˆุงู„ุชูŠ ุฅู…ุง ู„ุฌู„ุจ ุจูŠุงู†ุงุช (queryโ†—) ุฃูˆ ุฃู† ุชุบูŠุฑ ููŠ ุงู„ุจุงู†ุงุช (mutationโ†—) ุฃู†ุช

server/trpc/router/_app.ts
const appRouter = t.router({
  users: userRouter,
  posts: postRouter,
  messages: messageRouter,
});

export type AppRouter = typeof appRouter;

ู„ุงุญุธ ุฃู†ู†ุง ู†ู‚ูˆู… ุจุนู…ู„ export ูู‚ุท ู„ู€ routerโ€™s type ุฃูŠ ุฃู†ู†ุง ู„ุง ู†ุณุชุฎุฏู… ุงูŠ ู…ู† ุงู„ู€ server code ููŠ ุงู„ู€ client ุงู„ุงู† ุฏุนู†ุง ู†ู†ุงุฏูŠ ุงู„ู€ procedure ู…ู† ุงู„ู€ frontend ุŒ tRPC ุชูˆูุฑ wrapper ู„ู…ูƒุชุจุฉ @tanstack/react-query ู…ู…ุง ูŠุณู…ุญ ู„ูƒ ุจุฅุณุชุฎุฏุงู… ุงู„ู…ูƒุชุจุฉ ุจูƒุงู…ู„ ู‚ูˆุชู‡ุง.

pages/users/[id].tsx
import { useRouter } from "next/router";

const UserPage = () => {
  const { query } = useRouter();
  const userQuery = trpc.user.getById.useQuery(query.id);

  return (
    <div>
      <h1>{userQuery.data?.name}</h1>
    </div>
  );
};

ุณุชู„ุงุญุธ ุนู„ู‰ ุงู„ููˆุฑ ู…ุฏู‰ ุฌูˆุฏุฉ ุงู„ุฅูƒู…ุงู„ ุงู„ุชู„ู‚ุงุฆูŠ ูˆุงู„ู€ typesafety. ุจู…ุฌุฑุฏ ูƒุชุงุจุฉ โ€œtrpc.โ€ ุŒ ุณุชุธู‡ุฑ router ุงู„ุฎุงุตุฉ ุจูƒ ููŠ ุงู„ุฅูƒู…ุงู„ ุงู„ุชู„ู‚ุงุฆูŠ ุŒ ูˆุนู†ุฏู…ุง ุชุญุฏุฏ ุงู„ู€ routerุŒ ุณุชุธู‡ุฑ ุงู„ู€ procedures. ูˆุณุชุญุตู„ ุฃูŠุถู‹ุง ุนู„ู‰ ุฎุทุฃ TypeScript ุฅุฐุง ูƒุงู†ุช ุงู„ู…ูุฏุฎู„ุงุช ุงู„ุฎุงุต ุจูƒ ู„ุง ูŠุชุทุงุจู‚ ู…ุน ุงู„ู€ schema ุงู„ุฐูŠ ุญุฏุฏุชู‡ ู…ุณุจู‚ุง.

ูƒูŠู ุงูู†ุงุฏูŠ API ุฎุงุฑุฌูŠ ุŸ

ุจุงุณุชุฎุฏุงู… ุงู„ู€ API ุงู„ุนุงุฏูŠุฉ ุŒ ูŠู…ูƒู†ูƒ ุงุณุชุฏุนุงุก ุงู„ู€ End point ุงู„ุฎุงุตุฉ ุจูƒ ุจุงุณุชุฎุฏุงู… ุฃูŠ ุนู…ูŠู„ HTTP ู…ุซู„ curl ุฃูˆ Postman ุฃูˆ fetch ุฃูˆ ู…ุจุงุดุฑุฉ ู…ู† ู…ุชุตูุญูƒ. ู…ุน tRPC ุŒ ุงู„ุฃู…ุฑ ู…ุฎุชู„ู ุจุนุถ ุงู„ุดูŠุก. ุฅุฐุง ูƒู†ุช ุชุฑุบุจ ููŠ ุงู„ุงุชุตุงู„ ุจุงู„ู€ procedure ุจุฏูˆู† ุนู…ูŠู„ tRPC ุŒ ูู‡ู†ุงูƒ ุทุฑูŠู‚ุชุงู† ู…ูˆุตู‰ ุจู‡ู…ุง ู„ู„ู‚ูŠุงู… ุจุฐู„ูƒ:

Expose a single procedure externally

ุฅุฐุง ุฃุฑุฏุช ุฃู† ุชูุชูŠุญ procedure ู„ู„ู€ Apis ุงู„ุฎุงุฑุฌูŠุฉ ุงู„ู‚ ู†ุธุฑุฉ ุนู„ูŠ server side callsโ†—ุŒ ู…ู…ุง ุณูŠุณู…ุญ ู„ูƒ ุจุนู…ู„ Next.js Api ุฅุนุชูŠุงุฏูŠุฉ

pages/api/users/[id].ts
import { type NextApiRequest, type NextApiResponse } from "next";
import { appRouter, createCaller } from "../../../server/api/root";
import { createTRPCContext } from "../../../server/api/trpc";

const userByIdHandler = async (req: NextApiRequest, res: NextApiResponse) => {
  // Create context and caller
  const ctx = await createTRPCContext({ req, res });
  const caller = createCaller(ctx);
  try {
    const { id } = req.query;
    const user = await caller.user.getById(id);
    res.status(200).json(user);
  } catch (cause) {
    if (cause instanceof TRPCError) {
      // An error from tRPC occurred
      const httpCode = getHTTPStatusCodeFromError(cause);
      return res.status(httpCode).json(cause);
    }
    // Another error occurred
    console.error(cause);
    res.status(500).json({ message: "Internal server error" });
  }
};

export default userByIdHandler;

ุชุญูˆู„ ูƒู„ ุงู„ู€ Procedures ุงู„ูŠ REST endpoint ุŸ

ุฅุฐุง ูƒู†ุช ุชุฑุบุจ ููŠ ูƒุดู ูƒู„ ุงู„ู€ ุ›ู‚ุฎุคุซูŠุนู‚ุซุณ ุŒ ุงู„ู‚ ู†ุธุฑุฉ ุนู„ูŠ trpc-openapiโ†—.

Itโ€™s just HTTP Requests

tRPC communicates over HTTP, so it is also possible to call your tRPC procedures using โ€œregularโ€ HTTP requests. However, the syntax can be cumbersome due to the RPC protocolโ†— that tRPC uses. If youโ€™re curious, you can check what tRPC requests and responses look like in your browserโ€™s network tab, but we suggest doing this only as an educational exercise and sticking to one of the solutions outlined above.

Comparison to a Next.js API endpoint

ุฏุนู†ุง ู†ู‚ุงุฑู† โ€ Next.js Endpointโ€ ุจู€ โ€œtRPC procedureโ€. ู„ู†ูุชุฑุถ ุฃู†ู†ุง ู†ุฑูŠุฏ ุฌู„ุจ โ€œObjectโ€ ู…ุณุชุฎุฏู… ู…ุนูŠู† ู…ู† ู‚ุงุนุฏุฉ ุจูŠุงู†ุงุชู†ุง ูˆุฅุนุงุฏุชู‡ ุฅู„ู‰ ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฃู…ุงู…ูŠุฉ. ูŠู…ูƒู†ู†ุง ูƒุชุงุจุฉ Next.js API Endpoint ู…ุซู„ ู‡ุฐุง:

pages/api/users/[id].ts
import { type NextApiRequest, type NextApiResponse } from "next";
import { prisma } from "../../../server/db/client";

const userByIdHandler = async (req: NextApiRequest, res: NextApiResponse) => {
  if (req.method !== "GET") {
    return res.status(405).end();
  }

  const { id } = req.query;

  if (!id || typeof id !== "string") {
    return res.status(400).json({ error: "Invalid id" });
  }

  const examples = await prisma.example.findFirst({
    where: {
      id,
    },
  });

  res.status(200).json(examples);
};

export default userByIdHandler;
pages/users/[id].tsx
import { useState, useEffect } from "react";
import { useRouter } from "next/router";

const UserPage = () => {
  const router = useRouter();
  const { id } = router.query;

  const [user, setUser] = useState(null);
  useEffect(() => {
    fetch(`/api/user/${id}`)
      .then((res) => res.json())
      .then((data) => setUser(data));
  }, [id]);
};

ู‚ุงุฑู† ู‡ุฐุง ุจู…ุซุงู„ tRPC ุฃุนู„ุงู‡ ูˆูŠู…ูƒู†ูƒ ุฑุคูŠุฉ ุจุนุถ ู…ุฒุงูŠุง tRPC:

  • ุจุฏู„ุงู‹ ู…ู† ุชุญุฏูŠุฏ ุนู†ูˆุงู† url ู„ูƒู„ ู…ุณุงุฑ ุŒ ูˆุงู„ุฐูŠ ูŠู…ูƒู† ุฃู† ูŠุตุจุญ ู…ุฒุนุฌู‹ุง ุฅุฐุง ุญุงูˆู„ุช ู†ู‚ู„ ุดูŠุก ู…ุง ุŒ ูุฅู† ุงู„ู€ router ุจุฃูƒู…ู„ู‡ ุนุจุงุฑุฉ ุนู† Object ู…ุน ุงู„ุฅูƒู…ุงู„ ุงู„ุชู„ู‚ุงุฆูŠ.
  • ู„ุณุช ุจุญุงุฌุฉ ุฅู„ู‰ ุงู„ุชุญู‚ู‚ ู…ู† HTTP method ุงู„ุชูŠ ุชู… ุงุณุชุฎุฏุงู…ู‡ุง.
  • ู„ุง ุชุญุชุงุฌ ุฅู„ู‰ ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงู„ุทู„ุจ ุฃูˆ ุงู„ู€ query ุŒ ู„ุฃู† Zod ูŠุนุชู†ูŠ ุจุฐู„ูƒ.
  • ุจุฏู„ุงู‹ ู…ู† ุฅู†ุดุงุก ุงู„ู€ responde object ุŒ ูŠู…ูƒู†ูƒ ุฅุฑุฌุงุน ุฃุฎุทุงุก ุงูˆ ู‚ูŠู…ุฉ ุฃูˆ Object ูƒู…ุง ุชูุนู„ ููŠ ุฃูŠ function.

snippets ู…ููŠุฏุฉ

ุชูุนูŠู„ CORS

pages/api/trpc/[trpc].ts
import { type NextApiRequest, type NextApiResponse } from "next";
import { createNextApiHandler } from "@trpc/server/adapters/next";
import { appRouter } from "~/server/trpc/router/_app";
import { createContext } from "~/server/trpc/context";
import cors from "nextjs-cors";

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  // Enable cors
  await cors(req, res);

  // Create and call the tRPC handler
  return createNextApiHandler({
    router: appRouter,
    createContext,
  })(req, res);
};

export default handler;

Optimistic updates

ุงู„ู€ Optimistic updates ู‡ูŠ ุชุญุฏูŠุซุงุช ุชุญุฏูŠุซ ูˆุงุฌู‡ุฉ ุงู„ู…ุณุชุฎุฏู… ู‚ุจู„ ุฃู† ูŠู†ุชู‡ูŠ ุงู„ู€ Request ู…ู…ุง ูŠูุญุณู† ุชุฌุฑุจุฉ ุงู„ู…ุณุชุฎุฏู…ุŒ ู„ูƒู† ุงู„ุชุทุจูŠู‚ุงุช ุงู„ุชูŠ ุชููุถู„ ุฏู‚ุฉ ุงู„ู…ุนู„ูˆู…ุงุช ูŠุฌุจ ุฃู† ุชุชุฌู†ุจ ุงู„ู€ Optimistic updatesุŒ ู„ู„ู…ุฒูŠุฏ ู…ู† ุงู„ู…ุนู„ูˆู…ุงุช ุฅู‚ุฑุง React Query docsโ†—.

const MyComponent = () => {
  const listPostQuery = trpc.post.list.useQuery();

  const utils = trpc.useContext();
  const postCreate = trpc.post.create.useMutation({
    async onMutate(newPost) {
      // Cancel outgoing fetches (so they don't overwrite our optimistic update)
      await utils.post.list.cancel();

      // Get the data from the queryCache
      const prevData = utils.post.list.getData();

      // Optimistically update the data with our new post
      utils.post.list.setData(undefined, (old) => [...old, newPost]);

      // Return the previous data so we can revert if something goes wrong
      return { prevData };
    },
    onError(err, newPost, ctx) {
      // If the mutation fails, use the context-value from onMutate
      utils.post.list.setData(undefined, ctx.prevData);
    },
    onSettled() {
      // Sync with server once mutation has settled
      utils.post.list.invalidate();
    },
  });
};

ุนูŠู†ุฉ ู…ู† Integration Test

ุฅู‚ุฑุฃ Vitestโ†—

import { type inferProcedureInput } from "@trpc/server";
import { expect, test } from "vitest";

import { appRouter, type AppRouter } from "~/server/router/_app";
import { createContextInner } from "~/server/router/context";

test("example router", async () => {
  const ctx = await createContextInner({ session: null });
  const caller = appRouter.createCaller(ctx);

  type Input = inferProcedureInput<AppRouter["example"]["hello"]>;
  const input: Input = {
    text: "test",
  };

  const example = await caller.example.hello(input);

  expect(example).toMatchObject({ greeting: "Hello test" });
});

Useful Resources

ResourceLink
tRPC Docshttps://www.trpc.ioโ†—
Bunch of tRPC Exampleshttps://github.com/trpc/trpc/tree/next/examplesโ†—
React Query Docshttps://tanstack.com/query/v4/docs/adapters/react-queryโ†—