'req' of undefined in onError of express middleware

I've recently noticed that I get a bunch of errors regarding req object missing in ctx for onError property in trpc express middleware. Can't figure out how req obj might be undefined at the point of onerror handler??
app.use(
'/trpc',
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext: createTRPCContext,
onError: ({ path: errorPath, error, ctx }) => {
if (isDevelopment) {
ctx.req.logger.debug(
`❌❌❌ tRPC failed on ${errorPath ?? '<no-path>'}: ${
error.message
}`,
)
}

if (error.code === 'INTERNAL_SERVER_ERROR') ctx.req.logger.error(error)
else ctx.req.logger.warn(error)
},
}),
)
app.use(
'/trpc',
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext: createTRPCContext,
onError: ({ path: errorPath, error, ctx }) => {
if (isDevelopment) {
ctx.req.logger.debug(
`❌❌❌ tRPC failed on ${errorPath ?? '<no-path>'}: ${
error.message
}`,
)
}

if (error.code === 'INTERNAL_SERVER_ERROR') ctx.req.logger.error(error)
else ctx.req.logger.warn(error)
},
}),
)
Unhandled rejection, reason: TypeError: Cannot read property 'req' of undefined     at Object.onError (/home/app/server/index.js:135:90)     at onError (/home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/nodeHTTPRequestHandler-11f3df04.js:72:32)     at Object.resolveHTTPResponse (/home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/resolveHTTPResponse-94b380d2.js:187:18)     at /home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/nodeHTTPRequestHandler-11f3df04.js:63:50     at async /home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/adapters/express.js:16:9
}
Unhandled rejection, reason: TypeError: Cannot read property 'req' of undefined     at Object.onError (/home/app/server/index.js:135:90)     at onError (/home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/nodeHTTPRequestHandler-11f3df04.js:72:32)     at Object.resolveHTTPResponse (/home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/resolveHTTPResponse-94b380d2.js:187:18)     at /home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/nodeHTTPRequestHandler-11f3df04.js:63:50     at async /home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/adapters/express.js:16:9
}
N
Nick408d ago
It’s actually ctx which is undefined Can you share your createContext? Also do you have any middlewares doing context swapping?
M
Mugetsu408d ago
Ahh so seems I setup something in a wrong way coz I was sure ctx will have req/res at all times. yeah sure here is my whole setup
import { TRPCError, initTRPC } from '@trpc/server'
import superjson from 'superjson'
import * as trpcExpress from '@trpc/server/adapters/express'
import express from 'express'

import { cleanAuthorisation } from '../middleware/auth-middleware'
import {
SchemaType,
UserRoles,
} from '../../universal/components/utils/constants'

type CreateContextOptions = {
req: express.Request
res: express.Response
currentSchema: any
}

type MetaOptions = {
allowedRoles: UserRoles[]
}

export const createInnerTRPCContext = async (opts: CreateContextOptions) => ({
req: opts.req,
res: opts.res,
currentSchema: opts.currentSchema,
})

const getCurrentSchema = (req: express.Request) => {
const { schemaId, locationSchemas } = req

return (
locationSchemas.find(({ id }) => id === schemaId) ||
locationSchemas.find(({ canWrite }) => canWrite) ||
locationSchemas[0]
)
}

export const createTRPCContext = async (
opts: trpcExpress.CreateExpressContextOptions,
) => {
const { req, res } = opts
const currentSchema = getCurrentSchema(req)

return createInnerTRPCContext({
req,
res,
currentSchema,
})
}
import { TRPCError, initTRPC } from '@trpc/server'
import superjson from 'superjson'
import * as trpcExpress from '@trpc/server/adapters/express'
import express from 'express'

import { cleanAuthorisation } from '../middleware/auth-middleware'
import {
SchemaType,
UserRoles,
} from '../../universal/components/utils/constants'

type CreateContextOptions = {
req: express.Request
res: express.Response
currentSchema: any
}

type MetaOptions = {
allowedRoles: UserRoles[]
}

export const createInnerTRPCContext = async (opts: CreateContextOptions) => ({
req: opts.req,
res: opts.res,
currentSchema: opts.currentSchema,
})

const getCurrentSchema = (req: express.Request) => {
const { schemaId, locationSchemas } = req

return (
locationSchemas.find(({ id }) => id === schemaId) ||
locationSchemas.find(({ canWrite }) => canWrite) ||
locationSchemas[0]
)
}

export const createTRPCContext = async (
opts: trpcExpress.CreateExpressContextOptions,
) => {
const { req, res } = opts
const currentSchema = getCurrentSchema(req)

return createInnerTRPCContext({
req,
res,
currentSchema,
})
}
const t = initTRPC
.context<typeof createTRPCContext>()
.meta<MetaOptions>()
.create({
transformer: superjson,
errorFormatter({ shape, ctx }) {
const { data, ...rest } = shape

return {
...rest,
data: {
...data,
traceId: ctx?.req?.traceId,
schemaId: ctx?.req?.schemaId,
},
}
},
defaultMeta: { allowedRoles: [] },
})

export const createTRPCRouter = t.router

const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.req?.authorisation?.authorised) {
cleanAuthorisation(ctx.req)

throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'User not authorised',
})
}

return next({
ctx: {
req: ctx.req,
res: ctx.res,
},
})
})

const enforceUserRoles = enforceUserIsAuthed.unstable_pipe(
({ ctx, meta, next }) => {
const currentRoles = ctx.req?.authorisation?.userSecurityGroups ?? []
const allowedRoles = meta?.allowedRoles ?? []

if (!currentRoles.length) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'User roles are missing',
})
}

if (
allowedRoles.length &&
!allowedRoles.some((role) => currentRoles.includes(role))
) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'User role not allowed',
})
}

return next({ ctx })
},
)

const enforceValidSchema = enforceUserRoles.unstable_pipe(({ ctx, next }) => {
const { schemaId } = ctx.req

if (schemaId && !Object.values(SchemaType).includes(schemaId)) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: `SchemaId not supported: (${schemaId})`,
})
}

return next({ ctx })
})

export const publicProcedure = t.procedure
export const procedure = t.procedure.use(enforceValidSchema)
const t = initTRPC
.context<typeof createTRPCContext>()
.meta<MetaOptions>()
.create({
transformer: superjson,
errorFormatter({ shape, ctx }) {
const { data, ...rest } = shape

return {
...rest,
data: {
...data,
traceId: ctx?.req?.traceId,
schemaId: ctx?.req?.schemaId,
},
}
},
defaultMeta: { allowedRoles: [] },
})

export const createTRPCRouter = t.router

const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.req?.authorisation?.authorised) {
cleanAuthorisation(ctx.req)

throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'User not authorised',
})
}

return next({
ctx: {
req: ctx.req,
res: ctx.res,
},
})
})

const enforceUserRoles = enforceUserIsAuthed.unstable_pipe(
({ ctx, meta, next }) => {
const currentRoles = ctx.req?.authorisation?.userSecurityGroups ?? []
const allowedRoles = meta?.allowedRoles ?? []

if (!currentRoles.length) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'User roles are missing',
})
}

if (
allowedRoles.length &&
!allowedRoles.some((role) => currentRoles.includes(role))
) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'User role not allowed',
})
}

return next({ ctx })
},
)

const enforceValidSchema = enforceUserRoles.unstable_pipe(({ ctx, next }) => {
const { schemaId } = ctx.req

if (schemaId && !Object.values(SchemaType).includes(schemaId)) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: `SchemaId not supported: (${schemaId})`,
})
}

return next({ ctx })
})

export const publicProcedure = t.procedure
export const procedure = t.procedure.use(enforceValidSchema)
N
Nick408d ago
context<typeof createTRPCContext>() needs a inferAsyncReturnType<> sprinkled in there, but might not be your issue here Honestly this does look okay, it's a nice clean setup enforceUserIsAuthed also shouldn't need to add the req/res to context since you already did that I'm really just finding silly things though, and am confused why you're getting an undefined context If you're able to throw together a reproduction in stackblitz or something, a GitHub issue might be in order
M
Mugetsu408d ago
Yeah it will be hard to reproduce. No matter how hard I try I can't reproduce it locally. Happening only on Prod. I will try add a bit more logging maybe that will help me track down the problem.
N
Nick408d ago
That might be a more useful clue than you think Figure out what's really different in prod, in many cases it's quite a lot!
More Posts
Mutations and Queries in React Contexts causing unexpected behaviorsIn my organization, we recently moved to put all of our mutations/queries into React Contexts and ouCustom error managementHey peeps! I could've sworn I created a GitHub issue about this, but I must've been dreaming, becauscontext questionWhy are the context examples only showing opinionated examples with next/react auth ? Also i find itasync middlewareis it possible to define an async middleware? I want to do something like this but it throws errors The type of the second route of the nested route is neverhttps://github.com/StringKe/nx-mulit-trpc-type-error/blob/main/apps/app1/src/pages/index.tsx#L12errorFormatter ignored when using appRouter.createCallerMy errorFormatter works correctly in the actual application using an adapter, e.g. ``` trpcExpress.Is there a way to define the Websocket protocol when using wsLink()I am attempting to use tRPC with Azure's Pub/Sub Websockets service it appears to require custom proThe inferred type of this node exceeds the maximum length the compiler will serialize.Hey, there I am running into this error when I have more than 12 routers in the mergeRouters functioQuickstart not workingHello there, There is a type error in the quickstart example if i'm not mistaken. I pretty much coUse TRPC function inside of a TRPC functionHi all! We have a trpc query called getProduct. I want to use this getProduct query inside of anotheSupabase with trpcWhen trying to configure trpc with supabase, getting this kind of error in the console `tRPC failed Using tRPC for uploading audio filesI want to create an api router in tRPC but am not sure if the following code is doable with tRPC. IfEnigmatic INTERNAL SERVER ERRORIm having a problem finding out about what is causing the INTERNAL SERVER ERROR from tRPC in my prodcreateProxySSGHelpers type errorHi all! Anyone know how to use createProxySSGHelpers with appropriate createContext typing?Can I force metadata with the createCaller?I have custom metadata which I check within the middleware. Im trying to write tests for this. CurreVitest error testHow can I test for specific TRPCError code with vitest? I managed to get the message but can't figurUsing generics duplicates the types instead of referring to the existing typesHi! I'm creating a backend API using TRPC, where I'm encountering a slight problem. TL;DR; when defSet custom header for fetchRequestHandlerIs this possible? currently getting cors issue. Trying to use nextjs's edge function with trpc.. ``How to enforce usequery as NonNullableI have a custom hook with infinite query. I check for undefined at the app first render and then it How to call useQuery with params inside JSXHow can i achieve this? Thanks ``` export function MemberQueryList({ list }: Props) { function