How do I type hint the user argument when calling passport.serializeUser in TypeScript?

Viewed 1284

Using TypeScript, I'm writing an API using Express and Mongoose, and I'm trying to set up authentication using Passport, but I'm getting a "Property does not exist" error when trying to pass a serializer function to passport.

According to the documentation, I need to call passport.serializeUser with a function that takes my user object and calls done with that user's ID.

I'm getting the type definitions from @types/passport, and the declaration file types the user argument for the serializeUser function as Express.User:

serializeUser<TID>(fn: (user: Express.User, done: (err: any, id?: TID) => void) => void): void;

But Express.User - also defined by @types/passport - is an empty interface:

declare global {
    namespace Express {
        // tslint:disable-next-line:no-empty-interface
        interface AuthInfo {}
        // tslint:disable-next-line:no-empty-interface
        interface User {}

Here's my serializer. TypeScript complains that Property '_id' does not exist on type 'User'.

passport.serializeUser((user, done) => {
  done(null, user._id);
});

To work around this, I've added my type definition that add the _id property to Express.User:

declare namespace Express {
  interface User {
    _id?: string;
  }
}

This eliminates the TypeScript error, but it doesn't seem like this is the correct way to solve the problem. Is there a different way to provide a serializer function that doesn't require me to override the Express.User type definition?

1 Answers

The way Declaration Merging you've done is recommended by @types/express contributors. You can find how they test serializerUser with TypeScript here.

declare global {
  namespace Express {
    interface User {
      username: string;
      _id?: number;
    }
  }
}

Another way is to type User argument of callback function each time you use the function:

type User = {
  _id?: number
}

passport.serializeUser((user: User, done) => {
  done(null, user._id);
});

I think Declaration Merging is a bit smarter way.

Related