Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Last active April 1, 2024 16:16
Show Gist options
  • Save brennanMKE/ee8ea002d305d4539ef6 to your computer and use it in GitHub Desktop.
Save brennanMKE/ee8ea002d305d4539ef6 to your computer and use it in GitHub Desktop.
Example of Mongoose with TypeScript and MongoDb
import * as mongoose from 'mongoose';
export let Schema = mongoose.Schema;
export let ObjectId = mongoose.Schema.Types.ObjectId;
export let Mixed = mongoose.Schema.Types.Mixed;
export interface IHeroModel extends mongoose.Document {
name: string;
power: string;
amountPeopleSaved: number;
createdAt: Date;
modifiedAt: Date;
}
let schema = new Schema({
name: {
type: String,
required: true
},
power: {
type: String,
required: true
},
amountPeopleSaved: {
type: Number,
required: false
},
createdAt: {
type: Date,
required: false
},
modifiedAt: {
type: Date,
required: false
}
}).pre('save', function(next) {
if (this._doc) {
let doc = <IHeroModel>this._doc;
let now = new Date();
if (!doc.createdAt) {
doc.createdAt = now;
}
doc.modifiedAt = now;
}
next();
return this;
});
export let HeroSchema = mongoose.model<IHeroModel>('hero', schema, 'heroes', true);
export class HeroModel {
private _heroModel: IHeroModel;
constructor(heroModel: IHeroModel) {
this._heroModel = heroModel;
}
get name(): string {
return this._heroModel.name;
}
get power(): string {
return this._heroModel.power;
}
get amountPeopleSaved(): number {
return this._heroModel.amountPeopleSaved;
}
static createHero(name: string, power: string) : Promise.IThenable<IHeroModel> {
let p = new Promise((resolve, reject) => {
let repo = new HeroRepository();
let hero = <IHeroModel>{
name: name,
power: power,
amountPeopleSaved: 0
};
repo.create(hero, (err, res) => {
if (err) {
reject(err);
}
else {
resolve(res);
}
});
});
return p;
}
static findHero(name: string) : Promise.IThenable<IHeroModel> {
let p = new Promise((resolve, reject) => {
let repo = new HeroRepository();
repo.find({ name : name }).sort({ createdAt: -1 }).limit(1).exec((err, res) => {
if (err) {
reject(err);
}
else {
if (res.length) {
resolve(res[0]);
}
else {
resolve(null);
}
}
});
});
return p;
}
}
Object.seal(HeroModel);
export interface IRead<T> {
retrieve: (callback: (error: any, result: any) => void) => void;
findById: (id: string, callback: (error: any, result: T) => void) => void;
findOne(cond?: Object, callback?: (err: any, res: T) => void): mongoose.Query<T>;
find(cond: Object, fields: Object, options: Object, callback?: (err: any, res: T[]) => void): mongoose.Query<T[]>;
}
export interface IWrite<T> {
create: (item: T, callback: (error: any, result: any) => void) => void;
update: (_id: mongoose.Types.ObjectId, item: T, callback: (error: any, result: any) => void) => void;
delete: (_id: string, callback: (error: any, result: any) => void) => void;
}
export class RepositoryBase<T extends mongoose.Document> implements IRead<T>, IWrite<T> {
private _model: mongoose.Model<mongoose.Document>;
constructor(schemaModel: mongoose.Model<mongoose.Document>) {
this._model = schemaModel;
}
create(item: T, callback: (error: any, result: T) => void) {
this._model.create(item, callback);
}
retrieve(callback: (error: any, result: T) => void) {
this._model.find({}, callback);
}
update(_id: mongoose.Types.ObjectId, item: T, callback: (error: any, result: any) => void) {
this._model.update({ _id: _id }, item, callback);
}
delete(_id: string, callback: (error: any, result: any) => void) {
this._model.remove({ _id: this.toObjectId(_id) }, (err) => callback(err, null));
}
findById(_id: string, callback: (error: any, result: T) => void) {
this._model.findById(_id, callback);
}
findOne(cond?: Object, callback?: (err: any, res: T) => void): mongoose.Query<T> {
return this._model.findOne(cond, callback);
}
find(cond?: Object, fields?: Object, options?: Object, callback?: (err: any, res: T[]) => void): mongoose.Query<T[]> {
return this._model.find(cond, options, callback);
}
private toObjectId(_id: string): mongoose.Types.ObjectId {
return mongoose.Types.ObjectId.createFromHexString(_id);
}
}
export class HeroRepository extends RepositoryBase<IHeroModel> {
constructor() {
super(HeroSchema);
}
}
Object.seal(HeroRepository);
let uri = 'mongodb://localhost/heroes';
mongoose.connect(uri, (err) => {
if (err) {
console.log(err.message);
console.log(err);
}
else {
console.log('Connected to MongoDb');
}
});
HeroModel.createHero('Steve', 'Flying').then((res) => {
console.log('### Created Hero ###');
console.log(res);
HeroModel.findHero('Steve').then((res) => {
console.log('### Found Hero ###');
console.log(res);
// now update the Hero
let hero = <IHeroModel>res;
hero.power = 'Invisibility';
hero.save((err, res) => {
if (err) {
console.log(err.message);
console.log(err);
}
else {
console.log(res);
}
});
}, (err) => {
if (err) {
console.log(err.message);
}
});
}, (err) => {
if (err) {
console.log(err.message);
console.log(err);
}
});
@brennanMKE
Copy link
Author

@BMalaichik
Copy link

@brennanMKE nice, thanks!

@stevethibault
Copy link

Can't find IThenable Where does this type come from?

@BrightShadow
Copy link

BrightShadow commented Aug 2, 2017

@stevethibault try to use Promise<T>, instead of IThenable<T>.

@wiesson
Copy link

wiesson commented Aug 18, 2017

Also take a look at the typescript code guidelines -> https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines
e.g. Names -> Do not use "I" as a prefix for interface names.

@maxint137
Copy link

I can't see what's the intention of that code inside of pre('save', ... - where is that _doc used actually?

@capezzbr
Copy link

capezzbr commented Sep 5, 2019

Updated working version for pre:

}).pre< IHeroModel>('save', function (next) {
  if (this.isNew) {
    this.createdAt = new Date();
  } else {
    this.modifiedAt = new Date();
  }
  next();
});

@Tiziton
Copy link

Tiziton commented Dec 2, 2019

I got error like this at 165, 169 :

Type 'DocumentQuery<Document, Document, {}>' is not assignable to type 'Query'.
Types of property 'exec' are incompatible.
Type '{ (callback?: (err: NativeError, res: Document) => void): Promise; (operation: string | Function, callback?: (err: any, res: Document) => void): Promise; }' is not assignable to type '{ (callback?: (err: NativeError, res: T) => void): Promise; (operation: string | Function, callback?: (err: any, res: T) => void): Promise; }'.
Types of parameters 'callback' and 'callback' are incompatible.
Types of parameters 'res' and 'res' are incompatible.
Type 'Document' is not assignable to type 'T'.
'Document' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Document'.ts(2322)

Is anyone have resolved it?

@saadjhk
Copy link

saadjhk commented Dec 12, 2019

I have been constantly receiving the issue mentioned above, seems like a versioning problem

@ariel-frischer
Copy link

This is super useful thanks for sharing!

@Britskiy
Copy link

Britskiy commented Nov 25, 2020

I got error like this at 165, 169 :

Type 'DocumentQuery<Document, Document, {}>' is not assignable to type 'Query'.
Types of property 'exec' are incompatible.
Type '{ (callback?: (err: NativeError, res: Document) => void): Promise; (operation: string | Function, callback?: (err: any, res: Document) => void): Promise; }' is not assignable to type '{ (callback?: (err: NativeError, res: T) => void): Promise; (operation: string | Function, callback?: (err: any, res: T) => void): Promise; }'.
Types of parameters 'callback' and 'callback' are incompatible.
Types of parameters 'res' and 'res' are incompatible.
Type 'Document' is not assignable to type 'T'.
'Document' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Document'.ts(2322)

Is anyone have resolved it?

Hello. I fixed it by changing types using original *.ts file for mongoose. By opening the file node_modules/@types/mongoose/index.d.ts you can see all the required types, in the future this will help you find a solution when changing mongoose versions.

findOne(cond?: Object, callback?: (err: any, res: T) => void): mongoose.Query<T> { return this._model.findOne(cond, callback); }
To
findOne(conditions?: any, callback?: (err: any, res: mongoose.Model<T> | null) => void): mongoose.DocumentQuery<T | null, T>;

You can find more examples here: https://github.com/Britskiy/BasicAppArchitecture
I've updated the dependencies and added some functionality.

@guirip
Copy link

guirip commented Jan 8, 2022

For anyone stumbling on this gist, be aware that Mongoose team discourages to extend your interfaces from Mongoose.Document :
https://mongoosejs.com/docs/typescript.html#using-extends-document

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment