-
-
Save mkcode/a590d1c8f7b0a37b8299965de7f7e958 to your computer and use it in GitHub Desktop.
// TRPC API endpoint | |
// src/app/api/trpc/[trpc]/route.ts | |
import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; | |
import { type NextRequest } from "next/server"; | |
import { env } from "~/env"; | |
import { appRouter } from "~/server/api/root"; | |
import { createTRPCContext } from "~/server/api/trpc"; | |
import { getAuth } from "@clerk/nextjs/server"; | |
/** | |
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when | |
* handling a HTTP request (e.g. when you make requests from Client Components). | |
*/ | |
const createContext = async (req: NextRequest) => { | |
return createTRPCContext({ | |
headers: req.headers, | |
auth: getAuth(req), | |
}); | |
}; | |
const handler = (req: NextRequest) => | |
fetchRequestHandler({ | |
endpoint: "/api/trpc", | |
req, | |
router: appRouter, | |
createContext: () => createContext(req), | |
onError: | |
env.NODE_ENV === "development" | |
? ({ path, error }) => { | |
console.error( | |
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`, | |
); | |
} | |
: undefined, | |
}); | |
export { handler as GET, handler as POST }; |
// TRPC Proxy for server side usage | |
// src/trpc/server.ts | |
import "server-only"; | |
import { | |
createTRPCProxyClient, | |
loggerLink, | |
TRPCClientError, | |
} from "@trpc/client"; | |
import { callProcedure } from "@trpc/server"; | |
import { observable } from "@trpc/server/observable"; | |
import { type TRPCErrorResponse } from "@trpc/server/rpc"; | |
import { cookies, headers } from "next/headers"; | |
import { NextRequest } from "next/server"; | |
import { cache } from "react"; | |
import { appRouter, type AppRouter } from "~/server/api/root"; | |
import { createTRPCContext } from "~/server/api/trpc"; | |
import { transformer } from "./shared"; | |
import { getAuth } from "@clerk/nextjs/server"; | |
/** | |
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when | |
* handling a tRPC call from a React Server Component. | |
*/ | |
const createContext = cache(() => { | |
return createTRPCContext({ | |
headers: new Headers({ | |
cookie: cookies().toString(), | |
"x-trpc-source": "rsc", | |
}), | |
auth: getAuth( | |
new NextRequest("https://notused.com", { headers: headers() }), | |
), | |
}); | |
}); | |
export const api = createTRPCProxyClient<AppRouter>({ | |
transformer, | |
links: [ | |
loggerLink({ | |
enabled: (op) => | |
process.env.NODE_ENV === "development" || | |
(op.direction === "down" && op.result instanceof Error), | |
}), | |
/** | |
* Custom RSC link that lets us invoke procedures without using http requests. Since Server | |
* Components always run on the server, we can just call the procedure as a function. | |
*/ | |
() => | |
({ op }) => | |
observable((observer) => { | |
createContext() | |
.then((ctx) => { | |
return callProcedure({ | |
procedures: appRouter._def.procedures, | |
path: op.path, | |
rawInput: op.input, | |
ctx, | |
type: op.type, | |
}); | |
}) | |
.then((data) => { | |
observer.next({ result: { data } }); | |
observer.complete(); | |
}) | |
.catch((cause: TRPCErrorResponse) => { | |
observer.error(TRPCClientError.from(cause)); | |
}); | |
}), | |
], | |
}); |
// TRPC server response | |
// src/server/api/trpc.ts | |
/** | |
* YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS: | |
* 1. You want to modify request context (see Part 1). | |
* 2. You want to create a new middleware or type of procedure (see Part 3). | |
* | |
* TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will | |
* need to use are documented accordingly near the end. | |
*/ | |
import { initTRPC, TRPCError } from "@trpc/server"; | |
import superjson from "superjson"; | |
import { ZodError } from "zod"; | |
import { db } from "~/server/db"; | |
import { type getAuth } from "@clerk/nextjs/server"; | |
type AuthObject = ReturnType<typeof getAuth>; | |
/** | |
* 1. CONTEXT | |
* | |
* This section defines the "contexts" that are available in the backend API. | |
* | |
* These allow you to access things when processing a request, like the database, the session, etc. | |
* | |
* This helper generates the "internals" for a tRPC context. The API handler and RSC clients each | |
* wrap this and provides the required context. | |
* | |
* @see https://trpc.io/docs/server/context | |
*/ | |
export const createTRPCContext = async (opts: { | |
headers: Headers; | |
auth: AuthObject; | |
}) => { | |
return { | |
db, | |
userId: opts.auth.userId, | |
...opts, | |
}; | |
}; | |
/** | |
* 2. INITIALIZATION | |
* | |
* This is where the tRPC API is initialized, connecting the context and transformer. We also parse | |
* ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation | |
* errors on the backend. | |
*/ | |
const t = initTRPC.context<typeof createTRPCContext>().create({ | |
transformer: superjson, | |
errorFormatter({ shape, error }) { | |
return { | |
...shape, | |
data: { | |
...shape.data, | |
zodError: | |
error.cause instanceof ZodError ? error.cause.flatten() : null, | |
}, | |
}; | |
}, | |
}); | |
/** | |
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) | |
* | |
* These are the pieces you use to build your tRPC API. You should import these a lot in the | |
* "/src/server/api/routers" directory. | |
*/ | |
/** | |
* This is how you create new routers and sub-routers in your tRPC API. | |
* | |
* @see https://trpc.io/docs/router | |
*/ | |
export const createTRPCRouter = t.router; | |
/** | |
* Public (unauthenticated) procedure | |
* | |
* This is the base piece you use to build new queries and mutations on your tRPC API. It does not | |
* guarantee that a user querying is authorized, but you can still access user session data if they | |
* are logged in. | |
*/ | |
export const publicProcedure = t.procedure; | |
/** Reusable middleware that enforces users are logged in before running the procedure. */ | |
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { | |
if (!ctx.userId) { | |
throw new TRPCError({ code: "UNAUTHORIZED" }); | |
} | |
// Make ctx.userId non-nullable in protected procedures | |
return next({ ctx: { userId: ctx.userId } }); | |
}); | |
/** | |
* Protected (authenticated) procedure | |
* | |
* If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies | |
* the session is valid and guarantees `ctx.session.user` is not null. | |
* | |
* @see https://trpc.io/docs/procedures | |
*/ | |
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); |
Oh my friend. You solve my problem. Thank you so much!
Oh my friend. You solved my problem. Thank you so much!
Same here
This seems very insecure
auth: getAuth( new NextRequest("https://notused.com", { headers: headers() }), ),
What should be used here?
I have this setup:
packages/api/src/trpc.ts
interface AuthContext {
auth: SignedInAuthObject | SignedOutAuthObject;
}
/**
* 1. CONTEXT
*
* This section defines the "contexts" that are available in the backend API.
*
* These allow you to access things when processing a request, like the database, the session, etc.
*
* This helper generates the "internals" for a tRPC context. The API handler and RSC clients each
* wrap this and provides the required context.
*
* @see https://trpc.io/docs/server/context
*/
export const createContextInner = async ({ auth }: AuthContext) => {
return {
auth,
db,
};
};
export const createContext = async (opts: CreateNextContextOptions) => {
return await createContextInner({ auth: getAuth(opts.req) });
};
/**
* 2. INITIALIZATION
*
* This is where the trpc api is initialized, connecting the context and
* transformer
*/
const t = initTRPC.context<typeof createContext>().create({
transformer: superjson,
errorFormatter: ({ shape, error }) => ({
...shape,
data: {
...shape.data,
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
},
}),
});
/**
* Create a server-side caller
* @see https://trpc.io/docs/server/server-side-calls
*/
export const createCallerFactory = t.createCallerFactory;
/**
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
*
* These are the pieces you use to build your tRPC API. You should import these
* a lot in the /src/server/api/routers folder
*/
/**
* This is how you create new routers and subrouters in your tRPC API
* @see https://trpc.io/docs/router
*/
export const createTRPCRouter = t.router;
/**
* Public (unauthed) procedure
*
* This is the base piece you use to build new queries and mutations on your
* tRPC API. It does not guarantee that a user querying is authorized, but you
* can still access user session data if they are logged in
*/
export const publicProcedure = t.procedure;
/**
* Protected (authenticated) procedure
*
* If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies
* the session is valid and guarantees `ctx.session.user` is not null.
*
* @see https://trpc.io/docs/procedures
*/
// check if the user is signed in, otherwise throw a UNAUTHORIZED CODE
const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.auth.userId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: {
auth: ctx.auth,
},
});
});
// export this procedure to be used anywhere in your application
export const protectedProcedure = t.procedure.use(isAuthed);
And in the apps/nextjs/src/trpc/server..ts
/**
* This wraps the `createContext` helper and provides the required context for the tRPC API when
* handling a tRPC call from a React Server Component.
*/
const createContextRSC = cache(async () => {
// not sure what to use here, I do not need Headers, or session
});
export const api = createCaller(createContextRSC);
packages/api/src/index.ts
const createCaller = createCallerFactory(appRouter);
packages/api/src/root.ts
import { authRouter } from "./router/auth";
import { postRouter } from "./router/post";
import { createTRPCRouter } from "./trpc";
export const appRouter = createTRPCRouter({
auth: authRouter,
post: postRouter,
});
// export type definition of API
export type AppRouter = typeof appRouter;
Thank you all!
@mkcode , Thank you so much for this useful, it is working perfectly. I have only one issue, I am not able to build app. I am getting the below error while doing next build, I am not able to figure out how to fix it.
The inferred type of 'api' cannot be named without a reference to '../../../../packages/db/node_modules/@prisma/client/runtime/library'. This is likely not portable. A type annotation is necessary.ts(2742)
It comes when I am using db in creating context,
import { db } from "~/server/db";
https://gist.github.com/mkcode/a590d1c8f7b0a37b8299965de7f7e958#file-trpc-ts-L38
If I don't use any db then this error not showing, by the way, I am using Prisma as db client which is also on a separate package folder in my turbo repo
You can check my repo branch here - https://github.com/growupanand/ConvoForm/tree/145-add-trpc-for-backend
@growupanand - It seems to me that the most likely reason for this would be that you forgot to run the build step to generate to prisma typescript definitions. This used to be in the old t3-turbo repo when it used prisma, but it was removed when they migrated to Drizzle b/c drizzle doesn't need a build step.
The issue seems mostly unrelated to the code I posted above, please ensure that you can import and call a method on db itself.
@mkcode It is fixed now,
I follow this guide, In short, the solution is
Instead of using import { PrismaClient } from "@prisma/client";
We will use import { PrismaClient } from "../lib/generated/client";
This lib folder can be generated by adding the below line in your schema.prisma 's client section
output = "../lib/generated/client"
@mkcode Do you have utils to invalidate a specific query?
Like this in the docs https://tanstack.com/query/v4/docs/framework/react/guides/query-keys
@growupanand Thank you so much this code works.
But the docs in tRPC when handling the invalidate doesn't work
https://trpc.io/docs/client/react/useUtils
const utils = trpc.useUtils();
utils.post.all.invalidate();
utils.post.byId.invalidate({ id: input.id });
Your provided solution works
import { useQueryClient } from "@tanstack/react-query";
const queryClient = useQueryClient();
queryClient.invalidateQueries({
queryKey: [["workspace", "getAll"]],
});
How do you debug in vscode using this tRPC? It is not working for me, below is my launch.json
,
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "pnpm run dev"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",
"type": "node-terminal",
"request": "launch",
"command": "pnpm run dev",
"serverReadyAction": {
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}
Im using the new (still in beta but working) version of create t3 app that uses trpc 11 and tanstack 5, (link to pr here: t3-oss/create-t3-app#1741). I implemented everything you have but it's not working, i consistently get "unauthorized - not signed in",
any tips on adapting what you did to the updated version?
@alexander-densley - I won't have time to look into this for at least another week. It seems likely that the issue would be somewhere in this file: https://github.com/t3-oss/create-t3-app/pull/1741/files#diff-9e58d835a446da0f5eac0b0cddedfb036c45bef6b184fdfb09f910ae465928e7
Where we would need the get the getAuth
function into the new createCaller
method somehow.
Please share with us any progress you can make on this!
Im using the new (still in beta but working) version of create t3 app that uses trpc 11 and tanstack 5, (link to pr here: t3-oss/create-t3-app#1741). I implemented everything you have but it's not working, i consistently get "unauthorized - not signed in",
any tips on adapting what you did to the updated version?
@alexander-densley I recently upgraded tRPC 11 and tanstack 5 in my Project convoform, you can see this commit - growupanand/ConvoForm@131c0a7
Thank you so much mkcode, you've helped me out big time!
Thank you!
Thank you @mkcode you are a living legend ❤️
thank you @mkcode saved me a lot of headaches
Thank you. This solved hours of searching and trying.
Is this solution "production-ready" @mkcode?
I'm asking because I'm not sure about this part.
auth: getAuth(
new NextRequest("https://notused.com", { headers: headers() }),
),
Is this solution "production-ready" @mkcode? I'm asking because I'm not sure about this part.
auth: getAuth( new NextRequest("https://notused.com", { headers: headers() }), ),
Have you been able to find alternate solution to this ?
@oxillix - It's the same way that clerk itself does it. See: https://github.com/clerk/javascript/blob/ca67ee79be638eb902695cd0f599f68201beff3d/packages/nextjs/src/app-router/server/auth.ts#L10-L13
and
https://github.com/clerk/javascript/blob/ca67ee79be638eb902695cd0f599f68201beff3d/packages/nextjs/src/app-router/server/utils.ts#L6