Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript - How to define model in combination with using mongoose populate?

Background

I'm using mongoose and TypeScript in my Node.JS app. I'm using mongoose's populate in a bunch of places when fetching data from the database.

The issue I'm facing is that I don't know how to type my models so that a property can be either an ObjectId or populated with data from another collection.

What I've tried

I've attempted using union types in my model type definition, which seems like something that TypeScript offers to cover these kind of things:

interface User extends Document {     _id: Types.ObjectId;     name: string }  interface Item extends Document {     _id: Types.ObjectId;      // Union typing here     user: Types.ObjectId | User; } 

My schema only defines the property as an ObjectId with ref.

const ItemSchema = new Schema({     user: { type: Schema.Types.ObjectId, ref: "User", index: true } }) 

Example:

So I might do something like this:

ItemModel.findById(id).populate("user").then((item: Item) => {     console.log(item.user.name); }) 

Which produces the compilation error:

[ts] Property 'name' does not exist on type 'User | ObjectId'.      Property 'name' does not exist on type 'ObjectId'. 

Question

How can I have a model property that can be either of two types in TypeScript?

like image 910
maxpaj Avatar asked Dec 21 '17 10:12

maxpaj


People also ask

What does populate method do Mongoose?

Mongoose Populate() Method. In MongoDB, Population is the process of replacing the specified path in the document of one collection with the actual document from the other collection.

What is the difference between schema and model in Mongoose?

Mongoose Schema vs. Model. A Mongoose model is a wrapper on the Mongoose schema. A Mongoose schema defines the structure of the document, default values, validators, etc., whereas a Mongoose model provides an interface to the database for creating, querying, updating, deleting records, etc.

Does Mongoose model create collection?

Mongoose never create any collection until you will save/create any document.

What does Mongoose model return?

mongoose. model() returns a Model ( It is a constructor, compiled from Schema definitions).


1 Answers

You need to use a type guard to narrow the type from Types.ObjectId | User to User...

If you are dealing with a User class, you can use this:

if (item.user instanceof User) {     console.log(item.user.name); } else {     // Otherwise, it is a Types.ObjectId } 

If you have a structure that matches a User, but not an instance of a class (for example if User is an interface), you'll need a custom type guard:

function isUser(obj: User | any) : obj is User {     return (obj && obj.name && typeof obj.name === 'string'); } 

Which you can use with:

if (isUser(item.user)) {     console.log(item.user.name); } else {     // Otherwise, it is a Types.ObjectId } 

If you don't want to check structures for this purpose, you could use a discriminated union.

like image 180
Fenton Avatar answered Nov 08 '22 12:11

Fenton