Problem:
I want to get all user data from populate, but it is not populated the data with 1:1 relationship between User and Player model.
Schemas are simplified.
User schema:
export interface IUser {
_id: string;
name: string;
surname: string;
avatar: string;
age: number;
email: string;
password: string;
roles: (typeof ObjectId)[]; // loaded ok
}
const transform = (doc, ret, options) => {
ret.id = ret._id;
delete ret._id;
delete ret.password;
delete ret.roles;
return ret;
};
@Schema({
versionKey: false,
toObject: {
transform,
},
toJSON: {
transform,
},
})
export class User implements IUser {
@ApiProperty()
@Prop({
default: randomUUID,
type: UUID,
required: true,
transform: (id: any) => id.toString(),
})
_id: string;
...
@ApiProperty()
@Prop({
type: [
{
type: ObjectId,
ref: Role.name,
required: true,
},
],
})
roles: (typeof ObjectId)[]; // Works fine
}
Player schema:
export interface IPlayer {
nickname: string;
user: typeof UUID;
}
// To convert _id to id
const transform = (doc, ret, options) => {
ret.id = ret._id;
delete ret._id;
delete ret._gen;
return ret;
}
@Schema({
versionKey: '_gen',
toObject: {
transform
},
toJSON: {
transform
},
})
export class Player implements IPlayer {
@ApiProperty()
@Prop({
type: String,
index: true,
required: true,
})
nickname: string;
@ApiProperty()
@Prop({
type: {
type: UUID,
ref: User.name,
required: true,
unique: true,
},
})
user: typeof UUID;
}
export const PlayerSchema = SchemaFactory.createForClass(Player);
Players service
@Injectable()
export class PlayersService {
constructor(
@InjectModel(Player.name)
private playerModel: Model<IPlayer>,
) {}
async find(): Promise<IPlayer[]> {
return await this.playerModel
.find({
_id: '658ddee2f71a72e6d8ea9698', // just for testing to find specific player
})
.populate('user')
.exec();
}
Players controller
@ApiTags('player')
@Controller('players')
export class PlayersController {
constructor(private playersService: PlayersService) {}
@Get()
async find(@Res() res: Response) {
const players = await this.playersService.find();
return res.json(players);
}
}
With this configuration my result is:
❌ As you see, user is not populated. User prop has UUID as string (it equals to new UUID(b21439b9-87ff-4646-b9e1-780797c887e8).toString()
)
[
{
"id": "658ddee2f71a72e6d8ea9698"
"nickname": "nickname",
"user": "b21439b9-87ff-4646-b9e1-780797c887e8",
}
]
Then I tried to do same as I did with user-roles relationships (that are work fine).
Changes in Player Schema:
type is an array
transform function to get first elem from the array
...
@ApiProperty()
@Prop({
type: [
{
type: UUID,
ref: User.name,
required: true,
unique: true,
},
],
transform: (docs) => docs.at(0),
})
user: typeof UUID;
...
✅ Not it works, but it is mostly workaround than a proper way and would be nice to avoid it
[
{
"id": "658ddee2f71a72e6d8ea9698"
"nickname": "nickname",
...
"user": {
"id": "b21439b9-87ff-4646-b9e1-780797c887e8"
"email": "[email protected]"
},
}
]
Wishes
I want to describe Prop without array and transform function as in code below and get data directly
@ApiProperty()
@Prop({
type: {
type: UUID,
ref: User.name,
required: true,
unique: true,
},
})
user: typeof UUID;