Search code examples
typescriptmongodbexpressmongoosetypegoose

Typescript is not recognising the type of user from express-session


I'm retrieving the user object from the database and setting it on the express-session:

export const postLogin = async (
    request: Request,
    response: Response,
    next: NextFunction
): Promise<void> => {
    try {
        request.session.user = await UserModel.findById('6127bd9d204a47128947a07d').orFail().exec()
        response.redirect('/')
    } catch (error) {
        next(error)
    }
}

Then I call the Mongoose method populate() on the user object to get the cart associated with it:

export const getCart = async (
    request: Request,
    response: Response,
    next: NextFunction
): Promise<void> => {
    try {
        const userWithCartProducts = await request.session.user
            .populate('cart.items.productId')
            .execPopulate()
    } catch (error) {
        next(error)
    }
}

But here I'm getting an error: TypeError: request.session.user.populate is not a function

I have defined the custom user type on express-session like following:

declare module 'express-session' {
    interface SessionData {
        user?: DocumentType<User>
    }
}

As you can see in above definition of user, I'm using DocumentType<User> because I'm typing my models using Typegoose. I'm not sure if this is how it's done for Typegoose.

What am I doing wrong? Any input would be much appreciated.


Solution

  • The problem is that the user object in the session object was fetched by the MongoDBStore. The MongoDBStore is not aware of the User model that is defined in Typegoose, so, when it fetches the data from the session database, it only fetches the raw data, not the methods defined inside the Typegoose model.

    So, to fix this, when a new request comes in, after initializing the express-session, in a middleware, we need to fetch the user once from the database and put in on the request object like following:

    app.use(initializeUser)
    
    export const initializeUser = async (request: Request, response: Response, next: NextFunction): Promise<void> => {
        try {
            request.user = await User.findById(request.session.user._id).orFail().exec()
            next()
        } catch (error) {
            next(error)
        }
    }
    

    For Typegoose, define the User model on the Request as following:

    declare global {
        declare namespace Express {
            export interface Request {
                user?: DocumentType<User>
            }
        }
    }
    

    As mentioned in the question, in the login route, even though we are storing the user object in the session, the request dies down there and the session (along with the user object) gets saved to the database. But the next time the request comes in, the user object available in the session is retrieved by the MongoDBStore which has no knowledge of the User Model and the methods defined by the Typegoose. So, this is how we fix that problem. The same solution is also applied in Javascript with Mongoose.