Home > Software design >  How to make Express with Typescript and Passport "user" required after middleware?
How to make Express with Typescript and Passport "user" required after middleware?

Time:10-10

I have to keep doing stuff like

if (!req.user) return res.status(401).send()

The approach the comes into mind would be an express middleware for that. But even tho I can prevent non-logged users from reaching the route I can't think of a way to type express req correctly.

If I override the "req" param the router complains because the user key in "Express.User" is an optional parameter.

I don't believe changing the global override so "user" is required is a good option since "user" should only be required after the middleware validation. How can I achieve this?

Below some piece of useful code to understand the context.

Global Express Override

declare global {
  namespace Express {
    interface User extends TUser {
      _id: ObjectId
    }
  }
}

What I want to achieve

// Router
router.post('/', sessionMiddleware, controller)

// Middleware
const sessionMiddleware = (req: Request, res: Response, next: NextFunction) => {
if (!req.user) return res.status(401).send()
next()
}

// Controller
const controller = (req: RequestWithRequiredUser, res: Response) => {
  // user here can't possibly be typed undefined
  const user = req.user
  ...
}

What I actually have to do everytime:

const controller = (req: Request, res: Response) => {
  if (!req.user) return res.status(401).send()

  ...doSomethingElse
}

CodePudding user response:

I don't if better solution with middlewares are available but this is a workaround I found to avoid repeating logic.

buildSessionController.ts

/** Make given keys required */
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }

type TController<T extends Request> = (req: WithRequired<T, 'user'>, res: Response) => unknown

export const buildSessionController =
  <T extends Request>(controller: TController<T>) =>
  (req: Request, res: Response) => {
    if (!req.user) return res.status(401).send()

    // This is needed as a type workaround because a type mismatch happens otherwise
    // Using Omit instead of WithRequired above doesn't fix it
    const request = req as WithRequired<T, 'user'>

    return controller(request, res)
  }

someController.ts

export const expressController = 
  buildSessionController((req, res) => {
  
    // user will never be undefined here
    const { user } = req

    return res.json(user)
  })
  • Related