I have been looking into NestJS recently and started some sample project. This project uses MongoDB, so I also chose Typegoose to make this happen. I found a tutorial online (https://nartc.netlify.app/blogs/nestjs-typegoose/) that describes how to use some abstractions with base typegoose models.
So, my base.service.ts
now looks like this:
import { InternalServerErrorException } from '@nestjs/common';
import { DocumentType, ReturnModelType } from '@typegoose/typegoose';
import { AnyParamConstructor } from '@typegoose/typegoose/lib/types';
import { MongoError } from 'mongodb';
import { DocumentQuery, Types, Query, UpdateQuery } from 'mongoose';
import { BaseModel } from './base.model';
type QueryList<T extends BaseModel> = DocumentQuery<
Array<DocumentType<T>>,
DocumentType<T>
>;
type QueryItem<T extends BaseModel> = DocumentQuery<
DocumentType<T>,
DocumentType<T>
>;
export abstract class BaseService<T extends BaseModel> {
protected model: ReturnModelType<AnyParamConstructor<T>>;
protected constructor(model: ReturnModelType<AnyParamConstructor<T>>) {
this.model = model;
}
protected static throwMongoError(err: MongoError): void {
throw new InternalServerErrorException(err, err.errmsg);
}
protected static toObjectId(id: string): Types.ObjectId {
try {
return Types.ObjectId(id);
} catch (e) {
this.throwMongoError(e);
}
}
createModel(doc?: Partial<T>): T {
return new this.model(doc);
}
findAll(filter = {}): QueryList<T> {
return this.model.find(filter);
}
async findAllAsync(filter = {}): Promise<Array<DocumentType<T>>> {
try {
return await this.findAll(filter).exec();
} catch (e) {
BaseService.throwMongoError(e);
}
}
findOne(filter = {}): QueryItem<T> {
return this.model.findOne(filter);
}
async findOneAsync(filter = {}): Promise<DocumentType<T>> {
try {
return await this.findOne(filter).exec();
} catch (e) {
BaseService.throwMongoError(e);
}
}
findById(id: string): QueryItem<T> {
return this.model.findById(BaseService.toObjectId(id));
}
async findByIdAsync(id: string): Promise<DocumentType<T>> {
try {
return await this.findById(id).exec();
} catch (e) {
BaseService.throwMongoError(e);
}
}
async create(item: T): Promise<DocumentType<T>> {
try {
return await this.model.create(item);
} catch (e) {
BaseService.throwMongoError(e);
}
}
delete(filter = {}): QueryItem<T> {
return this.model.findOneAndDelete(filter);
}
async deleteAsync(filter = {}): Promise<DocumentType<T>> {
try {
return await this.delete(filter).exec();
} catch (e) {
BaseService.throwMongoError(e);
}
}
deleteById(id: string): QueryItem<T> {
return this.model.findByIdAndDelete(BaseService.toObjectId(id));
}
async deleteByIdAsync(id: string): Promise<DocumentType<T>> {
try {
return await this.deleteById(id).exec();
} catch (e) {
BaseService.throwMongoError(e);
}
}
update(item: T): QueryItem<T> {
return this.model.findByIdAndUpdate(BaseService.toObjectId(item.id), item, {
new: true
});
}
async updateAsync(item: T): Promise<DocumentType<T>> {
try {
return await this.update(item).exec();
} catch (e) {
BaseService.throwMongoError(e);
}
}
count(filter = {}): Query<number> {
return this.model.count(filter);
}
async countAsync(filter = {}): Promise<number> {
try {
return await this.count(filter);
} catch (e) {
BaseService.throwMongoError(e);
}
}
}
This is exact same code from the blog post mentioned above. However, I am getting some errors:
No overload matches this call.
The last overload gave the following error.
Argument of type 'T' is not assignable to parameter of type 'UpdateQuery<DocumentType<T>>'.
Type 'BaseModel' is not assignable to type 'UpdateQuery<DocumentType<T>>'.
Type 'BaseModel' is not assignable to type '_UpdateQuery<_AllowStringsForIds<LeanDocument<DocumentType<T>>>> & ReadonlyPartial<_AllowStringsForIds<LeanDocument<DocumentType<T>>>> & DotAndArrayNotation<...>'.
Type 'BaseModel' is not assignable to type 'ReadonlyPartial<_AllowStringsForIds<LeanDocument<DocumentType<T>>>>'.
Type 'T' is not assignable to type '_UpdateQuery<_AllowStringsForIds<LeanDocument<DocumentType<T>>>> & ReadonlyPartial<_AllowStringsForIds<LeanDocument<DocumentType<T>>>> & DotAndArrayNotation<...>'.
Type 'BaseModel' is not assignable to type '_UpdateQuery<_AllowStringsForIds<LeanDocument<DocumentType<T>>>> & ReadonlyPartial<_AllowStringsForIds<LeanDocument<DocumentType<T>>>> & DotAndArrayNotation<...>'.
Type 'BaseModel' is not assignable to type 'ReadonlyPartial<_AllowStringsForIds<LeanDocument<DocumentType<T>>>>'.
Type 'T' is not assignable to type 'ReadonlyPartial<_AllowStringsForIds<LeanDocument<DocumentType<T>>>>'.
Type 'BaseModel' is not assignable to type 'ReadonlyPartial<_AllowStringsForIds<LeanDocument<DocumentType<T>>>>'.
This error points to update
method, into item
parameter inside this.model.findByIdAndUpdate(BaseService.toObjectId(item.id), item, ...);
I understand that the type of item
is <T extends BaseModel>
and it works on the blog post, but why do I get an error over here?
Also, this error is also present:
Generic type 'Query<ResultType, DocType, THelpers>' requires between 2 and 3 type arguments.
on count
method, in the Query<number>
.
The one thing I do not get is why do these errors appear on my project, but not on the tutorial mentioned before?
This error seems like you have the unofficial and the official types installed OR the unofficial types with incompatible typegoose
the unofficial types are
@types/mongoose
the official types are provided since mongoose
5.10.19
(but only working since ~5.11.18
)typegoose
8.0.0-beta.x
currently only supports the official types, look here on which versions in the beta are supported [this is the beta branch]