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