crud-api-express 1.2.5 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +82 -46
- package/dist/index.d.ts +35 -2
- package/dist/index.mjs +82 -46
- package/package.json +1 -1
- package/src/index.ts +219 -154
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
var express = require('express');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A generic CRUD Controller for Express/Mongoose API development.
|
|
7
|
+
*
|
|
8
|
+
* It registers standard CRUD endpoints based on the provided model and endpoint string,
|
|
9
|
+
* along with any custom routes.
|
|
10
|
+
*/
|
|
5
11
|
class CrudController {
|
|
6
12
|
constructor(model, endpoint, options = {}) {
|
|
7
13
|
this.model = model;
|
|
@@ -10,37 +16,58 @@ class CrudController {
|
|
|
10
16
|
this.routes = [];
|
|
11
17
|
this.configureRoutes(options);
|
|
12
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Applies middleware to the route handler.
|
|
21
|
+
* @param routeHandler The original route handler.
|
|
22
|
+
* @param middlewareList Optional array of middleware functions.
|
|
23
|
+
* @returns Array of middleware functions including the route handler.
|
|
24
|
+
*/
|
|
25
|
+
applyMiddleware(handler, middleware) {
|
|
26
|
+
return [...middleware, handler];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Registers route definitions to the internal list.
|
|
30
|
+
* @param method HTTP method.
|
|
31
|
+
* @param path URL path.
|
|
32
|
+
* @param params Optional route parameter names.
|
|
33
|
+
*/
|
|
34
|
+
registerRoute(method, path, params) {
|
|
35
|
+
this.routes.push({ method, path, params });
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Configures all endpoints based on given options.
|
|
39
|
+
* @param options CrudOptions to setup CRUD behaviors.
|
|
40
|
+
*/
|
|
13
41
|
configureRoutes(options) {
|
|
42
|
+
// Destructure and set default middleware and callbacks
|
|
14
43
|
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;
|
|
15
|
-
|
|
16
|
-
return [...middleware, routeHandler];
|
|
17
|
-
};
|
|
18
|
-
// Helper to register a route
|
|
19
|
-
const registerRoute = (method, path, params) => {
|
|
20
|
-
this.routes.push({ method, path, params });
|
|
21
|
-
};
|
|
22
|
-
// Create
|
|
44
|
+
// CREATE operation - POST /endpoint
|
|
23
45
|
if (methods.includes('POST')) {
|
|
24
46
|
const path = `/${this.endpoint}`;
|
|
25
|
-
this.router.post(path, applyMiddleware(async (req, res) => {
|
|
47
|
+
this.router.post(path, this.applyMiddleware(async (req, res) => {
|
|
26
48
|
const method = 'POST';
|
|
27
49
|
try {
|
|
28
50
|
const result = await this.model.create(req.body);
|
|
51
|
+
// If a related model is defined and supports POST, create related entry
|
|
29
52
|
if (options.relatedModel && options.relatedMethods?.includes('POST')) {
|
|
30
|
-
await options.relatedModel.create({
|
|
53
|
+
await options.relatedModel.create({
|
|
54
|
+
[options.relatedField]: result._id,
|
|
55
|
+
...req.body
|
|
56
|
+
});
|
|
31
57
|
}
|
|
32
|
-
|
|
58
|
+
// Return 201 Created status if successful
|
|
59
|
+
onSuccess(res.status(201), method, result);
|
|
33
60
|
}
|
|
34
61
|
catch (error) {
|
|
35
62
|
onError(res, method, error);
|
|
36
63
|
}
|
|
37
|
-
}));
|
|
38
|
-
registerRoute('POST', path);
|
|
64
|
+
}, middleware));
|
|
65
|
+
this.registerRoute('POST', path);
|
|
39
66
|
}
|
|
40
|
-
//
|
|
67
|
+
// READ ALL operation - GET /endpoint
|
|
41
68
|
if (methods.includes('GET')) {
|
|
42
69
|
const path = `/${this.endpoint}`;
|
|
43
|
-
this.router.get(path, applyMiddleware(async (req, res) => {
|
|
70
|
+
this.router.get(path, this.applyMiddleware(async (req, res) => {
|
|
44
71
|
const method = 'GET';
|
|
45
72
|
try {
|
|
46
73
|
const { filter, sort, page, limit } = req.query;
|
|
@@ -51,6 +78,7 @@ class CrudController {
|
|
|
51
78
|
const skip = (pageNumber - 1) * pageSize;
|
|
52
79
|
let items;
|
|
53
80
|
if (options.relatedModel && options.relatedMethods?.includes('GET')) {
|
|
81
|
+
// Use aggregation with lookup if related model exists
|
|
54
82
|
items = await this.model.aggregate([
|
|
55
83
|
{ $match: query },
|
|
56
84
|
{
|
|
@@ -74,13 +102,13 @@ class CrudController {
|
|
|
74
102
|
catch (error) {
|
|
75
103
|
onError(res, method, error);
|
|
76
104
|
}
|
|
77
|
-
}));
|
|
78
|
-
registerRoute('GET', path, ['filter', 'sort', 'page', 'limit']);
|
|
105
|
+
}, middleware));
|
|
106
|
+
this.registerRoute('GET', path, ['filter', 'sort', 'page', 'limit']);
|
|
79
107
|
}
|
|
80
|
-
//
|
|
108
|
+
// READ ONE operation - GET /endpoint/:id
|
|
81
109
|
if (methods.includes('GET')) {
|
|
82
110
|
const path = `/${this.endpoint}/:id`;
|
|
83
|
-
this.router.get(path, applyMiddleware(async (req, res) => {
|
|
111
|
+
this.router.get(path, this.applyMiddleware(async (req, res) => {
|
|
84
112
|
const method = 'GET';
|
|
85
113
|
try {
|
|
86
114
|
let item;
|
|
@@ -96,32 +124,33 @@ class CrudController {
|
|
|
96
124
|
}
|
|
97
125
|
}
|
|
98
126
|
]);
|
|
99
|
-
item = aggregateResult[0];
|
|
127
|
+
item = aggregateResult[0] || null;
|
|
100
128
|
}
|
|
101
129
|
else {
|
|
102
130
|
item = await this.model.findById(req.params.id);
|
|
103
131
|
}
|
|
104
132
|
if (!item) {
|
|
105
|
-
return res.status(404).send();
|
|
133
|
+
return res.status(404).send({ message: 'Item not found' });
|
|
106
134
|
}
|
|
107
135
|
onSuccess(res, method, item);
|
|
108
136
|
}
|
|
109
137
|
catch (error) {
|
|
110
138
|
onError(res, method, error);
|
|
111
139
|
}
|
|
112
|
-
}));
|
|
113
|
-
registerRoute('GET', path, ['id']);
|
|
140
|
+
}, middleware));
|
|
141
|
+
this.registerRoute('GET', path, ['id']);
|
|
114
142
|
}
|
|
115
|
-
//
|
|
143
|
+
// UPDATE operation - PUT /endpoint/:id
|
|
116
144
|
if (methods.includes('PUT')) {
|
|
117
145
|
const path = `/${this.endpoint}/:id`;
|
|
118
|
-
this.router.put(path, applyMiddleware(async (req, res) => {
|
|
146
|
+
this.router.put(path, this.applyMiddleware(async (req, res) => {
|
|
119
147
|
const method = 'PUT';
|
|
120
148
|
try {
|
|
121
149
|
const item = await this.model.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
|
|
122
150
|
if (!item) {
|
|
123
|
-
return res.status(404).send();
|
|
151
|
+
return res.status(404).send({ message: 'Item not found' });
|
|
124
152
|
}
|
|
153
|
+
// Update related model entries if configured
|
|
125
154
|
if (options.relatedModel && options.relatedMethods?.includes('PUT')) {
|
|
126
155
|
await options.relatedModel.updateMany({ [options.relatedField]: item._id }, req.body);
|
|
127
156
|
}
|
|
@@ -130,19 +159,19 @@ class CrudController {
|
|
|
130
159
|
catch (error) {
|
|
131
160
|
onError(res, method, error);
|
|
132
161
|
}
|
|
133
|
-
}));
|
|
134
|
-
registerRoute('PUT', path, ['id']);
|
|
162
|
+
}, middleware));
|
|
163
|
+
this.registerRoute('PUT', path, ['id']);
|
|
135
164
|
}
|
|
136
|
-
//
|
|
165
|
+
// DELETE MULTIPLE operation - DELETE /endpoint?filter=...
|
|
137
166
|
if (methods.includes('DELETE')) {
|
|
138
167
|
const path = `/${this.endpoint}`;
|
|
139
|
-
this.router.delete(path, applyMiddleware(async (req, res) => {
|
|
168
|
+
this.router.delete(path, this.applyMiddleware(async (req, res) => {
|
|
140
169
|
const method = 'DELETE';
|
|
141
170
|
try {
|
|
142
171
|
const query = req.query.filter ? JSON.parse(req.query.filter) : {};
|
|
143
172
|
const deleteResult = await this.model.deleteMany(query);
|
|
144
173
|
if (deleteResult.deletedCount === 0) {
|
|
145
|
-
return res.status(404).send();
|
|
174
|
+
return res.status(404).send({ message: 'No matching items found to delete' });
|
|
146
175
|
}
|
|
147
176
|
if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
|
|
148
177
|
await options.relatedModel.deleteMany({ [options.relatedField]: { $in: query } });
|
|
@@ -152,18 +181,18 @@ class CrudController {
|
|
|
152
181
|
catch (error) {
|
|
153
182
|
onError(res, method, error);
|
|
154
183
|
}
|
|
155
|
-
}));
|
|
156
|
-
registerRoute('DELETE', path, ['filter']);
|
|
184
|
+
}, middleware));
|
|
185
|
+
this.registerRoute('DELETE', path, ['filter']);
|
|
157
186
|
}
|
|
158
|
-
//
|
|
187
|
+
// DELETE ONE operation - DELETE /endpoint/:id
|
|
159
188
|
if (methods.includes('DELETE')) {
|
|
160
189
|
const path = `/${this.endpoint}/:id`;
|
|
161
|
-
this.router.delete(path, applyMiddleware(async (req, res) => {
|
|
190
|
+
this.router.delete(path, this.applyMiddleware(async (req, res) => {
|
|
162
191
|
const method = 'DELETE';
|
|
163
192
|
try {
|
|
164
193
|
const item = await this.model.findByIdAndDelete(req.params.id);
|
|
165
194
|
if (!item) {
|
|
166
|
-
return res.status(404).send();
|
|
195
|
+
return res.status(404).send({ message: 'Item not found' });
|
|
167
196
|
}
|
|
168
197
|
if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
|
|
169
198
|
await options.relatedModel.deleteMany({ [options.relatedField]: item._id });
|
|
@@ -173,39 +202,46 @@ class CrudController {
|
|
|
173
202
|
catch (error) {
|
|
174
203
|
onError(res, method, error);
|
|
175
204
|
}
|
|
176
|
-
}));
|
|
177
|
-
registerRoute('DELETE', path, ['id']);
|
|
205
|
+
}, middleware));
|
|
206
|
+
this.registerRoute('DELETE', path, ['id']);
|
|
178
207
|
}
|
|
179
|
-
//
|
|
208
|
+
// AGGREGATE operation - GET /endpoint/aggregate
|
|
180
209
|
if (methods.includes('GET') && options.aggregatePipeline) {
|
|
181
210
|
const path = `/${this.endpoint}/aggregate`;
|
|
182
|
-
this.router.get(path, applyMiddleware(async (req, res) => {
|
|
211
|
+
this.router.get(path, this.applyMiddleware(async (req, res) => {
|
|
183
212
|
const method = 'GET (Aggregate)';
|
|
184
213
|
try {
|
|
185
|
-
const pipeline = options.aggregatePipeline
|
|
214
|
+
const pipeline = options.aggregatePipeline || [];
|
|
186
215
|
const results = await this.model.aggregate(pipeline);
|
|
187
216
|
onSuccess(res, method, results);
|
|
188
217
|
}
|
|
189
218
|
catch (error) {
|
|
190
219
|
onError(res, method, error);
|
|
191
220
|
}
|
|
192
|
-
}));
|
|
193
|
-
registerRoute('GET', path);
|
|
221
|
+
}, middleware));
|
|
222
|
+
this.registerRoute('GET', path);
|
|
194
223
|
}
|
|
195
|
-
//
|
|
224
|
+
// CUSTOM ROUTES
|
|
196
225
|
if (options.customRoutes) {
|
|
197
226
|
options.customRoutes.forEach(route => {
|
|
198
227
|
const { method, path, handler } = route;
|
|
199
228
|
if (methods.includes(method.toUpperCase())) {
|
|
200
|
-
|
|
201
|
-
|
|
229
|
+
// Prepend base endpoint to custom route path
|
|
230
|
+
this.router[method](`/${this.endpoint}${path}`, this.applyMiddleware(handler, middleware));
|
|
231
|
+
this.registerRoute(method.toUpperCase(), `/${this.endpoint}${path}`);
|
|
202
232
|
}
|
|
203
233
|
});
|
|
204
234
|
}
|
|
205
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Returns the configured Express router.
|
|
238
|
+
*/
|
|
206
239
|
getRouter() {
|
|
207
240
|
return this.router;
|
|
208
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Returns an array of registered route definitions.
|
|
244
|
+
*/
|
|
209
245
|
getRoutes() {
|
|
210
246
|
return this.routes;
|
|
211
247
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -25,9 +25,12 @@
|
|
|
25
25
|
/// <reference types="mongoose/types/inferrawdoctype" />
|
|
26
26
|
import { Request, Response, Router, NextFunction } from 'express';
|
|
27
27
|
import { Document, Model } from 'mongoose';
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Options for configuring the CRUD controller behavior.
|
|
30
|
+
*/
|
|
31
|
+
export interface CrudOptions<T extends Document> {
|
|
29
32
|
middleware?: ((req: Request, res: Response, next: NextFunction) => void)[];
|
|
30
|
-
onSuccess?: (res: Response, method: string, result: T | T[]) => void;
|
|
33
|
+
onSuccess?: (res: Response, method: string, result: T | T[] | any) => void;
|
|
31
34
|
onError?: (res: Response, method: string, error: Error) => void;
|
|
32
35
|
methods?: ('POST' | 'GET' | 'PUT' | 'DELETE')[];
|
|
33
36
|
relatedModel?: Model<any>;
|
|
@@ -40,14 +43,44 @@ interface CrudOptions<T extends Document> {
|
|
|
40
43
|
handler: (req: Request, res: Response) => void;
|
|
41
44
|
}[];
|
|
42
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* A generic CRUD Controller for Express/Mongoose API development.
|
|
48
|
+
*
|
|
49
|
+
* It registers standard CRUD endpoints based on the provided model and endpoint string,
|
|
50
|
+
* along with any custom routes.
|
|
51
|
+
*/
|
|
43
52
|
declare class CrudController<T extends Document> {
|
|
44
53
|
private model;
|
|
45
54
|
private endpoint;
|
|
46
55
|
private router;
|
|
47
56
|
private routes;
|
|
48
57
|
constructor(model: Model<T>, endpoint: string, options?: CrudOptions<T>);
|
|
58
|
+
/**
|
|
59
|
+
* Applies middleware to the route handler.
|
|
60
|
+
* @param routeHandler The original route handler.
|
|
61
|
+
* @param middlewareList Optional array of middleware functions.
|
|
62
|
+
* @returns Array of middleware functions including the route handler.
|
|
63
|
+
*/
|
|
64
|
+
private applyMiddleware;
|
|
65
|
+
/**
|
|
66
|
+
* Registers route definitions to the internal list.
|
|
67
|
+
* @param method HTTP method.
|
|
68
|
+
* @param path URL path.
|
|
69
|
+
* @param params Optional route parameter names.
|
|
70
|
+
*/
|
|
71
|
+
private registerRoute;
|
|
72
|
+
/**
|
|
73
|
+
* Configures all endpoints based on given options.
|
|
74
|
+
* @param options CrudOptions to setup CRUD behaviors.
|
|
75
|
+
*/
|
|
49
76
|
private configureRoutes;
|
|
77
|
+
/**
|
|
78
|
+
* Returns the configured Express router.
|
|
79
|
+
*/
|
|
50
80
|
getRouter(): Router;
|
|
81
|
+
/**
|
|
82
|
+
* Returns an array of registered route definitions.
|
|
83
|
+
*/
|
|
51
84
|
getRoutes(): {
|
|
52
85
|
method: string;
|
|
53
86
|
path: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A generic CRUD Controller for Express/Mongoose API development.
|
|
5
|
+
*
|
|
6
|
+
* It registers standard CRUD endpoints based on the provided model and endpoint string,
|
|
7
|
+
* along with any custom routes.
|
|
8
|
+
*/
|
|
3
9
|
class CrudController {
|
|
4
10
|
constructor(model, endpoint, options = {}) {
|
|
5
11
|
this.model = model;
|
|
@@ -8,37 +14,58 @@ class CrudController {
|
|
|
8
14
|
this.routes = [];
|
|
9
15
|
this.configureRoutes(options);
|
|
10
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Applies middleware to the route handler.
|
|
19
|
+
* @param routeHandler The original route handler.
|
|
20
|
+
* @param middlewareList Optional array of middleware functions.
|
|
21
|
+
* @returns Array of middleware functions including the route handler.
|
|
22
|
+
*/
|
|
23
|
+
applyMiddleware(handler, middleware) {
|
|
24
|
+
return [...middleware, handler];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Registers route definitions to the internal list.
|
|
28
|
+
* @param method HTTP method.
|
|
29
|
+
* @param path URL path.
|
|
30
|
+
* @param params Optional route parameter names.
|
|
31
|
+
*/
|
|
32
|
+
registerRoute(method, path, params) {
|
|
33
|
+
this.routes.push({ method, path, params });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Configures all endpoints based on given options.
|
|
37
|
+
* @param options CrudOptions to setup CRUD behaviors.
|
|
38
|
+
*/
|
|
11
39
|
configureRoutes(options) {
|
|
40
|
+
// Destructure and set default middleware and callbacks
|
|
12
41
|
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;
|
|
13
|
-
|
|
14
|
-
return [...middleware, routeHandler];
|
|
15
|
-
};
|
|
16
|
-
// Helper to register a route
|
|
17
|
-
const registerRoute = (method, path, params) => {
|
|
18
|
-
this.routes.push({ method, path, params });
|
|
19
|
-
};
|
|
20
|
-
// Create
|
|
42
|
+
// CREATE operation - POST /endpoint
|
|
21
43
|
if (methods.includes('POST')) {
|
|
22
44
|
const path = `/${this.endpoint}`;
|
|
23
|
-
this.router.post(path, applyMiddleware(async (req, res) => {
|
|
45
|
+
this.router.post(path, this.applyMiddleware(async (req, res) => {
|
|
24
46
|
const method = 'POST';
|
|
25
47
|
try {
|
|
26
48
|
const result = await this.model.create(req.body);
|
|
49
|
+
// If a related model is defined and supports POST, create related entry
|
|
27
50
|
if (options.relatedModel && options.relatedMethods?.includes('POST')) {
|
|
28
|
-
await options.relatedModel.create({
|
|
51
|
+
await options.relatedModel.create({
|
|
52
|
+
[options.relatedField]: result._id,
|
|
53
|
+
...req.body
|
|
54
|
+
});
|
|
29
55
|
}
|
|
30
|
-
|
|
56
|
+
// Return 201 Created status if successful
|
|
57
|
+
onSuccess(res.status(201), method, result);
|
|
31
58
|
}
|
|
32
59
|
catch (error) {
|
|
33
60
|
onError(res, method, error);
|
|
34
61
|
}
|
|
35
|
-
}));
|
|
36
|
-
registerRoute('POST', path);
|
|
62
|
+
}, middleware));
|
|
63
|
+
this.registerRoute('POST', path);
|
|
37
64
|
}
|
|
38
|
-
//
|
|
65
|
+
// READ ALL operation - GET /endpoint
|
|
39
66
|
if (methods.includes('GET')) {
|
|
40
67
|
const path = `/${this.endpoint}`;
|
|
41
|
-
this.router.get(path, applyMiddleware(async (req, res) => {
|
|
68
|
+
this.router.get(path, this.applyMiddleware(async (req, res) => {
|
|
42
69
|
const method = 'GET';
|
|
43
70
|
try {
|
|
44
71
|
const { filter, sort, page, limit } = req.query;
|
|
@@ -49,6 +76,7 @@ class CrudController {
|
|
|
49
76
|
const skip = (pageNumber - 1) * pageSize;
|
|
50
77
|
let items;
|
|
51
78
|
if (options.relatedModel && options.relatedMethods?.includes('GET')) {
|
|
79
|
+
// Use aggregation with lookup if related model exists
|
|
52
80
|
items = await this.model.aggregate([
|
|
53
81
|
{ $match: query },
|
|
54
82
|
{
|
|
@@ -72,13 +100,13 @@ class CrudController {
|
|
|
72
100
|
catch (error) {
|
|
73
101
|
onError(res, method, error);
|
|
74
102
|
}
|
|
75
|
-
}));
|
|
76
|
-
registerRoute('GET', path, ['filter', 'sort', 'page', 'limit']);
|
|
103
|
+
}, middleware));
|
|
104
|
+
this.registerRoute('GET', path, ['filter', 'sort', 'page', 'limit']);
|
|
77
105
|
}
|
|
78
|
-
//
|
|
106
|
+
// READ ONE operation - GET /endpoint/:id
|
|
79
107
|
if (methods.includes('GET')) {
|
|
80
108
|
const path = `/${this.endpoint}/:id`;
|
|
81
|
-
this.router.get(path, applyMiddleware(async (req, res) => {
|
|
109
|
+
this.router.get(path, this.applyMiddleware(async (req, res) => {
|
|
82
110
|
const method = 'GET';
|
|
83
111
|
try {
|
|
84
112
|
let item;
|
|
@@ -94,32 +122,33 @@ class CrudController {
|
|
|
94
122
|
}
|
|
95
123
|
}
|
|
96
124
|
]);
|
|
97
|
-
item = aggregateResult[0];
|
|
125
|
+
item = aggregateResult[0] || null;
|
|
98
126
|
}
|
|
99
127
|
else {
|
|
100
128
|
item = await this.model.findById(req.params.id);
|
|
101
129
|
}
|
|
102
130
|
if (!item) {
|
|
103
|
-
return res.status(404).send();
|
|
131
|
+
return res.status(404).send({ message: 'Item not found' });
|
|
104
132
|
}
|
|
105
133
|
onSuccess(res, method, item);
|
|
106
134
|
}
|
|
107
135
|
catch (error) {
|
|
108
136
|
onError(res, method, error);
|
|
109
137
|
}
|
|
110
|
-
}));
|
|
111
|
-
registerRoute('GET', path, ['id']);
|
|
138
|
+
}, middleware));
|
|
139
|
+
this.registerRoute('GET', path, ['id']);
|
|
112
140
|
}
|
|
113
|
-
//
|
|
141
|
+
// UPDATE operation - PUT /endpoint/:id
|
|
114
142
|
if (methods.includes('PUT')) {
|
|
115
143
|
const path = `/${this.endpoint}/:id`;
|
|
116
|
-
this.router.put(path, applyMiddleware(async (req, res) => {
|
|
144
|
+
this.router.put(path, this.applyMiddleware(async (req, res) => {
|
|
117
145
|
const method = 'PUT';
|
|
118
146
|
try {
|
|
119
147
|
const item = await this.model.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
|
|
120
148
|
if (!item) {
|
|
121
|
-
return res.status(404).send();
|
|
149
|
+
return res.status(404).send({ message: 'Item not found' });
|
|
122
150
|
}
|
|
151
|
+
// Update related model entries if configured
|
|
123
152
|
if (options.relatedModel && options.relatedMethods?.includes('PUT')) {
|
|
124
153
|
await options.relatedModel.updateMany({ [options.relatedField]: item._id }, req.body);
|
|
125
154
|
}
|
|
@@ -128,19 +157,19 @@ class CrudController {
|
|
|
128
157
|
catch (error) {
|
|
129
158
|
onError(res, method, error);
|
|
130
159
|
}
|
|
131
|
-
}));
|
|
132
|
-
registerRoute('PUT', path, ['id']);
|
|
160
|
+
}, middleware));
|
|
161
|
+
this.registerRoute('PUT', path, ['id']);
|
|
133
162
|
}
|
|
134
|
-
//
|
|
163
|
+
// DELETE MULTIPLE operation - DELETE /endpoint?filter=...
|
|
135
164
|
if (methods.includes('DELETE')) {
|
|
136
165
|
const path = `/${this.endpoint}`;
|
|
137
|
-
this.router.delete(path, applyMiddleware(async (req, res) => {
|
|
166
|
+
this.router.delete(path, this.applyMiddleware(async (req, res) => {
|
|
138
167
|
const method = 'DELETE';
|
|
139
168
|
try {
|
|
140
169
|
const query = req.query.filter ? JSON.parse(req.query.filter) : {};
|
|
141
170
|
const deleteResult = await this.model.deleteMany(query);
|
|
142
171
|
if (deleteResult.deletedCount === 0) {
|
|
143
|
-
return res.status(404).send();
|
|
172
|
+
return res.status(404).send({ message: 'No matching items found to delete' });
|
|
144
173
|
}
|
|
145
174
|
if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
|
|
146
175
|
await options.relatedModel.deleteMany({ [options.relatedField]: { $in: query } });
|
|
@@ -150,18 +179,18 @@ class CrudController {
|
|
|
150
179
|
catch (error) {
|
|
151
180
|
onError(res, method, error);
|
|
152
181
|
}
|
|
153
|
-
}));
|
|
154
|
-
registerRoute('DELETE', path, ['filter']);
|
|
182
|
+
}, middleware));
|
|
183
|
+
this.registerRoute('DELETE', path, ['filter']);
|
|
155
184
|
}
|
|
156
|
-
//
|
|
185
|
+
// DELETE ONE operation - DELETE /endpoint/:id
|
|
157
186
|
if (methods.includes('DELETE')) {
|
|
158
187
|
const path = `/${this.endpoint}/:id`;
|
|
159
|
-
this.router.delete(path, applyMiddleware(async (req, res) => {
|
|
188
|
+
this.router.delete(path, this.applyMiddleware(async (req, res) => {
|
|
160
189
|
const method = 'DELETE';
|
|
161
190
|
try {
|
|
162
191
|
const item = await this.model.findByIdAndDelete(req.params.id);
|
|
163
192
|
if (!item) {
|
|
164
|
-
return res.status(404).send();
|
|
193
|
+
return res.status(404).send({ message: 'Item not found' });
|
|
165
194
|
}
|
|
166
195
|
if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
|
|
167
196
|
await options.relatedModel.deleteMany({ [options.relatedField]: item._id });
|
|
@@ -171,39 +200,46 @@ class CrudController {
|
|
|
171
200
|
catch (error) {
|
|
172
201
|
onError(res, method, error);
|
|
173
202
|
}
|
|
174
|
-
}));
|
|
175
|
-
registerRoute('DELETE', path, ['id']);
|
|
203
|
+
}, middleware));
|
|
204
|
+
this.registerRoute('DELETE', path, ['id']);
|
|
176
205
|
}
|
|
177
|
-
//
|
|
206
|
+
// AGGREGATE operation - GET /endpoint/aggregate
|
|
178
207
|
if (methods.includes('GET') && options.aggregatePipeline) {
|
|
179
208
|
const path = `/${this.endpoint}/aggregate`;
|
|
180
|
-
this.router.get(path, applyMiddleware(async (req, res) => {
|
|
209
|
+
this.router.get(path, this.applyMiddleware(async (req, res) => {
|
|
181
210
|
const method = 'GET (Aggregate)';
|
|
182
211
|
try {
|
|
183
|
-
const pipeline = options.aggregatePipeline
|
|
212
|
+
const pipeline = options.aggregatePipeline || [];
|
|
184
213
|
const results = await this.model.aggregate(pipeline);
|
|
185
214
|
onSuccess(res, method, results);
|
|
186
215
|
}
|
|
187
216
|
catch (error) {
|
|
188
217
|
onError(res, method, error);
|
|
189
218
|
}
|
|
190
|
-
}));
|
|
191
|
-
registerRoute('GET', path);
|
|
219
|
+
}, middleware));
|
|
220
|
+
this.registerRoute('GET', path);
|
|
192
221
|
}
|
|
193
|
-
//
|
|
222
|
+
// CUSTOM ROUTES
|
|
194
223
|
if (options.customRoutes) {
|
|
195
224
|
options.customRoutes.forEach(route => {
|
|
196
225
|
const { method, path, handler } = route;
|
|
197
226
|
if (methods.includes(method.toUpperCase())) {
|
|
198
|
-
|
|
199
|
-
|
|
227
|
+
// Prepend base endpoint to custom route path
|
|
228
|
+
this.router[method](`/${this.endpoint}${path}`, this.applyMiddleware(handler, middleware));
|
|
229
|
+
this.registerRoute(method.toUpperCase(), `/${this.endpoint}${path}`);
|
|
200
230
|
}
|
|
201
231
|
});
|
|
202
232
|
}
|
|
203
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Returns the configured Express router.
|
|
236
|
+
*/
|
|
204
237
|
getRouter() {
|
|
205
238
|
return this.router;
|
|
206
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Returns an array of registered route definitions.
|
|
242
|
+
*/
|
|
207
243
|
getRoutes() {
|
|
208
244
|
return this.routes;
|
|
209
245
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
import { Request, Response, Router, NextFunction } from 'express';
|
|
2
2
|
import { Document, Model, Aggregate } from 'mongoose';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Options for configuring the CRUD controller behavior.
|
|
6
|
+
*/
|
|
7
|
+
export interface CrudOptions<T extends Document> {
|
|
5
8
|
middleware?: ((req: Request, res: Response, next: NextFunction) => void)[];
|
|
6
|
-
onSuccess?: (res: Response, method: string, result: T | T[]) => void;
|
|
9
|
+
onSuccess?: (res: Response, method: string, result: T | T[] | any) => void;
|
|
7
10
|
onError?: (res: Response, method: string, error: Error) => void;
|
|
8
11
|
methods?: ('POST' | 'GET' | 'PUT' | 'DELETE')[];
|
|
9
12
|
relatedModel?: Model<any>;
|
|
10
13
|
relatedField?: string;
|
|
11
14
|
relatedMethods?: ('POST' | 'GET' | 'PUT' | 'DELETE')[];
|
|
12
15
|
aggregatePipeline?: object[];
|
|
13
|
-
customRoutes?: {
|
|
16
|
+
customRoutes?: {
|
|
17
|
+
method: 'post' | 'get' | 'put' | 'delete',
|
|
18
|
+
path: string,
|
|
19
|
+
handler: (req: Request, res: Response) => void
|
|
20
|
+
}[];
|
|
14
21
|
}
|
|
15
22
|
|
|
23
|
+
/**
|
|
24
|
+
* A generic CRUD Controller for Express/Mongoose API development.
|
|
25
|
+
*
|
|
26
|
+
* It registers standard CRUD endpoints based on the provided model and endpoint string,
|
|
27
|
+
* along with any custom routes.
|
|
28
|
+
*/
|
|
16
29
|
class CrudController<T extends Document> {
|
|
17
30
|
private model: Model<T>;
|
|
18
31
|
private endpoint: string;
|
|
19
32
|
private router: Router;
|
|
20
|
-
private routes: { method: string
|
|
33
|
+
private routes: { method: string; path: string; params?: string[] }[];
|
|
21
34
|
|
|
22
35
|
constructor(model: Model<T>, endpoint: string, options: CrudOptions<T> = {}) {
|
|
23
36
|
this.model = model;
|
|
@@ -27,7 +40,33 @@ class CrudController<T extends Document> {
|
|
|
27
40
|
this.configureRoutes(options);
|
|
28
41
|
}
|
|
29
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Applies middleware to the route handler.
|
|
45
|
+
* @param routeHandler The original route handler.
|
|
46
|
+
* @param middlewareList Optional array of middleware functions.
|
|
47
|
+
* @returns Array of middleware functions including the route handler.
|
|
48
|
+
*/
|
|
49
|
+
private applyMiddleware(handler: (req: Request, res: Response) => void, middleware: ((req: Request, res: Response, next: NextFunction) => void)[]) {
|
|
50
|
+
return [...middleware, handler];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Registers route definitions to the internal list.
|
|
56
|
+
* @param method HTTP method.
|
|
57
|
+
* @param path URL path.
|
|
58
|
+
* @param params Optional route parameter names.
|
|
59
|
+
*/
|
|
60
|
+
private registerRoute(method: string, path: string, params?: string[]) {
|
|
61
|
+
this.routes.push({ method, path, params });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Configures all endpoints based on given options.
|
|
66
|
+
* @param options CrudOptions to setup CRUD behaviors.
|
|
67
|
+
*/
|
|
30
68
|
private configureRoutes(options: CrudOptions<T>) {
|
|
69
|
+
// Destructure and set default middleware and callbacks
|
|
31
70
|
const {
|
|
32
71
|
middleware = [],
|
|
33
72
|
onSuccess = (res, method, result) => res.status(200).send(result),
|
|
@@ -35,204 +74,230 @@ class CrudController<T extends Document> {
|
|
|
35
74
|
methods = ['POST', 'GET', 'PUT', 'DELETE']
|
|
36
75
|
} = options;
|
|
37
76
|
|
|
38
|
-
|
|
39
|
-
return [...middleware, routeHandler];
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Helper to register a route
|
|
43
|
-
const registerRoute = (method: string, path: string, params?: string[]) => {
|
|
44
|
-
this.routes.push({ method, path, params });
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// Create
|
|
77
|
+
// CREATE operation - POST /endpoint
|
|
48
78
|
if (methods.includes('POST')) {
|
|
49
79
|
const path = `/${this.endpoint}`;
|
|
50
|
-
this.router.post(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
await
|
|
80
|
+
this.router.post(
|
|
81
|
+
path,
|
|
82
|
+
this.applyMiddleware(async (req, res) => {
|
|
83
|
+
const method = 'POST';
|
|
84
|
+
try {
|
|
85
|
+
const result: any = await this.model.create(req.body);
|
|
86
|
+
// If a related model is defined and supports POST, create related entry
|
|
87
|
+
if (options.relatedModel && options.relatedMethods?.includes('POST')) {
|
|
88
|
+
await options.relatedModel.create({
|
|
89
|
+
[options.relatedField!]: result._id,
|
|
90
|
+
...req.body
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Return 201 Created status if successful
|
|
94
|
+
onSuccess(res.status(201), method, result);
|
|
95
|
+
} catch (error: any) {
|
|
96
|
+
onError(res, method, error);
|
|
56
97
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
}));
|
|
62
|
-
registerRoute('POST', path);
|
|
98
|
+
}, middleware)
|
|
99
|
+
);
|
|
100
|
+
this.registerRoute('POST', path);
|
|
63
101
|
}
|
|
64
102
|
|
|
65
|
-
//
|
|
103
|
+
// READ ALL operation - GET /endpoint
|
|
66
104
|
if (methods.includes('GET')) {
|
|
67
105
|
const path = `/${this.endpoint}`;
|
|
68
|
-
this.router.get(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
items
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
this.router.get(
|
|
107
|
+
path,
|
|
108
|
+
this.applyMiddleware(async (req, res) => {
|
|
109
|
+
const method = 'GET';
|
|
110
|
+
try {
|
|
111
|
+
const { filter, sort, page, limit } = req.query;
|
|
112
|
+
const query = filter ? JSON.parse(filter as string) : {};
|
|
113
|
+
const sortOrder = sort ? JSON.parse(sort as string) : {};
|
|
114
|
+
const pageNumber = parseInt(page as string, 10) || 1;
|
|
115
|
+
const pageSize = parseInt(limit as string, 10) || 10;
|
|
116
|
+
const skip = (pageNumber - 1) * pageSize;
|
|
117
|
+
|
|
118
|
+
let items: T[] | Aggregate<any[]>;
|
|
119
|
+
if (options.relatedModel && options.relatedMethods?.includes('GET')) {
|
|
120
|
+
// Use aggregation with lookup if related model exists
|
|
121
|
+
items = await this.model.aggregate([
|
|
122
|
+
{ $match: query },
|
|
123
|
+
{
|
|
124
|
+
$lookup: {
|
|
125
|
+
from: options.relatedModel.collection.name,
|
|
126
|
+
localField: options.relatedField!,
|
|
127
|
+
foreignField: '_id',
|
|
128
|
+
as: 'relatedData'
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{ $sort: sortOrder },
|
|
132
|
+
{ $skip: skip },
|
|
133
|
+
{ $limit: pageSize }
|
|
134
|
+
]);
|
|
135
|
+
} else {
|
|
136
|
+
items = await this.model.find(query).sort(sortOrder).skip(skip).limit(pageSize);
|
|
137
|
+
}
|
|
138
|
+
onSuccess(res, method, items);
|
|
139
|
+
} catch (error: any) {
|
|
140
|
+
onError(res, method, error);
|
|
96
141
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
}));
|
|
102
|
-
registerRoute('GET', path, ['filter', 'sort', 'page', 'limit']);
|
|
142
|
+
}, middleware)
|
|
143
|
+
);
|
|
144
|
+
this.registerRoute('GET', path, ['filter', 'sort', 'page', 'limit']);
|
|
103
145
|
}
|
|
104
146
|
|
|
105
|
-
//
|
|
147
|
+
// READ ONE operation - GET /endpoint/:id
|
|
106
148
|
if (methods.includes('GET')) {
|
|
107
149
|
const path = `/${this.endpoint}/:id`;
|
|
108
|
-
this.router.get(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
$
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
150
|
+
this.router.get(
|
|
151
|
+
path,
|
|
152
|
+
this.applyMiddleware(async (req, res) => {
|
|
153
|
+
const method = 'GET';
|
|
154
|
+
try {
|
|
155
|
+
let item: T | null;
|
|
156
|
+
if (options.relatedModel && options.relatedMethods?.includes('GET')) {
|
|
157
|
+
const aggregateResult = await this.model.aggregate([
|
|
158
|
+
{ $match: { _id: req.params.id } },
|
|
159
|
+
{
|
|
160
|
+
$lookup: {
|
|
161
|
+
from: options.relatedModel.collection.name,
|
|
162
|
+
localField: options.relatedField!,
|
|
163
|
+
foreignField: '_id',
|
|
164
|
+
as: 'relatedData'
|
|
165
|
+
}
|
|
121
166
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
167
|
+
]);
|
|
168
|
+
item = aggregateResult[0] as T || null;
|
|
169
|
+
} else {
|
|
170
|
+
item = await this.model.findById(req.params.id);
|
|
171
|
+
}
|
|
172
|
+
if (!item) {
|
|
173
|
+
return res.status(404).send({ message: 'Item not found' });
|
|
174
|
+
}
|
|
175
|
+
onSuccess(res, method, item);
|
|
176
|
+
} catch (error: any) {
|
|
177
|
+
onError(res, method, error);
|
|
127
178
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
onSuccess(res, method, item);
|
|
132
|
-
} catch (error: any) {
|
|
133
|
-
onError(res, method, error);
|
|
134
|
-
}
|
|
135
|
-
}));
|
|
136
|
-
registerRoute('GET', path, ['id']);
|
|
179
|
+
}, middleware)
|
|
180
|
+
);
|
|
181
|
+
this.registerRoute('GET', path, ['id']);
|
|
137
182
|
}
|
|
138
183
|
|
|
139
|
-
//
|
|
184
|
+
// UPDATE operation - PUT /endpoint/:id
|
|
140
185
|
if (methods.includes('PUT')) {
|
|
141
186
|
const path = `/${this.endpoint}/:id`;
|
|
142
|
-
this.router.put(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
187
|
+
this.router.put(
|
|
188
|
+
path,
|
|
189
|
+
this.applyMiddleware(async (req, res) => {
|
|
190
|
+
const method = 'PUT';
|
|
191
|
+
try {
|
|
192
|
+
const item = await this.model.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
|
|
193
|
+
if (!item) {
|
|
194
|
+
return res.status(404).send({ message: 'Item not found' });
|
|
195
|
+
}
|
|
196
|
+
// Update related model entries if configured
|
|
197
|
+
if (options.relatedModel && options.relatedMethods?.includes('PUT')) {
|
|
198
|
+
await options.relatedModel.updateMany({ [options.relatedField!]: item._id }, req.body);
|
|
199
|
+
}
|
|
200
|
+
onSuccess(res, method, item);
|
|
201
|
+
} catch (error: any) {
|
|
202
|
+
onError(res, method, error);
|
|
151
203
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
}));
|
|
157
|
-
registerRoute('PUT', path, ['id']);
|
|
204
|
+
}, middleware)
|
|
205
|
+
);
|
|
206
|
+
this.registerRoute('PUT', path, ['id']);
|
|
158
207
|
}
|
|
159
208
|
|
|
160
|
-
//
|
|
209
|
+
// DELETE MULTIPLE operation - DELETE /endpoint?filter=...
|
|
161
210
|
if (methods.includes('DELETE')) {
|
|
162
211
|
const path = `/${this.endpoint}`;
|
|
163
|
-
this.router.delete(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
212
|
+
this.router.delete(
|
|
213
|
+
path,
|
|
214
|
+
this.applyMiddleware(async (req, res) => {
|
|
215
|
+
const method = 'DELETE';
|
|
216
|
+
try {
|
|
217
|
+
const query = req.query.filter ? JSON.parse(req.query.filter as string) : {};
|
|
218
|
+
const deleteResult: any = await this.model.deleteMany(query);
|
|
219
|
+
if (deleteResult.deletedCount === 0) {
|
|
220
|
+
return res.status(404).send({ message: 'No matching items found to delete' });
|
|
221
|
+
}
|
|
222
|
+
if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
|
|
223
|
+
await options.relatedModel.deleteMany({ [options.relatedField!]: { $in: query } });
|
|
224
|
+
}
|
|
225
|
+
onSuccess(res, method, deleteResult);
|
|
226
|
+
} catch (error: any) {
|
|
227
|
+
onError(res, method, error);
|
|
173
228
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
}));
|
|
179
|
-
registerRoute('DELETE', path, ['filter']);
|
|
229
|
+
}, middleware)
|
|
230
|
+
);
|
|
231
|
+
this.registerRoute('DELETE', path, ['filter']);
|
|
180
232
|
}
|
|
181
233
|
|
|
182
|
-
//
|
|
234
|
+
// DELETE ONE operation - DELETE /endpoint/:id
|
|
183
235
|
if (methods.includes('DELETE')) {
|
|
184
236
|
const path = `/${this.endpoint}/:id`;
|
|
185
|
-
this.router.delete(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
237
|
+
this.router.delete(
|
|
238
|
+
path,
|
|
239
|
+
this.applyMiddleware(async (req, res) => {
|
|
240
|
+
const method = 'DELETE';
|
|
241
|
+
try {
|
|
242
|
+
const item = await this.model.findByIdAndDelete(req.params.id);
|
|
243
|
+
if (!item) {
|
|
244
|
+
return res.status(404).send({ message: 'Item not found' });
|
|
245
|
+
}
|
|
246
|
+
if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
|
|
247
|
+
await options.relatedModel.deleteMany({ [options.relatedField!]: item._id });
|
|
248
|
+
}
|
|
249
|
+
onSuccess(res, method, item);
|
|
250
|
+
} catch (error: any) {
|
|
251
|
+
onError(res, method, error);
|
|
194
252
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
}));
|
|
200
|
-
registerRoute('DELETE', path, ['id']);
|
|
253
|
+
}, middleware)
|
|
254
|
+
);
|
|
255
|
+
this.registerRoute('DELETE', path, ['id']);
|
|
201
256
|
}
|
|
202
257
|
|
|
203
|
-
//
|
|
258
|
+
// AGGREGATE operation - GET /endpoint/aggregate
|
|
204
259
|
if (methods.includes('GET') && options.aggregatePipeline) {
|
|
205
260
|
const path = `/${this.endpoint}/aggregate`;
|
|
206
|
-
this.router.get(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
261
|
+
this.router.get(
|
|
262
|
+
path,
|
|
263
|
+
this.applyMiddleware(async (req, res) => {
|
|
264
|
+
const method = 'GET (Aggregate)';
|
|
265
|
+
try {
|
|
266
|
+
const pipeline: any[] = options.aggregatePipeline || [];
|
|
267
|
+
const results = await this.model.aggregate(pipeline);
|
|
268
|
+
onSuccess(res, method, results);
|
|
269
|
+
} catch (error: any) {
|
|
270
|
+
onError(res, method, error);
|
|
271
|
+
}
|
|
272
|
+
}, middleware)
|
|
273
|
+
);
|
|
274
|
+
this.registerRoute('GET', path);
|
|
217
275
|
}
|
|
218
276
|
|
|
219
|
-
//
|
|
277
|
+
// CUSTOM ROUTES
|
|
220
278
|
if (options.customRoutes) {
|
|
221
279
|
options.customRoutes.forEach(route => {
|
|
222
280
|
const { method, path, handler } = route;
|
|
223
281
|
if (methods.includes(method.toUpperCase() as 'POST' | 'GET' | 'PUT' | 'DELETE')) {
|
|
224
|
-
|
|
225
|
-
|
|
282
|
+
// Prepend base endpoint to custom route path
|
|
283
|
+
this.router[method](`/${this.endpoint}${path}`, this.applyMiddleware(handler, middleware));
|
|
284
|
+
this.registerRoute(method.toUpperCase(), `/${this.endpoint}${path}`);
|
|
226
285
|
}
|
|
227
286
|
});
|
|
228
287
|
}
|
|
229
288
|
}
|
|
230
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Returns the configured Express router.
|
|
292
|
+
*/
|
|
231
293
|
public getRouter(): Router {
|
|
232
294
|
return this.router;
|
|
233
295
|
}
|
|
234
296
|
|
|
235
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Returns an array of registered route definitions.
|
|
299
|
+
*/
|
|
300
|
+
public getRoutes(): { method: string; path: string; params?: string[] }[] {
|
|
236
301
|
return this.routes;
|
|
237
302
|
}
|
|
238
303
|
}
|