express-base-controller
Version:
a nodejs api controller
149 lines (135 loc) • 3.55 kB
text/typescript
import {
ObjectId,
} from 'bson';
import {
NextFunction,
Response,
} from 'express';
import {
Types,
} from 'mongoose';
import {
isString,
} from './helpers/isString';
import {
IApiError,
} from './types/IApiError';
import {
ApiDocument,
ApiModel,
} from './types/IApiModel';
import {
IApiRequest,
} from './types/IApiRequest';
abstract class ApiController<T extends ApiDocument> {
protected model: ApiModel<T>;
constructor(model: ApiModel<T>) {
this.model = model;
}
// ERROR Responses
public respondServerError(res: Response, error: any): Response {
return res.status(500).json({ error });
}
public respondNotFound(
id: string | number | ObjectId,
res: Response,
modelName: string,
): Response {
const error: IApiError = {
id: 'notFound',
message: `${ modelName } ${ id } does not exist`,
fields: {},
errors: [],
};
return res.status(404).json({
error,
});
}
public respondInvalidId(res: Response): Response {
const error: IApiError = {
id: 'invalidId',
message: 'Invalid id',
fields: {},
errors: [],
};
return res.status(400).json({
error,
});
}
public respondModelMissingError(res: Response): Response {
const error: IApiError = {
id: 'modelMissing',
message: 'the model is missing in the request',
fields: {},
errors: [],
};
return res.status(400).json({
error,
});
}
public respondDeletionError(res: Response, err: Error): Response {
const error: IApiError = {
id: 'delete',
message: err.message,
fields: {},
errors: [],
};
return res.status(400).json({
error,
});
}
public respondValidationError(err: any, res: Response, next: NextFunction): Response | void {
if (err.name === 'ValidationError') {
const error: IApiError = {
id: 'validationError',
message: err.message,
fields: err.errors,
errors: [],
};
return res.status(400).json({
error,
});
}
if (err.code === 11000) {
return res.status(400).json({
error: {
id: 'duplicate',
message: `${ this.model.modelName } already exists`,
},
});
} else {
return next(err);
}
}
public apiResponse(req: IApiRequest, res: Response, _next: NextFunction): Response {
const hasMetaError = req.meta && req.meta.error;
let status = 200;
if (hasMetaError) {
status = 500;
}
return res.status(status).json({ meta: req.meta, data: req.data });
}
public async populateMeta(req: IApiRequest, _res: Response, next: NextFunction): Promise<void> {
const qTotal = req.modelQuery?.total || {};
return this.model.countDocuments(qTotal)
.then((total: number) => {
const offset = req.modelQuery?.offset || 0;
const limit = req.modelQuery?.limit || 100;
req.meta = {
total: total,
count: req.data ? req.data.length : 0,
offset: isString(offset) ? parseInt(offset, 10) : offset,
limit: isString(limit) ? parseInt(limit, 10) : limit,
};
return next();
})
.catch((err: unknown) => next(err));
}
protected hasModel<K extends ApiDocument>(model: ApiDocument | null | undefined): model is K {
return typeof model !== 'undefined'
&& model !== null
&& model.constructor?.name === 'model'
&& Types.ObjectId.isValid(model._id);
}
}
export default ApiController;