crud-api-express 1.0.0

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.
@@ -0,0 +1,51 @@
1
+ /// <reference types="mongoose/types/aggregate" />
2
+ /// <reference types="mongoose/types/callback" />
3
+ /// <reference types="mongoose/types/collection" />
4
+ /// <reference types="mongoose/types/connection" />
5
+ /// <reference types="mongoose/types/cursor" />
6
+ /// <reference types="mongoose/types/document" />
7
+ /// <reference types="mongoose/types/error" />
8
+ /// <reference types="mongoose/types/expressions" />
9
+ /// <reference types="mongoose/types/helpers" />
10
+ /// <reference types="mongoose/types/middlewares" />
11
+ /// <reference types="mongoose/types/indexes" />
12
+ /// <reference types="mongoose/types/models" />
13
+ /// <reference types="mongoose/types/mongooseoptions" />
14
+ /// <reference types="mongoose/types/pipelinestage" />
15
+ /// <reference types="mongoose/types/populate" />
16
+ /// <reference types="mongoose/types/query" />
17
+ /// <reference types="mongoose/types/schemaoptions" />
18
+ /// <reference types="mongoose/types/schematypes" />
19
+ /// <reference types="mongoose/types/session" />
20
+ /// <reference types="mongoose/types/types" />
21
+ /// <reference types="mongoose/types/utility" />
22
+ /// <reference types="mongoose/types/validation" />
23
+ /// <reference types="mongoose/types/virtuals" />
24
+ /// <reference types="mongoose/types/inferschematype" />
25
+ /// <reference types="mongoose/types/inferrawdoctype" />
26
+ import { Request, Response, Router, NextFunction } from 'express';
27
+ import { Document, Model } from 'mongoose';
28
+ interface CrudOptions<T extends Document> {
29
+ middleware?: ((req: Request, res: Response, next: NextFunction) => void)[];
30
+ onSuccess?: (res: Response, method: string, result: T | T[]) => void;
31
+ onError?: (res: Response, method: string, error: Error) => void;
32
+ methods?: ('POST' | 'GET' | 'PUT' | 'DELETE')[];
33
+ relatedModel?: Model<any>;
34
+ relatedField?: string;
35
+ relatedMethods?: ('POST' | 'GET' | 'PUT' | 'DELETE')[];
36
+ aggregatePipeline?: object[];
37
+ customRoutes?: {
38
+ method: 'post' | 'get' | 'put' | 'delete';
39
+ path: string;
40
+ handler: (req: Request, res: Response) => void;
41
+ }[];
42
+ }
43
+ declare class CrudController<T extends Document> {
44
+ private model;
45
+ private endpoint;
46
+ private router;
47
+ constructor(model: Model<T>, endpoint: string, options?: CrudOptions<T>);
48
+ private configureRoutes;
49
+ getRouter(): Router;
50
+ }
51
+ export default CrudController;
package/dist/index.js ADDED
@@ -0,0 +1,183 @@
1
+ import { Router } from 'express';
2
+
3
+ class CrudController {
4
+ constructor(model, endpoint, options = {}) {
5
+ this.model = model;
6
+ this.endpoint = endpoint;
7
+ this.router = Router();
8
+ this.configureRoutes(options);
9
+ }
10
+ configureRoutes(options) {
11
+ 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;
12
+ const applyMiddleware = (routeHandler) => {
13
+ return [...middleware, routeHandler];
14
+ };
15
+ // Create
16
+ if (methods.includes('POST')) {
17
+ this.router.post(`/${this.endpoint}`, applyMiddleware(async (req, res) => {
18
+ const method = 'POST';
19
+ try {
20
+ const item = new this.model(req.body);
21
+ const result = await item.save();
22
+ if (options.relatedModel && options.relatedMethods?.includes('POST')) {
23
+ await options.relatedModel.create({ [options.relatedField]: result._id, ...req.body });
24
+ }
25
+ onSuccess(res, method, result);
26
+ }
27
+ catch (error) {
28
+ onError(res, method, error);
29
+ }
30
+ }));
31
+ }
32
+ if (methods.includes('GET')) {
33
+ this.router.get(`/${this.endpoint}`, applyMiddleware(async (req, res) => {
34
+ const method = 'GET';
35
+ try {
36
+ const { filter, sort, page, limit } = req.query;
37
+ const query = filter ? JSON.parse(filter) : {};
38
+ const sortOrder = sort ? JSON.parse(sort) : {};
39
+ const pageNumber = parseInt(page, 10) || 1;
40
+ const pageSize = parseInt(limit, 10) || 10;
41
+ const skip = (pageNumber - 1) * pageSize;
42
+ let items;
43
+ if (options.relatedModel && options.relatedMethods?.includes('GET')) {
44
+ items = await this.model.aggregate([
45
+ { $match: query },
46
+ {
47
+ $lookup: {
48
+ from: options.relatedModel.collection.name,
49
+ localField: options.relatedField,
50
+ foreignField: '_id',
51
+ as: 'relatedData'
52
+ }
53
+ },
54
+ { $sort: sortOrder },
55
+ { $skip: skip },
56
+ { $limit: pageSize }
57
+ ]);
58
+ }
59
+ else {
60
+ items = await this.model.find(query).sort(sortOrder).skip(skip).limit(pageSize);
61
+ }
62
+ onSuccess(res, method, items);
63
+ }
64
+ catch (error) {
65
+ onError(res, method, error);
66
+ }
67
+ }));
68
+ }
69
+ if (methods.includes('GET')) {
70
+ this.router.get(`/${this.endpoint}/:id`, applyMiddleware(async (req, res) => {
71
+ const method = 'GET';
72
+ try {
73
+ let item;
74
+ if (options.relatedModel && options.relatedMethods?.includes('GET')) {
75
+ const aggregateResult = await this.model.aggregate([
76
+ { $match: { _id: req.params.id } },
77
+ {
78
+ $lookup: {
79
+ from: options.relatedModel.collection.name,
80
+ localField: options.relatedField,
81
+ foreignField: '_id',
82
+ as: 'relatedData'
83
+ }
84
+ }
85
+ ]);
86
+ item = aggregateResult[0];
87
+ }
88
+ else {
89
+ item = await this.model.findById(req.params.id);
90
+ }
91
+ if (!item) {
92
+ return res.status(404).send();
93
+ }
94
+ onSuccess(res, method, item);
95
+ }
96
+ catch (error) {
97
+ onError(res, method, error);
98
+ }
99
+ }));
100
+ }
101
+ if (methods.includes('PUT')) {
102
+ this.router.put(`/${this.endpoint}/:id`, applyMiddleware(async (req, res) => {
103
+ const method = 'PUT';
104
+ try {
105
+ const item = await this.model.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
106
+ if (!item) {
107
+ return res.status(404).send();
108
+ }
109
+ if (options.relatedModel && options.relatedMethods?.includes('PUT')) {
110
+ await options.relatedModel.updateMany({ [options.relatedField]: item._id }, req.body);
111
+ }
112
+ onSuccess(res, method, item);
113
+ }
114
+ catch (error) {
115
+ onError(res, method, error);
116
+ }
117
+ }));
118
+ }
119
+ if (methods.includes('DELETE')) {
120
+ this.router.delete(`/${this.endpoint}`, applyMiddleware(async (req, res) => {
121
+ const method = 'DELETE';
122
+ try {
123
+ const query = req.query.filter ? JSON.parse(req.query.filter) : {};
124
+ const deleteResult = await this.model.deleteMany(query);
125
+ if (deleteResult.deletedCount === 0) {
126
+ return res.status(404).send();
127
+ }
128
+ if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
129
+ await options.relatedModel.deleteMany({ [options.relatedField]: { $in: query } });
130
+ }
131
+ onSuccess(res, method, deleteResult);
132
+ }
133
+ catch (error) {
134
+ onError(res, method, error);
135
+ }
136
+ }));
137
+ }
138
+ if (methods.includes('DELETE')) {
139
+ this.router.delete(`/${this.endpoint}/:id`, applyMiddleware(async (req, res) => {
140
+ const method = 'DELETE';
141
+ try {
142
+ const item = await this.model.findByIdAndDelete(req.params.id);
143
+ if (!item) {
144
+ return res.status(404).send();
145
+ }
146
+ if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
147
+ await options.relatedModel.deleteMany({ [options.relatedField]: item._id });
148
+ }
149
+ onSuccess(res, method, item);
150
+ }
151
+ catch (error) {
152
+ onError(res, method, error);
153
+ }
154
+ }));
155
+ }
156
+ if (methods.includes('GET') && options.aggregatePipeline) {
157
+ this.router.get(`/${this.endpoint}/aggregate`, applyMiddleware(async (req, res) => {
158
+ const method = 'GET (Aggregate)';
159
+ try {
160
+ const pipeline = options.aggregatePipeline ?? [];
161
+ const results = await this.model.aggregate(pipeline);
162
+ onSuccess(res, method, results);
163
+ }
164
+ catch (error) {
165
+ onError(res, method, error);
166
+ }
167
+ }));
168
+ }
169
+ if (options.customRoutes) {
170
+ options.customRoutes.forEach(route => {
171
+ const { method, path, handler } = route;
172
+ if (methods.includes(method.toUpperCase())) {
173
+ this.router[method](`/${this.endpoint}${path}`, applyMiddleware(handler));
174
+ }
175
+ });
176
+ }
177
+ }
178
+ getRouter() {
179
+ return this.router;
180
+ }
181
+ }
182
+
183
+ export { CrudController as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "crud-api-express",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "npx rollup -c"
10
+ },
11
+ "keywords": [],
12
+ "author": "",
13
+ "license": "ISC",
14
+ "dependencies": {
15
+ "express": "^4.19.2",
16
+ "mongoose": "^8.4.3",
17
+ "rollup": "^4.12.0",
18
+ "typescript": "^5.4.5"
19
+ },
20
+ "devDependencies": {
21
+ "@rollup/plugin-typescript": "^11.1.6",
22
+ "@types/express": "^4.17.21",
23
+ "@types/mongoose": "^5.11.97",
24
+ "@types/react": "^18.3.3",
25
+ "tslib": "^2.6.2"
26
+ }
27
+ }
package/readme.md ADDED
@@ -0,0 +1,91 @@
1
+ # CRUD API Controller using Express and Mongoose
2
+
3
+ npm install crud-api
4
+
5
+
6
+ This project provides a flexible and reusable CRUD (Create, Read, Update, Delete) API controller for MongoDB using Express.js and Mongoose.
7
+
8
+ ## Table of Contents
9
+
10
+ - [Introduction](#introduction)
11
+ - [Installation](#installation)
12
+ - [Usage](#usage)
13
+ - [API](#api)
14
+ - [Examples](#examples)
15
+ - [Contributing](#contributing)
16
+ - [License](#license)
17
+
18
+ ## Introduction
19
+
20
+ The `CrudController` class is designed to simplify the creation of RESTful APIs in Node.js applications that use MongoDB as the database backend. It abstracts away common CRUD operations, error handling, middleware integration, and supports custom routes and aggregation pipelines.
21
+
22
+ ## Installation
23
+
24
+ To use `CrudController` in your Node.js project, follow these steps:
25
+
26
+ 1. **Install Node.js**: Make sure you have Node.js installed on your system.
27
+ 2. **Install Dependencies**: Navigate to your project directory and run:
28
+ ```bash
29
+ npm install express mongoose
30
+
31
+
32
+ # Usage
33
+ Here's a basic example of how to use CrudController:
34
+
35
+
36
+ import express, { Request, Response } from 'express';
37
+ import mongoose, { Document, Model } from 'mongoose';
38
+ import CrudController, { CrudOptions } from 'crud-api';
39
+
40
+ // Define your Mongoose schema and model
41
+ interface ExampleModel extends Document {
42
+ name: string;
43
+ age: number;
44
+ }
45
+
46
+ const ExampleSchema = new mongoose.Schema<ExampleModel>({
47
+ name: { type: String, required: true },
48
+ age: { type: Number, required: true },
49
+ });
50
+
51
+ const ExampleModel = mongoose.model<ExampleModel>('Example', ExampleSchema);
52
+
53
+ // Create an instance of CrudController
54
+ const exampleController = new CrudController<ExampleModel>(ExampleModel, 'examples', {
55
+ // Optional configuration
56
+ });
57
+
58
+ // Create Express application
59
+ const app = express();
60
+
61
+ // Mount the CrudController router
62
+ app.use('/api/examples', exampleController.getRouter());
63
+
64
+ // Start the server
65
+ const PORT = process.env.PORT || 3000;
66
+ app.listen(PORT, () => {
67
+ console.log(`Server is running on port ${PORT}`);
68
+ });
69
+
70
+
71
+ # API
72
+
73
+ model: Mongoose model for CRUD operations.
74
+ endpoint: API endpoint path.
75
+ options: Optional configuration for CRUD operations.
76
+ Methods
77
+ getRouter(): Router - Returns the Express Router instance configured with CRUD routes.
78
+
79
+ CrudOptions<T>
80
+ Configuration options for CrudController.
81
+
82
+ # options
83
+ middleware?: ((req: Request, res: Response, next: NextFunction) => void)[] - Array of middleware functions.
84
+ onSuccess?: (res: Response, method: string, result: T | T[]) => void - Success handler function.
85
+ onError?: (res: Response, method: string, error: Error) => void - Error handler function.
86
+ methods?: ('POST' | 'GET' | 'PUT' | 'DELETE')[] - Array of HTTP methods to support.
87
+ relatedModel?: Model<any> - Related Mongoose model for relational operations.
88
+ relatedField?: string - Field name for related models.
89
+ relatedMethods?: ('POST' | 'GET' | 'PUT' | 'DELETE')[] - Methods to apply on related models.
90
+ aggregatePipeline?: object[] - MongoDB aggregation pipeline stages.
91
+ customRoutes?: { method: 'post' | 'get' | 'put' | 'delete', path: string, handler: (req: Request, res: Response) => void }[] - Array of custom routes definitions.
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from "rollup";
2
+ import typescript from "@rollup/plugin-typescript";
3
+
4
+
5
+
6
+
7
+ export default defineConfig({
8
+ input: 'src/index.ts',
9
+ output:{
10
+ dir:'dist',
11
+ format:'es',
12
+ name:"crud-api-express"
13
+ },
14
+ external:['express', 'mongoose'],
15
+ plugins:[typescript({tsconfig:'tsconfig.json'})]
16
+ })
package/src/index.ts ADDED
@@ -0,0 +1,216 @@
1
+ import { Request, Response, Router, NextFunction } from 'express';
2
+ import { Document, Model, Aggregate } from 'mongoose';
3
+
4
+ interface CrudOptions<T extends Document> {
5
+ middleware?: ((req: Request, res: Response, next: NextFunction) => void)[];
6
+ onSuccess?: (res: Response, method: string, result: T | T[]) => void;
7
+ onError?: (res: Response, method: string, error: Error) => void;
8
+ methods?: ('POST' | 'GET' | 'PUT' | 'DELETE')[];
9
+ relatedModel?: Model<any>;
10
+ relatedField?: string;
11
+ relatedMethods?: ('POST' | 'GET' | 'PUT' | 'DELETE')[];
12
+ aggregatePipeline?: object[];
13
+ customRoutes?: { method: 'post' | 'get' | 'put' | 'delete', path: string, handler: (req: Request, res: Response) => void }[];
14
+ }
15
+
16
+ class CrudController<T extends Document> {
17
+ private model: Model<T>;
18
+ private endpoint: string;
19
+ private router: Router;
20
+
21
+ constructor(model: Model<T>, endpoint: string, options: CrudOptions<T> = {}) {
22
+ this.model = model;
23
+ this.endpoint = endpoint;
24
+ this.router = Router();
25
+ this.configureRoutes(options);
26
+ }
27
+
28
+ private configureRoutes(options: CrudOptions<T>) {
29
+ const {
30
+ middleware = [],
31
+ onSuccess = (res, method, result) => res.status(200).send(result),
32
+ onError = (res, method, error) => res.status(400).send(error),
33
+ methods = ['POST', 'GET', 'PUT', 'DELETE']
34
+ } = options;
35
+
36
+
37
+ const applyMiddleware = (routeHandler: (req: Request, res: Response) => void) => {
38
+ return [...middleware, routeHandler];
39
+ };
40
+
41
+ // Create
42
+ if (methods.includes('POST')) {
43
+ this.router.post(`/${this.endpoint}`, applyMiddleware(async (req, res) => {
44
+ const method = 'POST';
45
+ try {
46
+ const item = new this.model(req.body);
47
+ const result:any = await item.save();
48
+ if (options.relatedModel && options.relatedMethods?.includes('POST')) {
49
+ await options.relatedModel.create({ [options.relatedField!]: result._id, ...req.body });
50
+ }
51
+ onSuccess(res, method, result);
52
+ } catch (error:any) {
53
+ onError(res, method, error);
54
+ }
55
+ }));
56
+ }
57
+
58
+
59
+ if (methods.includes('GET')) {
60
+ this.router.get(`/${this.endpoint}`, applyMiddleware(async (req, res) => {
61
+ const method = 'GET';
62
+ try {
63
+ const { filter, sort, page, limit } = req.query;
64
+ const query = filter ? JSON.parse(filter as string) : {};
65
+ const sortOrder = sort ? JSON.parse(sort as string) : {};
66
+ const pageNumber = parseInt(page as string, 10) || 1;
67
+ const pageSize = parseInt(limit as string, 10) || 10;
68
+ const skip = (pageNumber - 1) * pageSize;
69
+
70
+ let items: T[] | Aggregate<any[]>;
71
+ if (options.relatedModel && options.relatedMethods?.includes('GET')) {
72
+ items = await this.model.aggregate([
73
+ { $match: query },
74
+ {
75
+ $lookup: {
76
+ from: options.relatedModel.collection.name,
77
+ localField: options.relatedField!,
78
+ foreignField: '_id',
79
+ as: 'relatedData'
80
+ }
81
+ },
82
+ { $sort: sortOrder },
83
+ { $skip: skip },
84
+ { $limit: pageSize }
85
+ ]);
86
+ } else {
87
+ items = await this.model.find(query).sort(sortOrder).skip(skip).limit(pageSize);
88
+ }
89
+ onSuccess(res, method, items);
90
+ } catch (error:any) {
91
+ onError(res, method, error);
92
+ }
93
+ }));
94
+ }
95
+
96
+ if (methods.includes('GET')) {
97
+ this.router.get(`/${this.endpoint}/:id`, applyMiddleware(async (req, res) => {
98
+ const method = 'GET';
99
+ try {
100
+ let item: T | null;
101
+ if (options.relatedModel && options.relatedMethods?.includes('GET')) {
102
+ const aggregateResult = await this.model.aggregate([
103
+ { $match: { _id: req.params.id } },
104
+ {
105
+ $lookup: {
106
+ from: options.relatedModel.collection.name,
107
+ localField: options.relatedField!,
108
+ foreignField: '_id',
109
+ as: 'relatedData'
110
+ }
111
+ }
112
+ ]);
113
+ item = aggregateResult[0] as T;
114
+ } else {
115
+ item = await this.model.findById(req.params.id);
116
+ }
117
+ if (!item) {
118
+ return res.status(404).send();
119
+ }
120
+ onSuccess(res, method, item);
121
+ } catch (error:any) {
122
+ onError(res, method, error);
123
+ }
124
+ }));
125
+ }
126
+
127
+
128
+ if (methods.includes('PUT')) {
129
+ this.router.put(`/${this.endpoint}/:id`, applyMiddleware(async (req, res) => {
130
+ const method = 'PUT';
131
+ try {
132
+ const item = await this.model.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
133
+ if (!item) {
134
+ return res.status(404).send();
135
+ }
136
+ if (options.relatedModel && options.relatedMethods?.includes('PUT')) {
137
+ await options.relatedModel.updateMany({ [options.relatedField!]: item._id }, req.body);
138
+ }
139
+ onSuccess(res, method, item);
140
+ } catch (error:any) {
141
+ onError(res, method, error);
142
+ }
143
+ }));
144
+ }
145
+
146
+
147
+ if (methods.includes('DELETE')) {
148
+ this.router.delete(`/${this.endpoint}`, applyMiddleware(async (req, res) => {
149
+ const method = 'DELETE';
150
+ try {
151
+ const query = req.query.filter ? JSON.parse(req.query.filter as string) : {};
152
+ const deleteResult:any = await this.model.deleteMany(query);
153
+ if (deleteResult.deletedCount === 0) {
154
+ return res.status(404).send();
155
+ }
156
+ if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
157
+ await options.relatedModel.deleteMany({ [options.relatedField!]: { $in: query } });
158
+ }
159
+ onSuccess(res, method, deleteResult);
160
+ } catch (error: any) {
161
+ onError(res, method, error);
162
+ }
163
+ }));
164
+ }
165
+
166
+
167
+
168
+ if (methods.includes('DELETE')) {
169
+ this.router.delete(`/${this.endpoint}/:id`, applyMiddleware(async (req, res) => {
170
+ const method = 'DELETE';
171
+ try {
172
+ const item = await this.model.findByIdAndDelete(req.params.id);
173
+ if (!item) {
174
+ return res.status(404).send();
175
+ }
176
+ if (options.relatedModel && options.relatedMethods?.includes('DELETE')) {
177
+ await options.relatedModel.deleteMany({ [options.relatedField!]: item._id });
178
+ }
179
+ onSuccess(res, method, item);
180
+ } catch (error:any) {
181
+ onError(res, method, error);
182
+ }
183
+ }));
184
+ }
185
+
186
+
187
+ if (methods.includes('GET') && options.aggregatePipeline) {
188
+ this.router.get(`/${this.endpoint}/aggregate`, applyMiddleware(async (req, res) => {
189
+ const method = 'GET (Aggregate)';
190
+ try {
191
+ const pipeline:any = options.aggregatePipeline ?? [];
192
+ const results = await this.model.aggregate(pipeline);
193
+ onSuccess(res, method, results);
194
+ } catch (error:any) {
195
+ onError(res, method, error);
196
+ }
197
+ }));
198
+ }
199
+
200
+
201
+ if (options.customRoutes) {
202
+ options.customRoutes.forEach(route => {
203
+ const { method, path, handler } = route;
204
+ if (methods.includes(method.toUpperCase() as 'POST' | 'GET' | 'PUT' | 'DELETE')) {
205
+ this.router[method](`/${this.endpoint}${path}`, applyMiddleware(handler));
206
+ }
207
+ });
208
+ }
209
+ }
210
+
211
+ public getRouter(): Router {
212
+ return this.router;
213
+ }
214
+ }
215
+
216
+ export default CrudController;
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "sourceMap": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }