UNPKG

crud-api-express

Version:

![npm](https://img.shields.io/npm/v/crud-api-express) ![downloads](https://img.shields.io/npm/dm/crud-api-express) ![license](https://img.shields.io/npm/l/crud-api-express) ![made-with-node](https://img.shields.io/badge/Made%20with-Node.js-green) ![ma

213 lines (210 loc) 9.09 kB
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 };