ViralV
tRPC13mo ago
1 reply
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.

//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 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
Was this page helpful?