npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mongoose-tsgen

v9.4.4

Published

A Typescript interface generator for Mongoose that works out of the box.

Downloads

43,447

Readme

mongoose-tsgen

A plug-n-play Typescript generator for Mongoose.

Version npm License

Motivation

Using Mongoose with Typescript requires duplicating Mongoose Schemas using Typescript interfaces. To avoid duplication, libraries like typegoose 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 into an unfamiliar syntax and does not support the entire Mongoose feature set.

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 new syntax and makes this library extremely simple to integrate into an existing Mongoose project.

Features

  • 😌 Automatically generate Typescript typings for each Mongoose document, model and subdocument
  • 📦 Works out of the box, don't need to rewrite your schemas
  • ⛑ Type-safe population
  • ➕ Includes a "Mongoose-less" version of each schema interface (Mongoose typings removed)

Compatibility

  • ✅ All Mongoose types, arrays and maps
  • ✅ Virtual properties
  • ✅ Mongoose method, static & query functions
  • ✅ Multiple schemas per file
  • ✅ Typescript path aliases

Mongoose version

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 | | -------------- | -------------- | | 6.1.5+ | latest | | 5.11.19-6.1.14 | 8.4.7 | | 5.11.0-5.11.18 | 7.1.3 | | <5.11.0 | 6.0.10 |

Note: For Mongoose v6.3.2 - v6.4.0, see Known Issues first.

Installation

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 with npm or yarn
npm install -D mongoose-tsgen

# install mongoose-tsgen v7.1.3 for mongoose v5.10.19 (see table above for compatibility)
npm install -D [email protected]

# install with yarn
yarn add -D mongoose-tsgen

The Gist

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.

user.ts before:

import mongoose from "mongoose";

const UserSchema = new Schema(...);

export const User = mongoose.model("User", UserSchema);

user.ts after:

import mongoose from "mongoose";
import { UserDocument, UserModel, UserSchema } from "../interfaces/mongoose.gen.ts";

const UserSchema: UserSchema = new Schema(...);

export const User: UserModel = mongoose.model<UserDocument, UserModel>("User", UserSchema);

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:

Usage

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=imports   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.

  --dates-as-strings      Dates will be typed as strings. Useful for types returned to a frontend by API requests.

  --debug                 Print debug information if anything isn't working

  --no-format             Disable formatting generated files with prettier.

  --no-mongoose           Don't generate types that reference mongoose (i.e. documents). Replace ObjectId with
                          string.

  --no-populate-overload  Disable augmenting mongoose with Query.populate overloads (the overloads narrow
                          the return type of populated documents queries).

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

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"
}

Use as a module

mongoose-tsgen can also be imported or required and used programmatically. Below is an example:

import MongooseTsgen from "mongoose-tsgen";

const tsgen = new MongooseTsgen([]);
const result = await tsgen.generateDefinitions({
  flags: {
    "dry-run": false,
    "no-format": false,
    "no-mongoose": false,
    "no-populate-overload": false,
    "dates-as-strings": false,
    debug: false,
    output: "./src/interfaces",
    project: "./"
  },
  args: {
    model_path: "./src/models" // optional
  }
});
await result.sourceFile.save();

Note that this will not load the config file.

Query Population

Any field with a ref property will be typed as RefDocument["_id"] | RefDocument. As part of the generated file, mongoose will be augmented with Query.populate overloads to narrow return types of populated queries (this can be disabled using the --no-populate-overload flag). A helper type PopulatedDocument and a type guard function IsPopulated will also be generated to help with handling populated documents, see usage below:

import { IsPopulated, PopulatedDocument } from "../interfaces/mongoose.gen.ts";

// UserDocument["bestFriend"] = mongoose.Types.ObjectId | UserDocument
function unsafeType(user: UserDocument) {
  // type guard
  if (IsPopulated(user.bestFriend))) {
    // user.bestFriend is confirmed to be populated, typescript will allow accessing its properties now
    console.log(user.bestFriend._id)
  }
}

// `user` is typed as a UserDocument with `bestFriend` populated
function safeType(user: PopulatedDocument<UserDocument, "bestFriend">) {
  console.log(user.bestFriend._id)
}

// due to the `Query.populate` overload, `user` will be typed as `PopulatedDocument<UserDocument, "bestFriend">`
// rather than the usual `UserDocument`
const user = await User.findById(uid).populate("bestFriend").exec()

// completely type-safe
safeType(user)

Both the mongoose populate overload and the PopulateDocument type handle nested and array types with ease; you rarely need to worry about enforcing types manually. In the case that the populated type cannot be determined, types will fallback to the generic RefDocument["_id"] | RefDocument.

Example

./src/models/user.ts

import mongoose, { Schema } from "mongoose";
import { UserDocument, UserModel, UserSchema, 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("bestFriend", "firstName lastName");
  }
};

export const User = mongoose.model<UserDocument, UserModel>("User", UserSchema);

generate typings

# run mongoose-tsgen
npx mtgen

generated typings file ./src/interfaces/mongoose.gen.ts

import mongoose from "mongoose";

export type UserFriend = {
  uid: User["_id"] | User;
  nickname?: string;
  _id: mongoose.Types.ObjectId;
}

export type UserObject = User;

export type UserQueries = {
  populateFriends: () => mongoose.Query<any, UserDocument, UserQueries> & UserQueries;
}

export type UserMethods = {
  isMetadataString: (this: UserDocument) => boolean;
}

export type UserStatics = {
  getFriends: (this: UserModel, friendUids: UserDocument["_id"][]) => Promise<UserObject[]>;
}

export type UserModel = mongoose.Model<UserDocument, UserQueries> & UserStatics

export type UserSchema = mongoose.Schema<UserDocument, UserModel, UserMethods, UserQueries>

export type User = {
  email: string;
  firstName: string;
  lastName: string;
  bestFriend?: User["_id"] | User;
  friends: UserFriend[];
  city: {
    coordinates: number[];
  };
  _id: mongoose.Types.ObjectId;
}

export type UserFriendDocument = mongoose.Types.Subdocument & {
  uid: UserDocument["_id"] | UserDocument;
  nickname?: string;
  _id: mongoose.Types.ObjectId;
};

export type UserDocument = 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;
  };

Known Issues

Type instantiation is excessively deep and possibly infinite

This issue is present when using Mongoose v6.3.2 - v6.4.0 due to a conflict in types.

Workarounds:

  • Fix your Mongoose version to <6.3.2 or >6.4.0.
  • Use the --no-populate-overload flag.

References:

  • https://github.com/Automattic/mongoose/issues/11787
  • https://github.com/francescov1/mongoose-tsgen/issues/95

Development

  • [ ] Consider population field selection when typing populates
  • [ ] Slim down dependencies: oclif is unnecessarily large, prettier should be handled by users if desired.