ts-mongo-orm
is a MongoDB ORM inspired by ActiveRecord.
It's still under active development as I use it to build some apps, so until you see the 1.0.0
release, expect things
to change or be missing.
In its current state, it has a fairly complete type-safe wrapper around MongoDB, and I'm already using it productively in deployed apps. See the "Future Work" section below for what's on the roadmap.
npm install --save ts-mongo-orm
Note that the following peer dependencies are required:
@hapi/joi
and@types/hapi__joi
for validation types.mongodb
and@types/mongodb
for connecting to and using MongoDB.
See the ts-mongo-orm-example repo for an example program you can clone and run.
Below are some selected examples.
Connect to the database:
import {DatabaseConnectDefault} from "ts-mongo-orm";
(async () => {
await DatabaseConnectDefault("mongodb://localhost:27017/test");
})();
Create define a User
ActiveRecord
:
import {ActiveRecord, Document, Field, ObjectIdField} from "ts-mongo-orm";
import {ObjectId} from "mongodb";
import * as Joi from "@hapi/joi";
@Document
class User {
static databaseName = "test";
static collectionName = "users";
@ObjectIdField()
_id: ObjectId;
@Field(Joi.string())
email: string;
@Field(Joi.string())
name: string;
@Field(Joi.string())
passwordHash: string;
@Field(Joi.string())
passwordSalt: string;
@Field(Joi.date())
createdAt: Date;
constructor() {
this._id = new ObjectId("000000000000000000000000");
this.email = "";
this.name = "";
this.passwordHash = "";
this.passwordSalt = "";
this.createdAt = new Date();
}
}
export const UserActiveRecord = ActiveRecord.for(User, User);
Note that the ActiveRecord
class has a separation between the document class (which describes purely the data
that will be stored in the database the and validations for that data), and the model class (which could define
additional logic, helpers, computed fields, etc.)
In the above example, there is no additional model logic, so the User
class is passed for both arguments to
ActiveRecord.for
. Defining additional model logic would might look like this:
class UserModel extends User {
// This will appear on the active record object but will not get saved to the database:
get nameAndEmail() {
return this.name + " " + this.email;
}
}
export const UserActiveRecord = ActiveRecord.for(User, UserModel);
Using the UserActiveRecord
might look like this:
import {UserActiveRecord} from "./user";
export async function userFindAndLog(email: string) {
// This query is fully type-checked:
let user = await UserActiveRecord.findOne({email: email});
if (user) {
console.log(user.nameAndEmail);
}
}
export async function userDeleteIfFound(email: string) {
// This query is fully type-checked:
let user = await UserActiveRecord.findOne({email: email});
if (user) {
console.log("Deleting " + user._id + "...");
// Since user is an ActiveRecord instance, we can simply call delete()
// on it to remove it from the DB:
await user.delete();
}
}
- Be able to use the
ActiveRecord
pattern to find and update documents, similar to Ruby on Rails. - Make it easy to validate objects going into and coming out of the database.
- Make it easy to define those validations by using decorators to annotate the necessary validations alongside the type definitions.
- Provide a typed version of most (or all if possible) MongoDB APIs, specific to each type of document. For example,
collection.update({}, {$set: {a: 1}})
should fail to typecheck ifa
is not anumber
or does not exist on the document type. - Minimal overhead. Getting the type-safety guarantees and validation should not come at a huge cost of speed, but a small cost is expected.
- Optional:
- If you need to bail out of the typings for whatever reason, that should be easy.
- If you need to not use the
ActiveRecord
pattern, but instead update or query the database directly, that should be easy.
This package will not do these things:
- Have zero-overhead abstractions. Validation and the ActiveRecord pattern incur some cost by their very nature.
- Provide a type-safe way of dealing with aggregations. Too much pain for too little gain.
- Have exhaustive support for various versions of the
mongodb
library. Pull requests that add support for older versions are welcome, but I will focus only on the4.x
branch of themongodb
library. - Support for validation libraries other than
joi
. Pull requests making this optional are welcome. - Create typings for an existing DB automatically.
It's not that any of these things are bad, just that they are either outside the scope of this package, or require more time than I have.
If you see an issue or a way to improve ts-mongo-orm
feel free to open an issue or a pull request!
Please include as much as possible, and keep in mind that issues with associated pull requests are much more likely to get merged, as my time to manage issues on this project is limited.
- Updating to the v4.x driver broke some things. Might be worth rethinking how the superstructure of this module works so it's not as fragile to the whims of MongoDB. I'd rather expose the MongoDB API directly somehow, and just transparently provide my niceties on top.
- A mechanism for doing type-safe updates and queries on nested fields (e.g. a type safe
{$set: {"a.b.c": 123}}
) - Lifecycle hooks (e.g. OnLoad, AfterInit, BeforeUpdate, BeforeInsert, etc.)
- Easy optional mechanism for running validations when reading from the database.
The license is MIT, see the LICENSE
file for more details.