crud-api-express 1.2.6 → 2.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.
- package/dist/index.cjs +483 -119
- package/dist/index.d.ts +197 -37
- package/dist/index.mjs +480 -119
- package/package.json +27 -18
- package/readme.md +322 -198
- package/src/index.ts +909 -199
- package/dist/index.js +0 -212
package/readme.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# crud-api-express
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
@@ -8,271 +8,396 @@
|
|
|
8
8
|

|
|
9
9
|

|
|
10
10
|
|
|
11
|
+
> A powerful, flexible CRUD controller for Express + Mongoose — auto‑generates RESTful endpoints with lifecycle hooks, validation, soft delete, search, bulk operations, pagination metadata, and more.
|
|
11
12
|
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ✨ Features
|
|
16
|
+
|
|
17
|
+
- 🚀 **Zero boilerplate** — full CRUD in 3 lines of code
|
|
18
|
+
- 🪝 **Lifecycle hooks** — `beforeCreate`, `afterUpdate`, `beforeDelete`, etc.
|
|
19
|
+
- ✅ **Validation hooks** — reject bad data before it hits Mongoose
|
|
20
|
+
- 🔍 **Search endpoint** — case-insensitive text search across multiple fields
|
|
21
|
+
- 🗑️ **Soft delete** — mark records as deleted + restore endpoint
|
|
22
|
+
- 📦 **Bulk operations** — create, update, and delete in batch
|
|
23
|
+
- 🔒 **Per-route middleware** — different auth/logic for read vs. write
|
|
24
|
+
- 📄 **Pagination metadata** — total, pages, hasNext, hasPrev
|
|
25
|
+
- 🎯 **Field selection & population** — `?select=name,email&populate=author`
|
|
26
|
+
- 🔢 **Count & exists** — lightweight endpoints for checking data
|
|
27
|
+
- 📊 **Dynamic aggregation** — static pipelines or functions of `req`
|
|
28
|
+
- 🏗️ **PATCH support** — partial updates with `$set` semantics
|
|
29
|
+
- 🔗 **Related model cascading** — auto‑create/update/delete linked models
|
|
30
|
+
- 📝 **Full TypeScript support** — exported types, generics, JSDoc
|
|
12
31
|
|
|
13
|
-
|
|
32
|
+
---
|
|
14
33
|
|
|
15
|
-
|
|
34
|
+
## 📦 Installation
|
|
16
35
|
|
|
17
36
|
```bash
|
|
18
37
|
npm install crud-api-express
|
|
38
|
+
# Peer dependencies (install alongside):
|
|
39
|
+
npm install express mongoose
|
|
19
40
|
```
|
|
20
|
-
This project provides a flexible and reusable CRUD (Create, Read, Update, Delete) API controller for MongoDB using Express.js and Mongoose.
|
|
21
41
|
|
|
22
|
-
|
|
23
|
-
[Doc Page Visit here](https://mukeshdev.vercel.app/crudapi)
|
|
42
|
+
---
|
|
24
43
|
|
|
25
|
-
##
|
|
44
|
+
## 🚀 Quick Start
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
import express from 'express';
|
|
48
|
+
import mongoose from 'mongoose';
|
|
49
|
+
import CrudController from 'crud-api-express';
|
|
26
50
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
// 1. Define your model
|
|
52
|
+
const UserSchema = new mongoose.Schema({
|
|
53
|
+
name: { type: String, required: true },
|
|
54
|
+
email: { type: String, required: true, unique: true },
|
|
55
|
+
role: { type: String, default: 'user', enum: ['user', 'admin'] },
|
|
56
|
+
}, { timestamps: true, versionKey: false });
|
|
33
57
|
|
|
58
|
+
const User = mongoose.model('User', UserSchema);
|
|
34
59
|
|
|
60
|
+
// 2. Create the controller
|
|
61
|
+
const userCtrl = new CrudController(User, 'users');
|
|
35
62
|
|
|
36
|
-
|
|
63
|
+
// 3. Mount and go
|
|
64
|
+
const app = express();
|
|
65
|
+
app.use(express.json());
|
|
66
|
+
app.use('/api', userCtrl.getRouter());
|
|
37
67
|
|
|
38
|
-
|
|
68
|
+
mongoose.connect('mongodb://localhost:27017/mydb').then(() => {
|
|
69
|
+
app.listen(3000, () => console.log('Server running on port 3000'));
|
|
70
|
+
});
|
|
71
|
+
```
|
|
39
72
|
|
|
40
|
-
|
|
73
|
+
That's it — you now have **15+ endpoints** auto-generated. 🎉
|
|
41
74
|
|
|
42
75
|
---
|
|
43
76
|
|
|
44
|
-
##
|
|
77
|
+
## 🛣️ Auto-Generated Endpoints
|
|
78
|
+
|
|
79
|
+
| Method | Endpoint | Description |
|
|
80
|
+
|--------|----------|-------------|
|
|
81
|
+
| `POST` | `/users` | Create a record |
|
|
82
|
+
| `POST` | `/users/bulk` | Bulk create records |
|
|
83
|
+
| `GET` | `/users` | List all (filter/sort/paginate/select/populate) |
|
|
84
|
+
| `GET` | `/users/:id` | Get one by ID |
|
|
85
|
+
| `GET` | `/users/search` | Text search across fields |
|
|
86
|
+
| `GET` | `/users/count` | Count matching records |
|
|
87
|
+
| `GET` | `/users/exists/:id` | Check if a record exists |
|
|
88
|
+
| `GET` | `/users/aggregate` | Run aggregation pipeline |
|
|
89
|
+
| `PUT` | `/users/:id` | Full update by ID |
|
|
90
|
+
| `PATCH` | `/users/:id` | Partial update by ID |
|
|
91
|
+
| `PATCH` | `/users/bulk` | Bulk update by filter |
|
|
92
|
+
| `PATCH` | `/users/:id/restore` | Restore soft-deleted record *(soft delete only)* |
|
|
93
|
+
| `DELETE` | `/users/:id` | Delete one by ID |
|
|
94
|
+
| `DELETE` | `/users` | Delete by filter |
|
|
95
|
+
| `DELETE` | `/users/bulk` | Bulk delete by IDs array |
|
|
45
96
|
|
|
46
|
-
|
|
97
|
+
---
|
|
47
98
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
99
|
+
## ⚙️ Full Options Reference
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const ctrl = new CrudController(Model, 'endpoint', {
|
|
103
|
+
// HTTP methods to enable (default: all five)
|
|
104
|
+
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
105
|
+
|
|
106
|
+
// Global middleware for all routes
|
|
107
|
+
middleware: [authMiddleware, loggerMiddleware],
|
|
52
108
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
109
|
+
// Per-operation middleware
|
|
110
|
+
routeMiddleware: {
|
|
111
|
+
create: [validateBody],
|
|
112
|
+
read: [],
|
|
113
|
+
update: [validateBody],
|
|
114
|
+
delete: [requireAdmin],
|
|
59
115
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const options = {
|
|
66
|
-
middleware: [
|
|
67
|
-
(req, res, next) => {
|
|
68
|
-
const authToken = req.headers.authorization;
|
|
69
|
-
if (!authToken) {
|
|
70
|
-
return res.status(401).json({ message: 'Unauthorized' });
|
|
71
|
-
}
|
|
72
|
-
next();
|
|
73
|
-
},
|
|
74
|
-
(req, res, next) => {
|
|
75
|
-
console.log(`Request received at ${new Date()}`);
|
|
76
|
-
next();
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
onSuccess: (res, method, result) => {
|
|
80
|
-
console.log(`Successful ${method} operation:`, result);
|
|
81
|
-
res.status(200).json({ success: true, data: result });
|
|
116
|
+
|
|
117
|
+
// Custom success/error response shapes
|
|
118
|
+
onSuccess: (res, method, result, meta) => {
|
|
119
|
+
res.status(200).json({ success: true, data: result, ...(meta && { pagination: meta }) });
|
|
82
120
|
},
|
|
83
121
|
onError: (res, method, error) => {
|
|
84
|
-
|
|
85
|
-
|
|
122
|
+
res.status(500).json({ success: false, error: error.message });
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Lifecycle hooks
|
|
126
|
+
hooks: {
|
|
127
|
+
beforeCreate: async (req, data) => ({ ...data, createdBy: req.user.id }),
|
|
128
|
+
afterCreate: async (req, result) => { await notifySlack(result); },
|
|
129
|
+
beforeUpdate: async (req, id, data) => data,
|
|
130
|
+
afterUpdate: async (req, result) => {},
|
|
131
|
+
beforeDelete: async (req, id) => {},
|
|
132
|
+
afterDelete: async (req, result) => { await auditLog('delete', result._id); },
|
|
133
|
+
beforeRead: async (req, query) => ({ ...query, org: req.user.orgId }),
|
|
134
|
+
afterRead: async (req, result) => result,
|
|
86
135
|
},
|
|
87
|
-
|
|
136
|
+
|
|
137
|
+
// Validation hooks (run before Mongoose validation)
|
|
138
|
+
validate: {
|
|
139
|
+
create: (data) => ({
|
|
140
|
+
valid: !!data.email && !!data.name,
|
|
141
|
+
errors: [
|
|
142
|
+
...(!data.email ? ['Email is required'] : []),
|
|
143
|
+
...(!data.name ? ['Name is required'] : []),
|
|
144
|
+
],
|
|
145
|
+
}),
|
|
146
|
+
update: (data) => ({ valid: true }),
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Field selection (Mongoose select syntax)
|
|
150
|
+
select: 'name email role -_id',
|
|
151
|
+
|
|
152
|
+
// Auto-populate references
|
|
153
|
+
populate: 'department',
|
|
154
|
+
// or: populate: [{ path: 'department', select: 'name' }],
|
|
155
|
+
|
|
156
|
+
// Search fields for GET /endpoint/search
|
|
157
|
+
searchFields: ['name', 'email'],
|
|
158
|
+
|
|
159
|
+
// Soft delete (sets deletedAt instead of removing)
|
|
160
|
+
softDelete: true,
|
|
161
|
+
|
|
162
|
+
// Aggregation pipeline (static or dynamic)
|
|
88
163
|
aggregatePipeline: [
|
|
89
164
|
{ $match: { status: 'Active' } },
|
|
90
165
|
{ $sort: { createdAt: -1 } },
|
|
91
166
|
],
|
|
167
|
+
// or dynamic:
|
|
168
|
+
// aggregatePipeline: (req) => [{ $match: { region: req.query.region } }],
|
|
169
|
+
|
|
170
|
+
// Related model cascading
|
|
171
|
+
relatedModel: ProfileModel,
|
|
172
|
+
relatedField: 'userId',
|
|
173
|
+
relatedMethods: ['POST', 'DELETE'],
|
|
174
|
+
|
|
175
|
+
// Custom routes (always registered regardless of methods filter)
|
|
92
176
|
customRoutes: [
|
|
93
177
|
{
|
|
94
178
|
method: 'get',
|
|
95
|
-
path: '/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
{
|
|
101
|
-
method: 'post',
|
|
102
|
-
path: '/custom-action',
|
|
103
|
-
handler: (req, res) => {
|
|
104
|
-
res.json({ message: 'Custom action executed' });
|
|
179
|
+
path: '/stats',
|
|
180
|
+
middleware: [cacheMiddleware],
|
|
181
|
+
handler: async (req, res) => {
|
|
182
|
+
const count = await Model.countDocuments({ status: 'Active' });
|
|
183
|
+
res.json({ activeUsers: count });
|
|
105
184
|
},
|
|
106
185
|
},
|
|
107
186
|
],
|
|
108
|
-
};
|
|
187
|
+
});
|
|
188
|
+
```
|
|
109
189
|
|
|
110
|
-
|
|
190
|
+
---
|
|
111
191
|
|
|
112
|
-
|
|
192
|
+
## 📡 Query Parameters
|
|
113
193
|
|
|
114
|
-
|
|
115
|
-
.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true })
|
|
116
|
-
.then(() => {
|
|
117
|
-
console.log('Connected to MongoDB');
|
|
194
|
+
### GET All — `GET /api/users?...`
|
|
118
195
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
196
|
+
| Param | Example | Description |
|
|
197
|
+
|-------|---------|-------------|
|
|
198
|
+
| `filter` | `{"status":"Active"}` | MongoDB filter object |
|
|
199
|
+
| `sort` | `{"createdAt":-1}` | Sort order |
|
|
200
|
+
| `page` | `1` | Page number (default: 1) |
|
|
201
|
+
| `limit` | `10` | Results per page (default: 10) |
|
|
202
|
+
| `select` | `name,email` | Fields to include/exclude |
|
|
203
|
+
| `populate` | `author,comments` | References to populate |
|
|
204
|
+
| `includeDeleted` | `true` | Include soft-deleted records |
|
|
122
205
|
|
|
123
|
-
|
|
206
|
+
### Search — `GET /api/users/search?...`
|
|
124
207
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
208
|
+
| Param | Example | Description |
|
|
209
|
+
|-------|---------|-------------|
|
|
210
|
+
| `q` | `john` | Search term (**required**) |
|
|
211
|
+
| `fields` | `name,email` | Override default searchFields |
|
|
212
|
+
| `page` | `1` | Page number |
|
|
213
|
+
| `limit` | `10` | Results per page |
|
|
214
|
+
| `select` | `name,email` | Fields to include |
|
|
215
|
+
| `populate` | `author` | References to populate |
|
|
216
|
+
|
|
217
|
+
### Pagination Response Shape
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"data": [...],
|
|
222
|
+
"pagination": {
|
|
223
|
+
"total": 150,
|
|
224
|
+
"page": 2,
|
|
225
|
+
"limit": 10,
|
|
226
|
+
"pages": 15,
|
|
227
|
+
"hasNext": true,
|
|
228
|
+
"hasPrev": true
|
|
229
|
+
}
|
|
230
|
+
}
|
|
134
231
|
```
|
|
135
|
-
Here's a basic example of how to use in cjs module `CrudController`:
|
|
136
|
-
```javascript
|
|
137
232
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 🪝 Lifecycle Hooks
|
|
141
236
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
237
|
+
Hooks let you inject business logic without fighting the abstraction:
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
hooks: {
|
|
241
|
+
// Transform data before saving — return the modified object
|
|
242
|
+
beforeCreate: async (req, data) => {
|
|
243
|
+
data.createdBy = req.user.id;
|
|
244
|
+
data.slug = slugify(data.name);
|
|
245
|
+
return data;
|
|
148
246
|
},
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const options = {
|
|
155
|
-
middleware: [
|
|
156
|
-
(req, res, next) => {
|
|
157
|
-
const authToken = req.headers.authorization;
|
|
158
|
-
if (!authToken) {
|
|
159
|
-
return res.status(401).json({ message: 'Unauthorized' });
|
|
160
|
-
}
|
|
161
|
-
next();
|
|
162
|
-
},
|
|
163
|
-
(req, res, next) => {
|
|
164
|
-
console.log(`Request received at ${new Date()}`);
|
|
165
|
-
next();
|
|
166
|
-
},
|
|
167
|
-
],
|
|
168
|
-
onSuccess: (res, method, result) => {
|
|
169
|
-
console.log(`Successful ${method} operation:`, result);
|
|
170
|
-
res.status(200).json({ success: true, data: result });
|
|
247
|
+
|
|
248
|
+
// Side-effects after saving
|
|
249
|
+
afterCreate: async (req, result) => {
|
|
250
|
+
await sendWelcomeEmail(result.email);
|
|
251
|
+
await auditLog('user.created', result._id);
|
|
171
252
|
},
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
253
|
+
|
|
254
|
+
// Scope all reads to the user's organization
|
|
255
|
+
beforeRead: async (req, query) => {
|
|
256
|
+
return { ...query, organizationId: req.user.orgId };
|
|
175
257
|
},
|
|
176
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
177
|
-
aggregatePipeline: [
|
|
178
|
-
{ $match: { status: 'Active' } },
|
|
179
|
-
{ $sort: { createdAt: -1 } },
|
|
180
|
-
],
|
|
181
|
-
customRoutes: [
|
|
182
|
-
{
|
|
183
|
-
method: 'get',
|
|
184
|
-
path: '/custom-route',
|
|
185
|
-
handler: (req, res) => {
|
|
186
|
-
res.json({ message: 'Custom route handler executed' });
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
method: 'post',
|
|
191
|
-
path: '/custom-action',
|
|
192
|
-
handler: (req, res) => {
|
|
193
|
-
res.json({ message: 'Custom action executed' });
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
};
|
|
198
258
|
|
|
199
|
-
|
|
259
|
+
// Prevent deletion of system records
|
|
260
|
+
beforeDelete: async (req, id) => {
|
|
261
|
+
const item = await User.findById(id);
|
|
262
|
+
if (item?.role === 'system') {
|
|
263
|
+
throw new Error('Cannot delete system users');
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
```
|
|
200
268
|
|
|
201
|
-
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 🗑️ Soft Delete
|
|
202
272
|
|
|
203
|
-
|
|
204
|
-
.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true })
|
|
205
|
-
.then(() => {
|
|
206
|
-
console.log('Connected to MongoDB');
|
|
273
|
+
Enable soft delete to preserve data while hiding it from default queries:
|
|
207
274
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
275
|
+
```javascript
|
|
276
|
+
const ctrl = new CrudController(User, 'users', {
|
|
277
|
+
softDelete: true,
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
- `DELETE /users/:id` → Sets `deletedAt: Date` instead of removing
|
|
282
|
+
- `GET /users` → Auto-excludes records with `deletedAt`
|
|
283
|
+
- `GET /users?includeDeleted=true` → Shows everything including deleted
|
|
284
|
+
- `PATCH /users/:id/restore` → Removes `deletedAt` to restore the record
|
|
285
|
+
|
|
286
|
+
---
|
|
211
287
|
|
|
212
|
-
|
|
288
|
+
## 📦 Bulk Operations
|
|
213
289
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
290
|
+
```bash
|
|
291
|
+
# Bulk Create
|
|
292
|
+
POST /api/users/bulk
|
|
293
|
+
Body: [{ "name": "Alice" }, { "name": "Bob" }]
|
|
294
|
+
|
|
295
|
+
# Bulk Update (by filter)
|
|
296
|
+
PATCH /api/users/bulk
|
|
297
|
+
Body: { "filter": { "role": "user" }, "update": { "status": "inactive" } }
|
|
298
|
+
|
|
299
|
+
# Bulk Delete (by IDs)
|
|
300
|
+
DELETE /api/users/bulk
|
|
301
|
+
Body: { "ids": ["id1", "id2", "id3"] }
|
|
223
302
|
```
|
|
224
303
|
|
|
225
304
|
---
|
|
226
305
|
|
|
227
|
-
##
|
|
306
|
+
## 🔒 Per-Route Middleware
|
|
228
307
|
|
|
229
|
-
|
|
230
|
-
Returns the Express Router instance configured with CRUD routes.
|
|
308
|
+
Apply different middleware to different operations:
|
|
231
309
|
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
{ "method": "POST", "path": "/examples/custom-action", "params": null }
|
|
243
|
-
]
|
|
310
|
+
```javascript
|
|
311
|
+
const ctrl = new CrudController(User, 'users', {
|
|
312
|
+
middleware: [loggerMiddleware], // applies to ALL routes
|
|
313
|
+
routeMiddleware: {
|
|
314
|
+
create: [requireAuth, validateBody],
|
|
315
|
+
read: [optionalAuth],
|
|
316
|
+
update: [requireAuth, requireOwner],
|
|
317
|
+
delete: [requireAuth, requireAdmin],
|
|
318
|
+
},
|
|
319
|
+
});
|
|
244
320
|
```
|
|
245
321
|
|
|
246
|
-
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## 💡 Multiple Controllers
|
|
325
|
+
|
|
326
|
+
Mount multiple controllers on the same app:
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
const userCtrl = new CrudController(User, 'users', { ... });
|
|
330
|
+
const productCtrl = new CrudController(Product, 'products', { ... });
|
|
331
|
+
const orderCtrl = new CrudController(Order, 'orders', { ... });
|
|
332
|
+
|
|
333
|
+
app.use('/api', userCtrl.getRouter());
|
|
334
|
+
app.use('/api', productCtrl.getRouter());
|
|
335
|
+
app.use('/api', orderCtrl.getRouter());
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 📋 API Methods
|
|
341
|
+
|
|
342
|
+
| Method | Returns | Description |
|
|
343
|
+
|--------|---------|-------------|
|
|
344
|
+
| `getRouter()` | `Router` | Express Router with all configured routes |
|
|
345
|
+
| `getRoutes()` | `RouteInfo[]` | Array of registered route definitions |
|
|
346
|
+
|
|
347
|
+
---
|
|
247
348
|
|
|
349
|
+
## 🔄 Migration from v1.x
|
|
248
350
|
|
|
249
|
-
###
|
|
351
|
+
### Breaking Changes
|
|
250
352
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
353
|
+
1. **`onSuccess` signature** — Now receives an optional 4th `meta` parameter for pagination metadata
|
|
354
|
+
2. **Custom routes** — No longer gated by the `methods` filter; they always register
|
|
355
|
+
3. **Soft delete** — When `softDelete: true`, DELETE behavior changes from removing to marking
|
|
356
|
+
4. **Bulk delete safety** — `DELETE /endpoint` now requires a non-empty filter to prevent accidental full-table deletes
|
|
357
|
+
|
|
358
|
+
### New Defaults
|
|
359
|
+
|
|
360
|
+
- Methods array now includes `'PATCH'` by default
|
|
361
|
+
- GET all returns pagination metadata in the default response shape
|
|
362
|
+
|
|
363
|
+
### Upgrade Steps
|
|
364
|
+
|
|
365
|
+
1. Update your package: `npm install crud-api-express@latest`
|
|
366
|
+
2. If your `onSuccess` callback has strict arity checks, add the optional `meta` parameter
|
|
367
|
+
3. Test your custom routes — they will now register even if their HTTP method isn't in the `methods` array
|
|
368
|
+
4. If using deletion endpoints, ensure you pass filters for bulk delete
|
|
262
369
|
|
|
263
370
|
---
|
|
264
371
|
|
|
265
|
-
##
|
|
372
|
+
## 🔧 CommonJS Usage
|
|
266
373
|
|
|
267
|
-
|
|
268
|
-
|
|
374
|
+
```javascript
|
|
375
|
+
const CrudController = require('crud-api-express');
|
|
376
|
+
const User = require('./models/User');
|
|
269
377
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
- **`sort`** → Sort order (e.g., `{ "expiry_date": 1 }` for ascending).
|
|
273
|
-
- **`page`** → Pagination (e.g., `page=1`).
|
|
274
|
-
- **`limit`** → Number of results per page.
|
|
378
|
+
const ctrl = new CrudController(User, 'users', { ... });
|
|
379
|
+
```
|
|
275
380
|
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 📖 TypeScript Support
|
|
384
|
+
|
|
385
|
+
All types are exported for full TypeScript support:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import CrudController, {
|
|
389
|
+
CrudOptions,
|
|
390
|
+
MiddlewareFunction,
|
|
391
|
+
SuccessHandler,
|
|
392
|
+
ErrorHandler,
|
|
393
|
+
ValidationResult,
|
|
394
|
+
PaginationMeta,
|
|
395
|
+
HttpMethod,
|
|
396
|
+
RouteInfo,
|
|
397
|
+
} from 'crud-api-express';
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
276
401
|
|
|
277
402
|
## License
|
|
278
403
|
|
|
@@ -280,6 +405,5 @@ This project is licensed under the **ISC License**.
|
|
|
280
405
|
|
|
281
406
|
## Support Me! ❤️
|
|
282
407
|
|
|
283
|
-
If you find this package useful, consider supporting me:
|
|
408
|
+
If you find this package useful, consider supporting me:
|
|
284
409
|
[Buy Me a Coffee ☕](https://buymeacoffee.com/mrider007)
|
|
285
|
-
|