Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
mongoose-tsgen
Advanced tools
A Typescript interface generator for Mongoose that works out of the box.
A plug-n-play Typescript interface generator for Mongoose.
Using Mongoose with Typescript requires duplicating Mongoose Schemas using Typescript interfaces (see this post by the Mongoose creator). To avoid duplication, libraries such as typegoose & ts-mongoose have sprouted up which define a custom schema syntax that is used to generate both the Mongoose Schemas and the Typescript interfaces. Unfortunately, this requires users to completely rewrite their Mongoose Schemas using an unfamiliar and less-supported syntax than Mongoose itself.
This library aims to remove these drawbacks by instead parsing your already-written Mongoose Schemas and generating associated Typescript interfaces. This removes the need to learn a whole new library and makes this library extremely simple to integrate into an existing Mongoose project.
Find your Mongoose version below and install the associated mongoose-tsgen version. Ensure to refer to each version's respective README for documentation (hyperlinked in table).
mongoose | mongoose-tsgen |
---|---|
5.11.19+ | latest |
5.11.0-5.11.18 | 7.1.3 |
<5.11.0 | 6.0.10 |
mongoose-tsgen can be installed globally or locally as a dev dependency. Refer to the table above to ensure you are using the correct version.
# install as dev dependency with npm
npm install -D mongoose-tsgen
# install for mongoose v5.10.19 (see table above for compatibility)
npm install -D mongoose-tsgen@5.10.19
# install as dev dependency with yarn
yarn add -D mongoose-tsgen
Once you've generated your typings file (see Usage), all you need to do is use the generated types in your schema definitions and throughout your project.
import mongoose from "mongoose";
const UserSchema = new Schema(...);
export const User = mongoose.model("User", UserSchema);
export default User;
import mongoose from "mongoose";
import { UserDocument, UserModel, UserQueries, UserSchema } from "../interfaces/mongoose.gen.ts";
const UserSchema: UserSchema = new Schema(...);
export const User: UserModel = mongoose.model<UserDocument, UserModel, UserQueries>("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 {
// user will be of type User
const user = await User.findById(uid);
return user;
}
async function editEmail(user: UserDocument, newEmail: string): UserDocument {
user.email = newEmail;
return await user.save();
}
Note that this practice is well documented online, I've found the following two Medium articles especially useful:
mtgen [MODEL_PATH]
Generate a Typescript file containing Mongoose Schema typings.
If you run into unknown type issues, check your Mongoose version. For Mongoose v5.11+, ensure you have removed the deprecated community typings
@types/mongoose
.
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.
-o, --output=output [default: ./src/interfaces] Path of output file to write generated typings.
If a folder path is passed, the generator will create a `mongoose.gen.ts` file
in the specified folder.
-p, --project=project [default: ./] Path of `tsconfig.json` or its root folder.
--debug Print debug information if anything isn't working
--no-format Disable formatting generated files with prettier.
Specify the directory of your Mongoose schema definitions using MODEL_PATH
. If left blank, all sub-directories will be searched for models/*.ts
(ignores index.ts
files). Files found are expected to export a Mongoose model.
See code: src/index.ts
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"
}
Any field with a ref
property will be typed as RefDocument["_id"] | RefDocument
. This allows you to use the same type whether you populate a field or not. When populating a field, you will need to use Typeguards or Type Assertion to tell Typescript that the field is populated:
// fetch user with bestFriend populated
const user = await User.findById(uid).populate("bestFriend").exec()
// typescript won't allow this, since `bestFriend` is typed as `UserDocument["_id"] | UserDocument`
console.log(user.bestFriend._id)
// instead use type assertion
const bestFriend = user.bestFriend as UserDocument;
console.log(bestFriend._id);
// or use typeguards
function isPopulated<T>(doc: T | mongoose.Types.ObjectId): doc is T {
return doc instanceof mongoose.Document;
}
if (isPopulated<UserDocument>(user.bestFriend)) {
// user.bestFriend is a UserDocument
console.log(user.bestFriend._id)
}
import mongoose, { Schema } from "mongoose";
import { UserDocument, UserModel, UserSchema, UserQueries, UserObject } from "../interfaces/mongoose.gen.ts";
// UserSchema type
const UserSchema: UserSchema = new Schema({
email: {
type: String,
required: true
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
metadata: Schema.Types.Mixed,
bestFriend: {
type: Schema.Types.ObjectId,
ref: "User"
},
friends: [
{
uid: {
type: Schema.Types.ObjectId,
ref: "User",
required: true
},
nickname: String
}
],
city: {
coordinates: {
type: [Number]
}
}
});
// NOTE: `this: UserDocument` is required for virtual properties to tell TS the type of `this` value using the "fake this" feature
// you will need to add these in after your first ever run of the CLI
UserSchema.virtual("name").get(function (this: UserDocument) {
return `${this.firstName} ${this.lastName}`;
});
UserSchema.methods = {
isMetadataString() {
return this.metadata === "string";
}
};
UserSchema.statics = {
async getFriends(friendUids: UserDocument["_id"][]): Promise<UserObject[]> {
return await this.aggregate([{ $match: { _id: { $in: friendUids } } }]);
}
};
UserSchema.query = {
populateFriends() {
return this.populate("friends.uid", "firstName lastName");
}
};
export const User = mongoose.model<UserDocument, UserModel, UserQueries>("User", UserSchema);
export default User;
# run mongoose-tsgen
npx mtgen
import mongoose from "mongoose";
export interface UserFriend {
uid: User["_id"] | User;
nickname?: string;
_id: mongoose.Types.ObjectId;
}
export type UserObject = User;
export type UserQueries = {
populateFriends: <Q extends mongoose.Query<any, UserDocument, any>>(this: Q) => Q;
}
export type UserMethods = {
isMetadataString: (this: UserDocument) => boolean;
}
export type UserStatics = {
getFriends: (this: UserModel, friendUids: UserDocument["_id"][]) => Promise<UserObject[]>;
}
export interface UserModel extends mongoose.Model<UserDocument, UserQueries>, UserStatics {}
export type UserSchema = mongoose.Schema<UserDocument, UserModel>
export interface User {
email: string;
firstName: string;
lastName: string;
bestFriend?: User["_id"] | User;
friends: UserFriend[];
city: {
coordinates: number[];
};
_id: mongoose.Types.ObjectId;
}
export interface UserFriendDocument extends mongoose.Types.EmbeddedDocument {
uid: UserDocument["_id"] | UserDocument;
nickname?: string;
_id: mongoose.Types.ObjectId;
};
export interface UserDocument extends mongoose.Document<mongoose.Types.ObjectId, UserQueries>,
UserMethods {
email: string;
firstName: string;
lastName: string;
metadata?: any;
bestFriend?: UserDocument["_id"] | UserDocument;
friends: mongoose.Types.DocumentArray<UserFriendDocument>;
city: {
coordinates: mongoose.Types.Array<number>;
};
name: string;
_id: mongoose.Types.ObjectId;
};
_id
fields as a string rather than an ObjectId on lean version of documents (see #7).8.0.0 / 2021-04-29
--js
(generator depends too much on TS compiler libraries now, so using this with JS has very limited functionality. Convert type files to TS before using mongoose-tsgen moving forward)--augment
(added much unnecessary complexity, functionality can be achieved using a simple user-created file)--no-func-types
(this was mainly used to avoid using TS compiler API when running the generator. These days the generator depends on the TS compiler API for all basic functions so this option has no use)--debug
flag to print useful debug infoFAQs
A Typescript interface generator for Mongoose that works out of the box.
The npm package mongoose-tsgen receives a total of 11,917 weekly downloads. As such, mongoose-tsgen popularity was classified as popular.
We found that mongoose-tsgen demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.