Viral
Viral
TtRPC
Created by Viral on 12/26/2024 in #❓-help
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.
//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)
I have tried externalDir experimental flag being enabled on NextJSConfig I am using Bun
2 replies