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

strongoose

v0.1.5

Published

Mongoose meets Typescript

Downloads

4

Readme

Strongoose

Mongoose is great and all, but it makes for a subpar experience in Typescript. You need schemas, interfaces, and plain classes just for getters or instance methods. Strongoose streamlines all of that in single classes with just a few decorators. This project was inspired and makes heavy use of the implementation of Typegoose, however builds upon its own ideas.

Simply put, you'll be defining intuitive models as these in no time:

class User extends Strongoose {
  @field({ required: true })
  firstName: string

  @field({ required: true })
  lastName: string

  @field({ required: true, unique: true })
  email: string

  @field()
  password: string

  @field({ ref: Team })
  teams: Team[]

  @field()
  settings: Settings

  get fullName() {
    return `${this.firstName} ${this.lastName}`
  }
}

class Team extends Strongoose {
  @field()
  name: string
}

class Settings {
  @field()
  receiveEmails: boolean

  @field()
  receiveNotifications: boolean
}

const UserModel = new User().getModel(User)
const TeamModel = new Team().getModel(Team)

Installation

Before installing, make sure you have Mongoose and Reflect Metadata installed, both listed as peerDependencies.

$ npm install --save strongoose

Field

The @field decorator accepts a plain object with any of the Mongoose options listed in here. As Strongoose automatically infers type information, there's no need to set a type manually. It works even for Mongoose-specific types, such as ObjectId or Decimal128.

@field()
id: mongoose.Schema.Types.ObjectId

An overly complicated field would look like:

@field({ required: true, unique: true, default: '[email protected]', index: true, lowercase: true, validate: /(.+)@(.+)/ })
email: string

Schema

The @schema decorator is an optional object of schema-wide settings, listed in here. Typically, it may look like this:

@schema({ collection: 'docs', timestamps: true, autoIndex: false })
class Document extends Strongoose {
  @field()
  title: string
}

Virtual, Methods, and Statics

Mongoose supports them either via schema methods, or by passing a plain class to Schema.loadClass. The latter is specifically problematic for Typescript, which has no idea where the field names are coming from. Strongoose makes it as easy as defining methods.

class Book extends Strongoose {
  @field()
  title: string

  @field()
  author: string

  get whole() {
    return `${this.author} - ${this.title}`
  }

  addIsbn(isbn: string) {
    return `${this.title}: ${isbn}`
  }

  static findByAuthor(author: string) {
    return this.find({ author })
  }
}

References

References are handled by passing in a model as type, which automatically builds the correct schema. Returning to the firstmost example, the code below adds a reference to an array of Team on User.teams.

class User extends Strongoose {
  @field({ ref: Team })
  teams: Team[]
}

class Team extends Strongoose {
  @field()
  name: string
}

The only ceveat is the ref option passed to @field, as it informs Strongoose that you're trying to build a reference, not a subdocument.

The above code is equivalent to these schemas in Mongoose:

const team = new mongoose.Schema({
  name: string
})

const user = new mongoose.Schema({
  teams: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Team' }]
})

Subdocuments

Subdocuments are handled as a simplified case of references. It still infers the schema from the type, but it doesn't expect a ref option and the subschema classes don't need to extend Strongoose or be initialized as a model. They're simply used to build the schema and aren't evaluated.

class User extends Strongoose {
  @field()
  settings: Settings
}

class Settings {
  @field()
  receiveEmails: boolean

  @field()
  receiveNotifications: boolean
}

That's equivalent to these schemas in Mongoose:

const settingsSchema = new mongoose.Schema({
  receiveEmails: Boolean,
  receiveNotifications: Boolean
})

const userSchema = new mongoose.Schema({
  settings: settingsSchema
})

In the case of the settings, we don't really need _ids generated for the subdocument, something Mongoose does by default. We just need a plain object.

class User extends Strongoose {
  @field({ _id: false })
  settings: Settings
}

class Settings {
  @field()
  receiveEmails: boolean

  @field()
  receiveNotifications: boolean
}

Inheritance

Class inheritance can be exploited to compose schemas using shared fields that are built into the children. All the children will receive them as if they were originally declared into them. Just be aware of the implications of such strategy, or you'll end up with model definitions that are difficult to reason about. For simple and common fields, it makes total sense though.

class Person extends Strongoose {
  @field({ required: true })
  name: string

  @field({ required: true, unique: true, index: true })
  email: string
}

class User extends Person {
  @field()
  avatar: string
}

class Friend extends Person {
  @field()
  private: boolean
}

Base classes need to extend Strongoose, even if they're not going to be used as concrete models. That way, children can inherit methods like getModel() or setModel().

Initializing Models

Before being usable as Mongoose models, Strongoose classes need to be initialized. This is done using the getModel() method on instances:

class User extends Strongoose {
  @field()
  name: string
}

const UserModel = new User().getModel(User)

The only parameter to getModel is the class itself, which helps in informing Typescript of static methods.

Roadmap

Before going for version 1.0, I plan on at least these features:

  • Support Middleware.
  • Support Plugins.
  • Support schema-wide indexes.
  • Support Enums.
  • Automated tests.
  • Real-world testing on my own projects.
  • ~~Allow using an existing connection or mongoose instance.~~