T
Join ServertRPC
❓-help
'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)
},
}),
)
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
}
It’s actually ctx which is undefined
Can you share your createContext? Also do you have any middlewares doing context swapping?
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,
})
}
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)
context<typeof createTRPCContext>()
needs a inferAsyncReturnType<>
sprinkled in there, but might not be your issue hereHonestly 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 thatI'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
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.
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!