crud-api-express
Version:
    ![ma
213 lines (210 loc) • 9.09 kB
JavaScript
import { Router } from 'express';
class CrudController {
constructor(model, endpoint, options = {}) {
this.model = model;
this.endpoint = endpoint;
this.router = Router();
this.routes = [];
this.configureRoutes(options);
}
configureRoutes(options) {
const { middleware = [], onSuccess = (res, method, result) => res.status(200).send(result), onError = (res, method, error) => res.status(400).send(error), methods = ['POST', 'GET', 'PUT', 'DELETE'] } = options;
const applyMiddleware = (routeHandler) => {
return [...middleware, routeHandler];
};
// Helper to register a route
const registerRoute = (method, path, params) => {
this.routes.push({ method, path, params });
};
// Create
if (methods.includes('POST')) {
const path = `/${this.endpoint}`;
this.router.post(path, applyMiddleware(async (req, res) => {
const method = 'POST';
try {
const result = await this.model.create(req.body);
if (options.relatedModel && options.relatedMethods?.includes('POST')) {
await options.relatedModel.create({ [options.relatedField]: result._id, ...req.body });
}
onSuccess(res, method, result);
}
catch (error) {
onError(res, method, error);
}
}));
registerRoute('POST', path);
}
// Read all
if (methods.includes('GET')) {
const path = `/${this.endpoint}`;
this.router.get(path, applyMiddleware(async (req, res) => {
const method = 'GET';
try {
const { filter, sort, page, limit } = req.query;
const query = filter ? JSON.parse(filter) : {};
const sortOrder = sort ? JSON.parse(sort) : {};
const pageNumber = parseInt(page, 10) || 1;
const pageSize = parseInt(limit, 10) || 10;
const skip = (pageNumber - 1) * pageSize;
let items;
if (options.relatedModel && options.relatedMethods?.includes('GET')) {
items = await this.model.aggregate([
{ $match: query },
{
$lookup: {
from: options.relatedModel.collection.name,
localField: options.relatedField,
foreignField: '_id',
as: 'relatedData'
}
},
{ $sort: sortOrder },
{ $skip: skip },
{ $limit: pageSize }
]);
}
else {
items = await this.model.find(query).sort(sortOrder).skip(skip).limit(pageSize);
}
onSuccess(res, method, items);
}
catch (error) {
onError(res, method, error);
}
}));
registerRoute('GET', path, ['filter', 'sort', 'page', 'limit']);
}
// Read one
if (methods.includes('GET')) {
const path = `/${this.endpoint}/:id`;
this.router.get(path, applyMiddleware(async (req, res) => {
const method = 'GET';
try {
let item;
if (options.relatedModel && options.relatedMethods?.includes('GET')) {
const aggregateResult = await this.model.aggregate([
{ $match: { _id: req.params.id } },
{
$lookup: {
from: options.relatedModel.collection.name,
localField: options.relatedField,
foreignField: '_id',
as: 'relatedData'
}
}
]);
item = aggregateResult[0];
}
else {
item = await this.model.findById(req.params.id);
}
if (!item) {
return res.status(404).send();
}
onSuccess(res, method, item);
}
catch (error) {
onError(res, method, error);
}
}));
registerRoute('GET', path, ['id']);
}
// Update
if (methods.includes('PUT')) {
const path = `/${this.endpoint}/:id`;
this.router.put(path, applyMiddleware(async (req, res) => {
const method = 'PUT';
try {
const item = await this.model.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!item) {
return res.status(404).send();
}
if (options.relatedModel && options.relatedMethods?.includes('PUT')) {
await options.relatedModel.updateMany({ [options.relatedField]: item._id }, req.body);
}
onSuccess(res, method, item);
}
catch (error) {
onError(res, method, error);
}
}));
registerRoute('PUT', path, ['id']);
}
// Delete multiple
if (methods.includes('DELETE')) {
const path = `/${this.endpoint}`;
this.router.delete(path, applyMiddleware(async (req, res) => {
const method = 'DELETE';
try {
const query = req.query.filter ? JSON.parse(req.query.filter) : {};
const deleteResult = await this.model.deleteMany(query);
if (deleteResult.deletedCount === 0) {
return res.status(404).send();
}
if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
await options.relatedModel.deleteMany({ [options.relatedField]: { $in: query } });
}
onSuccess(res, method, deleteResult);
}
catch (error) {
onError(res, method, error);
}
}));
registerRoute('DELETE', path, ['filter']);
}
// Delete one
if (methods.includes('DELETE')) {
const path = `/${this.endpoint}/:id`;
this.router.delete(path, applyMiddleware(async (req, res) => {
const method = 'DELETE';
try {
const item = await this.model.findByIdAndDelete(req.params.id);
if (!item) {
return res.status(404).send();
}
if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
await options.relatedModel.deleteMany({ [options.relatedField]: item._id });
}
onSuccess(res, method, item);
}
catch (error) {
onError(res, method, error);
}
}));
registerRoute('DELETE', path, ['id']);
}
// Aggregate
if (methods.includes('GET') && options.aggregatePipeline) {
const path = `/${this.endpoint}/aggregate`;
this.router.get(path, applyMiddleware(async (req, res) => {
const method = 'GET (Aggregate)';
try {
const pipeline = options.aggregatePipeline ?? [];
const results = await this.model.aggregate(pipeline);
onSuccess(res, method, results);
}
catch (error) {
onError(res, method, error);
}
}));
registerRoute('GET', path);
}
// Custom routes
if (options.customRoutes) {
options.customRoutes.forEach(route => {
const { method, path, handler } = route;
if (methods.includes(method.toUpperCase())) {
this.router[method](`/${this.endpoint}${path}`, applyMiddleware(handler));
registerRoute(method.toUpperCase(), `/${this.endpoint}${path}`);
}
});
}
}
getRouter() {
return this.router;
}
getRoutes() {
return this.routes;
}
}
export { CrudController as default };