A simple typed rpc interface to easily type api endpoints in an app router nextjs application.
npm install @creatorem/next-trpc zodContains your api endpoints.
trpc/router.ts
import { router, endpoint } from "@creatorem/next-trpc";
import z from "zod";
export const appRouter = router({
getUser: endpoint.action(async ({ db }) => {
return await myAsyncFunction();
}),
greeting: endpoint
.input(
// you can add zod typechecking to your entry params
z.object({
name: z.string(),
age: z.coerce.number(),
})
)
.action(({ name, age }) => {
return `Hi my name is ${name}, and I am ${age} years old.`;
}),
});
export type AppRouter = typeof appRouter;app/api/trpc/[trpc]/route.ts
import { createTrpcAPI } from "@creatorem/next-trpc/server";
import { appRouter } from "~/trpc/router";
const handler = createTrpcAPI({
router: appRouter,
});
export { handler as GET, handler as POST };trpc/client.ts
import { envs } from "~/envs";
import { createTrpcClient } from "@creatorem/next-trpc/client";
import { type AppRouter } from "./router";
const url = envs().NEXT_PUBLIC_YOUR_APP_URL + "/api/trpc";
export const trpc = createTrpcClient<AppRouter>({
url,
headers: async () => {
// add custom headers like Authorization to make it works with auth logic
return {
/* Authorization: `Bearer ${jwt!}` */
};
},
});Done !
Now you can use the trpc client and server side.
app/layout.tsx
import React from "react";
import { redirect } from "next/navigation";
import { trpc } from "~/trpc/client";
export default async function Layout(
props: React.PropsWithChildren
): Promise<React.JSX.Element> {
const user = await trpc.getUser.fetch();
if (!user) {
return redirect(/* path to login page */);
}
return <>{props.children}</>;
}We offer a client side only function to create client object that pre-implement the useQuery hook from @tanstack/react-query package.
You need to have @tanstack/react-query installed.
npm install @tanstack/react-queryThen you can create the following file :
trpc/query-client.ts
import "client-only";
import { envs } from "~/envs";
import { createTrpcQueryClient } from "@creatorem/next-trpc/query-client";
import { type AppRouter } from "./router";
const url = envs().NEXT_PUBLIC_YOUR_APP_URL + "/api/trpc";
export const clientTrpc = createTrpcQueryClient<AppRouter>({
url,
headers: async () => {
// add custom headers like Authorization to make it works with auth logic
return {
/* Authorization: `Bearer ${jwt!}` */
};
},
});Now you can do :
"use client";
import React from "react";
import { clientTrpc } from "~/trpc/query-client";
export const MyClientComponent: React.FC<React.PropsWithChildren> = (props) => {
const { data: user } = clientTrpc.getUser.useQuery();
/* ... */
return <>{props.children}</>;
};Warning
Do not forget to wrap your app with <QueryClientProvider>. See installation instructions for more details.
You can use a context object to pass data to all your endpoint callbacks.
trpc/router.ts
import { CtxRouter, endpoint } from "@creatorem/next-trpc";
import z from "zod";
export const createContext = async () => {
/* your own context logic here ... */
return { db /* let's say db is a database client. */ };
};
const ctx = new CtxRouter<Awaited<ReturnType<typeof createContext>>>();
export const appRouter = ctx.router({
getUser: ctx.endpoint.action(async ({ db }) => {
return await db.user.get();
}),
greeting: ctx.endpoint
.input(
// you can add zod typechecking to your entry params
z.object({
name: z.string(),
age: z.coerce.number(),
})
)
.action(({ name, age }) => {
return `Hi my name is ${name}, and I am ${age} years old.`;
}),
});
export type AppRouter = typeof appRouter;Note
Param types are serialized during the http request to the api. You need to use z.coerce for non-string types.
Next pass your context to the nextjs api endpoint.
app/api/trpc/[trpc]/route.ts
import { createTrpcAPI } from "@creatorem/next-trpc/server";
import { appRouter, createContext } from "~/trpc/router";
const handler = createTrpcAPI({
router: appRouter,
ctx: createContext,
});
export { handler as GET, handler as POST };