question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Why findById returns (User & { _id: Schema.Types.ObjectId; }) | null they User | null

See original GitHub issue

This is my first time using typescript and mongoose. Here’s my code

type

export interface User extends Document {
  _id: ObjectId;
  lastName: string;
}

Schema

const userSchema = new Schema<User>({
  lastName: { type: String, required: true, trim: true },
});

model

const User = model<User>('user', UserSchema, 'users');

request

const user = await User.findById(userId).exec();

I expect the user variable to be of type User | null.but i get (User & {_id: Schema.Types.ObjectId;}) | null. If I don’t declare _id: ObjectId; then I get ( User & { _id: any; }) | null. I do not know if this is some kind of mistake or it should be like this

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
vkarpov15commented, Dec 26, 2021

@drewkht 's comment https://github.com/Automattic/mongoose/issues/10987#issuecomment-996170399 is correct. We added a note about the Schema.Types.ObjectId vs Types.ObjectId distinction in #10949

1reaction
drewkhtcommented, Dec 17, 2021

I’ve the same code except that _id is any in the interface, the return type of a findOne is:

const user: IUser & {
    _id: any;
}

I think it should be IUser, where this & comes from?

The return type comes from the internal types used by mongoose.model() to create the User model.

Mongoose’s HydratedDocument<T> type is used for all documents created/queried by a model of type Model<T>. It’s explained further in the linked Mongoose docs.

Because the User interface extends Document, HydratedDocument<User> resolves to the Mongoose internal type Require_id<User>, which simply creates a type ~union~ intersection User & { _id: User['_id'] } (to, I assume, ensure that the _id property is non-nullable - as the _id property on Mongoose’s Document type is nullable).

User & { _id: Schema.Types.ObjectId } is a functionally identical type to User, because User already contains the property _id: Schema.Types.ObjectId. It definitely looks weird if you’re not familiar with what’s going on behind the scenes or with TypeScript ~unions~ intersections, though.

@Darfion - I believe if you just change the _id property in the User interface to Types.ObjectId instead you’ll get the proper results (i.e. const user will be of type HydratedDocument<User, ...> | null). Types.Schema.ObjectId is only intended to be used within schema definitions, from what I understand.

Also, a couple potential issues in the provided examples:

export interface User extends Document { ... }
const User = model<User>('user', UserSchema, 'users');

At least in my VS Code with my tsconfig.json, this causes a naming conflict between the User interface and const User, resulting in the latter resolving to any. Either renaming the interface to something like IUser or const User to something like const UserModel would solve this problem.

const userSchema = new Schema<User>({ ... });
const User = model<User>('user', UserSchema, 'users');

In the above, userSchema is used to create the schema, but then UserSchema (with an uppercase U) is passed to the model function. Because you’re passing User as a type parameter to the model call (which isn’t necessary if you’re passing the type to Schema already) you’ll still get a correctly typed Model from it, but I’m not sure what other issues might arise from not providing an actual Schema to the function.

I rewrote the example like this and it works as expected in my testing:

import { Types, model, Document, Schema } from 'mongoose';

export interface IUser extends Document {
  _id: Types.ObjectId;
  lastName: string;
}

const userSchema = new Schema<IUser>({
  lastName: { type: String, required: true, trim: true },
});

const User = model('user', userSchema, 'users');

const user = await User.findById(userId).exec();   // user: HydratedDocument<IUser, {}, {}> | null
if (user) user._id;   // user._id: Types.ObjectId
Read more comments on GitHub >

github_iconTop Results From Across the Web

Why findById returns (User & { _id: Schema.Types.ObjectId ...
I expect the user variable to be of type User | null. Well, this just happens to be an invalid expectation. In the...
Read more >
findById() is not working properly - Code with Mosh Forum
FindById () is not working and giving null. Where as find(), findOne() works seamlessly. I tried many many solutions to make findById() work....
Read more >
findById returns no results even with correct ObjectId #3079
I had a similar issue today. It turns out i saved an _id as a ObjectId string and tried to retrieve id using...
Read more >
findById in mongoose returns null for a certain schema ...
Since you haven't specified the _id field in your Schema it will be of type ObjectId . For findById you can pass id...
Read more >
Mongoose findById() is returning null? - JavaScript
It's also not of type String but it's an ObjectId . I have no clue what happens if you tell Mongoose to save...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found