Strict Model
Strict Model
Use toStrictModel<T>() when you want .populate() to change the TypeScript result type as you chain Mongoose queries. Start with ordinary references; use this wrapper when population types become hard to track.
Define every referenced path
Register the referenced Mongoose models before querying. The same schema below defines author, author.user, and mentions, so each population example has a real path.
import mongoose from 'mongoose';
import {z} from 'zod/v4';
import {toMongooseSchema, toStrictModel, zRef} from '@nullix/zod-mongoose';
const UserSchema = z.object({username: z.string()});
const AuthorSchema = z.object({
name: z.string(),
user: zRef('Account', UserSchema),
});
const PostSchema = z.object({
title: z.string(),
author: zRef('Author', AuthorSchema),
mentions: z.array(zRef('Author', AuthorSchema)),
});
mongoose.model('Account', toMongooseSchema(UserSchema));
mongoose.model('Author', toMongooseSchema(AuthorSchema));
const PostModel = toStrictModel<z.infer<typeof PostSchema>>(
'Post',
toMongooseSchema(PostSchema),
);
Populate one or more paths
These queries require an active MongoDB connection and build on the schemas above.
const post = await PostModel.findOne({title: 'Hello'}).populate('author').exec();
if (post?.author) {
console.log(post.author.name); // Typed as the populated Author document
}
const deepPost = await PostModel.findOne().populate('author.user').exec();
if (deepPost?.author?.user) {
console.log(deepPost.author.user.username);
}
const withMentions = await PostModel.findOne()
.populate('author mentions')
.exec();
const withNestedOptions = await PostModel.findOne().populate({
path: 'author',
populate: {path: 'user'},
}).exec();
toStrictModel<T>(name, mongooseSchema) compiles a Mongoose model and returns it with enhanced populate() and exec() types. Pass the Zod-inferred document shape as T. StrictDocument<DocType>, StrictQuery<Result, DocType>, and StrictModel<RawModel, DocType> are exported types for advanced usage; most applications only need toStrictModel().