mongoose-tsgen
An plug-n-play Typescript interface generator for Mongoose.
Features
Compatibility
Installation
$ npm install -D mongoose-tsgen
$ npx mtgen --help # print usage
Usage
mtgen [ROOT_PATH]
Generate a Typescript file containing Mongoose Schema typings.
USAGE
$ mtgen [MODEL_PATH]
OPTIONS
-c, --config=config [default: ./] Path of mtgen.config.json or its root folder. CLI flag options will take precendence over settings in mtgen.config.json
-d, --dry-run Print output rather than writing to file.
-h, --help Show CLI help
-i, --imports=import Custom import statements to add to the output file. Useful if you use third-party types in your mongoose schema definitions. For multiple imports, specify this flag more than once.
-j, --js Search for Mongoose schemas in Javascript files rather than in Typescript files.
-o, --output=output [default: ./src/interfaces] Path of output file containing generated typings. If a folder path is passed, the generator
will default to creating an `mongoose.gen.ts` file in the specified folder.
-p, --project=project [default: ./] Path of tsconfig.json or its root folder.
--augment Augment generated typings into the 'mongoose' module
--no-format Disable formatting generated files with prettier and fixing with eslint.
--no-func-types Disable using TS compiler API for method, static and query typings.
Specify the directory of your Mongoose model definitions using MODEL_PATH
. If left blank, all sub-directories will be searched for models/*.ts
files (or models/*.js
). Files in this folder (other than an index file) are expected to export a Mongoose model.
See code: src/index.ts
Configuration File
All CLI options can be provided using a mtgen.config.json
file. Use the --config
option to provide the folder path containing this file ("./" will be searched if no path is provided). CLI options will take precendence over options in the mtgen.config.json
file.
mtgen.config.json
{
"imports": ["import Stripe from \"stripe\""],
"output": "./src/custom/path/mongoose-types.ts"
}
Example
./src/models/user.ts
import mongoose from "mongoose";
import { UserDocument, UserModel, UserQueries } from "../interfaces/mongoose.gen.ts";
const { Schema } = mongoose;
const UserSchema = new Schema({
email: {
type: String,
required: true
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
metadata: Schema.Types.Mixed,
friends: [
{
uid: {
type: Schema.Types.ObjectId,
ref: "User",
required: true
},
nickname: String
}
],
city: {
coordinates: {
type: [Number],
index: "2dsphere"
}
}
});
UserSchema.virtual("name").get(function (this: UserDocument) {
return `${this.firstName} ${this.lastName}`;
});
UserSchema.methods = {
isMetadataString(this: UserDocument) {
return typeof this.metadata === "string";
}
};
UserSchema.statics = {
async getFriends(this: UserModel, friendUids: UserDocument["_id"][]) {
return await this.aggregate([{ $match: { _id: { $in: friendUids } } }]);
}
};
const queryFuncs: UserQueries = {
populateFriends() {
return this.populate("friends.uid", "firstName lastName");
}
};
UserSchema.query = queryFuncs;
export const User: UserModel = mongoose.model<UserDocument, UserModel>("User", UserSchema);
export default User;
generate typings
$ mtgen
generated typings file ./src/interfaces/mongoose.gen.ts
import mongoose from "mongoose";
export interface UserFriend {
uid: User["_id"] | User;
nickname?: string;
_id: mongoose.Types.ObjectId;
}
export interface UserQueries {
populateFriends<Q extends mongoose.DocumentQuery<any, UserDocument, {}>>(
this: Q,
...args: any[]
): Q;
}
export interface UserModel extends mongoose.Model<UserDocument, UserQueries> {
getFriends: (this: any, friendUids: UserDocument["_id"][]) => Promise<any>;
}
export interface User {
email: string;
firstName: string;
lastName: string;
bestFriend?: mongoose.Types.ObjectId;
friends: UserFriend[];
city: {
coordinates?: number[];
};
_id: mongoose.Types.ObjectId;
}
export type UserFriendDocument = mongoose.Types.Subdocument & {
uid: UserDocument["_id"] | UserDocument;
} & UserFriend;
export type UserDocument = mongoose.Document & {
metadata?: any;
friends: mongoose.Types.DocumentArray<UserFriendDocument>;
city: {};
name: any;
isMetadataString: (this: any) => boolean;
} & User;
Initializing Schemas
Once you've generated your typings file, all you need to do is add the following types to your schema definitions:
user.ts before:
import mongoose from "mongoose";
const UserSchema = new Schema(...);
export const User = mongoose.model("User", UserSchema);
export default User;
user.ts after:
import mongoose from "mongoose";
import { UserDocument, UserModel } from "../interfaces/mongoose.gen.ts";
const UserSchema = new Schema(...);
export const User: UserModel = mongoose.model<UserDocument, UserModel>("User", UserSchema);
export default User;
Then you can import the typings across your application from the Mongoose module and use them for document types:
import { UserDocument } from "./interfaces/mongoose.gen.ts";
async function getUser(uid: string): UserDocument {
const user = await User.findById(uid);
return user;
}
async function editEmail(user: UserDocument, newEmail: string): UserDocument {
user.email = newEmail;
return await user.save();
}
Development