ts-mongoose
Automatically infer TypeScript interfaces from mongoose schemas.
Installation
npm i ts-mongoose mongoose @types/mongoose
yarn add ts-mongoose mongoose @types/mongoose
The Problem
When using mongoose and Typescript, you must define schemas and interfaces. Both definitions must be maintained separately and must match each other. It can be error-prone during development and cause overhead.
ts-mongoose
is a very lightweight library that allows you to create a mongoose schema and a typescript type from a common definition.
All types as created from 1-liner functions and does not depend on decorators❗️.
For example:
Type.string()
returns {type: String, required: true}
, which is the same definition required in the original mongoose library.
Example
Before:
import { Schema, model, Model, Document } from 'mongoose';
const AddressSchema = new Schema({
city: { type: String, required: true },
country: String,
zip: String,
});
const UserSchema = new Schema({
title: { type: String, required: true },
author: { type: String, required: true },
body: { type: String, required: true },
comments: [
{
body: { type: String, required: true },
date: { type: Date, required: true },
},
],
date: { type: Date, default: Date.now, required: true },
hidden: { type: Boolean, required: true },
meta: {
votes: { type: Schema.Types.Number },
favs: { type: Schema.Types.Number },
},
m: {
type: Schema.Types.Mixed,
required: true,
},
otherId: {
type: Schema.Types.ObjectId,
required: true,
},
address: {
type: AddressSchema,
required: true,
},
});
interface UserProps extends Document {
title: string;
author: string;
body: string;
}
const User: Model<UserProps> = model('User', UserSchema);
🎉🎉🎉 After:
import { createSchema, Type, typedModel } from 'ts-mongoose';
const AddressSchema = createSchema({
city: Type.string(),
country: Type.optionalString(),
zip: Type.optionalString(),
});
const UserSchema = createSchema({
title: Type.string(),
author: Type.string(),
body: Type.string(),
comments: Type.array().of({
body: Type.string(),
date: Type.date(),
}),
date: Type.date({ default: Date.now as any }),
hidden: Type.boolean(),
meta: Type.object().of({
votes: Type.number(),
favs: Type.number(),
}),
m: Type.mixed(),
otherId: Type.objectId(),
address: Type.schema().of(AddressSchema),
});
const User = typedModel('User', UserSchema);
User.findById('123').then(user => {
if (user) {
user.
}
});
API
- Each type has two forms: required and optional
{
firstName: Type.optionalString(),
email: Type.string(),
}
- Each type accepts the same options from mongoose
{
email: Type.string({unique: true, index: true})
}
schema
, object
, array
types have a method of
where you must provide a child type
{
tags: Type.array().of(Type.string())
}
ref
is a special type for creating references
{
comments: Type.array().of(
Type.ref(Type.objectId()).to('Comment', CommentSchema)
),
}
populateTs(property: string)
use this function to populate a property and adjust the returned type automatically. Under the hood it calls only the native populate
method.
Method will be available if you import a special plugin.
import 'ts-mongoose/plugin'
User.find().populateTs('comments');
Refs
Refs and populations are supported.
Check code under example/example4.ts
.
Custom Field
If you need to specify custom fields in the model, you can add a fake annotation.
It's only required if you add virtual fields or custom methods to the model.
const UserSchema = createSchema({
title: Type.string(),
author: Type.string(),
...({} as {
generatedField: string;
customFunction: () => number;
}),
});
const User = typedModel('User', UserSchema);
Autocomplete popup:
TODO
- support types: Decimal128, Map
MIT