All Projects → francescov1 → Mongoose Tsgen

francescov1 / Mongoose Tsgen

A plug-n-play Typescript interface generator for Mongoose.

Programming Languages

typescript
32286 projects

Labels

Projects that are alternatives of or similar to Mongoose Tsgen

Mevn Cli
Light speed setup for MEVN(Mongo Express Vue Node) Apps
Stars: ✭ 696 (+1518.6%)
Mutual labels:  mongoose
Summary
个人总结 持续更新 欢迎提出各种issues
Stars: ✭ 12 (-72.09%)
Mutual labels:  mongoose
Cmms
Computerized Maintenance Management System
Stars: ✭ 31 (-27.91%)
Mutual labels:  mongoose
Pwned
Simple C++ code for simple tasks
Stars: ✭ 16 (-62.79%)
Mutual labels:  mongoose
Node Auth
基于 Node Express Mongoose 实现的用户注册/登陆权限验证
Stars: ✭ 10 (-76.74%)
Mutual labels:  mongoose
Timeline Vue
💌基于 Vue -> Koa2 -> Mongoose 的留言时间轴,记录美好时光。
Stars: ✭ 14 (-67.44%)
Mutual labels:  mongoose
Node Express Mongodb Jwt Rest Api Skeleton
This is a basic API REST skeleton written on JavaScript using async/await. Great for building a starter web API for your front-end (Android, iOS, Vue, react, angular, or anything that can consume an API). Demo of frontend in VueJS here: https://github.com/davellanedam/vue-skeleton-mvp
Stars: ✭ 603 (+1302.33%)
Mutual labels:  mongoose
Friend.ly
A social media platform with a friend recommendation engine based on personality trait extraction
Stars: ✭ 41 (-4.65%)
Mutual labels:  mongoose
Mongoose Model Decorators
ES2016 decorator functions for building Mongoose models
Stars: ✭ 11 (-74.42%)
Mutual labels:  mongoose
Checksheet Manager
Checksheet Manager for college checksheets. Created with AngularJS and Node/Express/MongoDB.
Stars: ✭ 31 (-27.91%)
Mutual labels:  mongoose
Node Express Boilerplate
A boilerplate for building production-ready RESTful APIs using Node.js, Express, and Mongoose
Stars: ✭ 890 (+1969.77%)
Mutual labels:  mongoose
Express Boilerplate
🚀 Starter project for a RESTful API in Node with Express & mongoose component-based
Stars: ✭ 9 (-79.07%)
Mutual labels:  mongoose
Essay
A blog system based on Nuxt.js
Stars: ✭ 913 (+2023.26%)
Mutual labels:  mongoose
Nodepress
😎 RESTful API service for Blog/CMS, powered by @nestjs
Stars: ✭ 829 (+1827.91%)
Mutual labels:  mongoose
Littlewin.server
A blog backend server based on koa + mongoose.
Stars: ✭ 32 (-25.58%)
Mutual labels:  mongoose
Kov Blog
A blog platform built with koa,vue and mongoose. 使用 koa ,vue 和 mongo 搭建的博客页面和支持markdown语法的博客编写平台,自动保存草稿。博客地址:https://chuckliu.me
Stars: ✭ 635 (+1376.74%)
Mutual labels:  mongoose
Tvrboreact
Dream starter project: React, Redux, React Router, Webpack
Stars: ✭ 13 (-69.77%)
Mutual labels:  mongoose
Mean Angular4 Chat App
MEAN stack with Angular 4 Chat App
Stars: ✭ 41 (-4.65%)
Mutual labels:  mongoose
Es6 Express Mongoose Passport Rest Api
Lightweight boilerplate for Node RESTful API, ES6, Express, Mongoose and Passport 🎁
Stars: ✭ 36 (-16.28%)
Mutual labels:  mongoose
Jwt Node Vue
Repositório responsável pelo primeiro projeto da série de vídeos: Coding Stuff.
Stars: ✭ 29 (-32.56%)
Mutual labels:  mongoose

mongoose-tsgen

A plug-n-play Typescript interface generator for Mongoose.

Version npm License

Motivation

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.

Features

  • [x] Automatically generate Typescript typings for each Mongoose document, model and subdocument
  • [x] Works out of the box, don't need to rewrite your schemas
  • [x] Includes a "Mongoose-less" version of each schema interface (Mongoose typings removed)

Compatibility

Mongoose: v5.11+

For previous Mongoose versions, install mongoose-tsgen v6.0.10 with npm install [email protected] and see its README for instructions.

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

Installation

$ npm install -D mongoose-tsgen
$ npx mtgen --help # print usage

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. Note that this practice is well documented online, I've found the following two Medium articles especially useful:

If you run into unknown type issues, ensure you've updated Mongoose to v5.11+ and have removed the deprecated community typings @types/mongoose.

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, UserSchema } from "../interfaces/mongoose.gen.ts";

const UserSchema: 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 {
  // 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();
}

Usage

mtgen [MODEL_PATH]

Generate a Typescript file containing Mongoose Schema typings.

Note that these docs refer to Typescript files only. If you haven't yet converted Mongoose schema definition files to Typescript, you can use the --js flag to still generate types.

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 Javascript schema files rather than Typescript files. 
                         Passing this flag also triggers --no-func-types.

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

  --augment              Augment generated typings into the 'mongoose' module.

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

  --no-func-types        Disable using TS compiler API for method, static, query & virtual typings.

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

Query Population

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

Example

./src/models/user.ts

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

const { Schema } = mongoose;

// 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}`;
});

// method functions, use Type Assertion (cast to UserMethods) for type safety
UserSchema.methods = <UserMethods>{
  // the return type (boolean) will be inferred from the TS compiler here 
  isMetadataString() {
    return typeof this.metadata === "string";
  }
};

// static functions, use Type Assertion (cast to UserStatics) for type safety
UserSchema.statics = <UserStatics>{
  async getFriends(friendUids: UserDocument["_id"][]): Promise<UserObject[]> {
    return await this.aggregate([{ $match: { _id: { $in: friendUids } } }]);
  }
};

// query functions, use Type Assertion (cast to UserQueries) for type safety
UserSchema.query = <UserQueries>{
  populateFriends() {
    return this.populate("friends.uid", "firstName lastName");
  }
};

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

generate typings

$ mtgen

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

/* tslint:disable */
/* eslint-disable */

// ######################################## THIS FILE WAS GENERATED BY MONGOOSE-TSGEN ######################################## //

// NOTE: ANY CHANGES MADE WILL BE OVERWRITTEN ON SUBSEQUENT EXECUTIONS OF MONGOOSE-TSGEN.

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>>(this: Q) => Q;
}

declare module "mongoose" {
  interface Query<ResultType, DocType extends Document> extends UserQueries {}
}

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>, 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>,
  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;
  };

Development

  • [ ] In progress: The generating piece of src/helpers/parser.ts needs to be rewritten using ts-morph. Currently it builds the interfaces by appending generated lines of code to a string sequentially, with no knowledge of the AST. This leads to pretty confusing logic, using the TS compiler API would simplify it a ton.
  • [ ] Stronger populate typing by augmenting Mongoose types with more accurate return types (see Query Population).
  • [ ] Add CLI option to type _id fields as a string rather than an ObjectId on lean version of documents (see #7).
  • [ ] Cut down node_modules by using peer dependencies (i.e. mongoose) and stripping oclif.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].