Published on

tRPC Router for building APIs

tRPC is a package that lets us build API endpoints within the same file in the form of functions, as well as providing type safety between the front and back-ends.

Installing tRPC

First, install dependencies. You can use any input validators apart from zod as well. But zod is officialy recommended.

zsh.exe
yarn add @trpc/client @trpc/server @trpc/react @trpc/next zod react-query

Also have to ensure that strict mode is turned on in tsconfig.json for zod to run properly.

tsconfig.json
{
  // ...
  "compilerOptions": {
    // ...
    "strict": true
  }
}

Structuring the Next.js project

Create a new folder called server within src directory and add the following files:

context.ts
import { CreateNextContextOptions } from "@trpc/server/adapters/next";
import { inferAsyncReturnType } from "@trpc/server";

export async function createContext(contextOptions?: CreateNextContextOptions) {
  const req = contextOptions?.req;
  const res = contextOptions?.res;

  return {
    req,
    res,
  };
}

export type MyContextType = inferAsyncReturnType<typeof createContext>;
create-router.ts
import * as trpc from "@trpc/server";
import { MyContextType } from "./context";

export function createRouter() {
  return trpc.router<MyContextType>();
}

Then, create a folder named routers and add the following two files to it:

_app.ts
import { createRouter } from "../create-router";
import { nameRouter } from "./nameRouter";

export const appRouter = createRouter().merge("names.", nameRouter);

export type AppRouter = typeof appRouter;
nameRouter.ts
import { z } from "zod";
import { createRouter } from "../create-router";

export const nameRouter = createRouter.query("getName", {
  input: z.object({
    name: z.string().nullish(),
  }),
  resolve({ input }) {
    return { greeting: `Hello ${input.name}!` };
  },
});

Finally, adding an endpoint in NextJS pages/api folder

[trpc].ts
import { createNextApiHandler } from "@trpc/server/adapters/next";
import { appRouter } from "../../../server/routers/_app";
import { createContext } from "../../../server/context";

export default createNextApiHandler({
  router: appRouter,
  createContext,
  batching: {
    enabled: true,
  },
});

Next, we need to add a new folder called /utils and add a file called trpc.ts

trpc.ts
import { createReactQueryHooks } from "@trpc/react";
import { AppRouter } from "../server/routers/_app";
import { inferProcedureOutput } from "@trpc/server";

export const trpc = createReactQueryHooks<AppRouter>();

export type inferQueryOutput<
  TRouteKey extends keyof AppRouter["_def"]["queries"]
> = inferProcedureOutput<AppRouter["_def"]["queries"][TRouteKey]>;

Finally, we can actually start using our query

index.tsx
import { trpc } from "../utils/trpc";

export default function Name() {
  const nameQuery = trpc.useQuery(["name.getName", { name: "Brock" }]);

  return (
    <>
      {nameQuery.data ? (
        <h1>{nameQuery.data.greeting}</h1>
      ) : (
        <span>Loading...</span>
      )}
    </>
  );
}