Viral
Express/NextJS Server Side Setup
After following the T3Repo TRPC setup, I attempted to modify it for an Express API. Everything works great except... on server side requests it runs the backend as it imports createCaller from the backend.
I have been lost trying to configure the NextJS Config and TSConfig to see if I can get out of this but I am out of ideas.
I have tried externalDir experimental flag being enabled on NextJSConfig
I am using Bun
//NextJS /server.ts
import 'server-only'
import { createHydrationHelpers } from '@trpc/react-query/rsc'
import { headers } from 'next/headers'
import { cache } from 'react'
import { type AppRouter, createCaller } from '@backend/routes/_app'
import { createTRPCContext } from '@backend/routes/trpc'
import { createQueryClient } from './query-client'
/**
* 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(async () => {
const heads = new Headers(await headers())
heads.set('x-trpc-source', 'rsc')
return createTRPCContext({
headers: heads,
})
})
const getQueryClient = cache(createQueryClient)
const caller = createCaller(createContext)
export const { trpc: api, HydrateClient } = createHydrationHelpers<AppRouter>(
caller,
getQueryClient,
)
//NextJS /server.ts
import 'server-only'
import { createHydrationHelpers } from '@trpc/react-query/rsc'
import { headers } from 'next/headers'
import { cache } from 'react'
import { type AppRouter, createCaller } from '@backend/routes/_app'
import { createTRPCContext } from '@backend/routes/trpc'
import { createQueryClient } from './query-client'
/**
* 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(async () => {
const heads = new Headers(await headers())
heads.set('x-trpc-source', 'rsc')
return createTRPCContext({
headers: heads,
})
})
const getQueryClient = cache(createQueryClient)
const caller = createCaller(createContext)
export const { trpc: api, HydrateClient } = createHydrationHelpers<AppRouter>(
caller,
getQueryClient,
)
//Backend _app.ts
import { createCallerFactory, createTRPCRouter, publicProcedure } from './trpc'
import { z } from 'zod'
export const appRouter = createTRPCRouter({
// Example procedures
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
}
}),
getUser: publicProcedure.input(z.string()).query((opts) => {
return { id: opts.input, name: 'Example User' }
})
})
export type AppRouter = typeof appRouter
export const createCaller = createCallerFactory(appRouter)
//Backend _app.ts
import { createCallerFactory, createTRPCRouter, publicProcedure } from './trpc'
import { z } from 'zod'
export const appRouter = createTRPCRouter({
// Example procedures
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
}
}),
getUser: publicProcedure.input(z.string()).query((opts) => {
return { id: opts.input, name: 'Example User' }
})
})
export type AppRouter = typeof appRouter
export const createCaller = createCallerFactory(appRouter)
//Backend trpc.ts
import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
import { ZodError } from 'zod'
import * as trpcExpress from '@trpc/server/adapters/express'
type CreateContextOptions = {
req?: trpcExpress.CreateExpressContextOptions['req']
headers?: Headers
}
export const createTRPCContext = async (opts: CreateContextOptions) => {
return {
headers: opts.req?.headers ?? opts.headers,
}
}
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,
},
}
},
})
/**
* Create a server-side caller.
*
* @see https://trpc.io/docs/server/server-side-calls
*/
export const createCallerFactory = t.createCallerFactory
export const createTRPCRouter = t.router
/**
* Middleware for timing procedure execution and adding an artificial delay in development.
*
* You can remove this if you don't like it, but it can help catch unwanted waterfalls by simulating
* network latency that would occur in production but not in local development.
*/
const timingMiddleware = t.middleware(async ({ next, path }) => {
const start = Date.now()
if (t._config.isDev) {
// artificial delay in dev
const waitMs = Math.floor(Math.random() * 400) + 100
await new Promise((resolve) => setTimeout(resolve, waitMs))
}
const result = await next()
const end = Date.now()
console.log(`[TRPC] ${path} took ${end - start}ms to execute`)
return result
})
export const publicProcedure = t.procedure.use(timingMiddleware)
//Backend trpc.ts
import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
import { ZodError } from 'zod'
import * as trpcExpress from '@trpc/server/adapters/express'
type CreateContextOptions = {
req?: trpcExpress.CreateExpressContextOptions['req']
headers?: Headers
}
export const createTRPCContext = async (opts: CreateContextOptions) => {
return {
headers: opts.req?.headers ?? opts.headers,
}
}
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,
},
}
},
})
/**
* Create a server-side caller.
*
* @see https://trpc.io/docs/server/server-side-calls
*/
export const createCallerFactory = t.createCallerFactory
export const createTRPCRouter = t.router
/**
* Middleware for timing procedure execution and adding an artificial delay in development.
*
* You can remove this if you don't like it, but it can help catch unwanted waterfalls by simulating
* network latency that would occur in production but not in local development.
*/
const timingMiddleware = t.middleware(async ({ next, path }) => {
const start = Date.now()
if (t._config.isDev) {
// artificial delay in dev
const waitMs = Math.floor(Math.random() * 400) + 100
await new Promise((resolve) => setTimeout(resolve, waitMs))
}
const result = await next()
const end = Date.now()
console.log(`[TRPC] ${path} took ${end - start}ms to execute`)
return result
})
export const publicProcedure = t.procedure.use(timingMiddleware)
2 replies