@promind/honey 1.47.22 → 1.47.24

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.
Files changed (2) hide show
  1. package/package.json +1 -2
  2. package/README.md +0 -1041
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promind/honey",
3
- "version": "1.47.22",
3
+ "version": "1.47.24",
4
4
  "description": "An expressJS based library for simplifying the development of CRUD APIs using Postgres",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -29,7 +29,6 @@
29
29
  "test-watch": "jest --watch --passWithNoTests",
30
30
  "start": "node dist/index.js",
31
31
  "start:dev": "nodemon --exec ts-node --transpile-only ./src/index.ts",
32
- "prepare": "npm run build",
33
32
  "prepare:dev": "husky install",
34
33
  "bump": "yarn changeset; yarn changeset version"
35
34
  },
package/README.md DELETED
@@ -1,1041 +0,0 @@
1
- # HoneyJS
2
-
3
- A TypeScript-based Node.js declarative library for building RESTful APIs with seamless integration to PostgreSQL databases using Sequelize ORM. HoneyJS streamlines the process of creating, reading, updating, deleting, and upserting data with a focus on simplicity and reusability.
4
-
5
- ## Table of Contents
6
-
7
- - [Features](#features)
8
- - [Prerequisites](#prerequisites)
9
- - [Installation](#installation)
10
- - [Quick Start](#quick-start)
11
- - [Configuration](#configuration)
12
- - [Database Connection](#database-connection)
13
- - [Environment Variables](#environment-variables)
14
- - [Built-in Defaults](#built-in-defaults)
15
- - [API Reference](#api-reference)
16
- - [Core Methods](#core-methods)
17
- - [CRUD Operations](#crud-operations)
18
- - [Advanced Usage](#advanced-usage)
19
- - [Database Utilities](#database-utilities)
20
- - [Error Handling](#error-handling)
21
- - [Middleware](#middleware)
22
- - [Examples](#examples)
23
- - [Custom Express Routes](#custom-express-routes)
24
- - [TypeScript Support](#typescript-support)
25
- - [Contributing](#contributing)
26
- - [License](#license)
27
-
28
- ## Features
29
-
30
- - **Declarative API Definition**: Define your REST endpoints with simple configuration objects
31
- - **Full CRUD Support**: Built-in controllers for create, read, update, delete, and upsert operations
32
- - **PostgreSQL Integration**: Seamless connection to PostgreSQL via Sequelize ORM
33
- - **Query Building**: Flexible filtering, sorting, and pagination
34
- - **Middleware Support**: Add custom middleware to your routes
35
- - **TypeScript Ready**: Full TypeScript support with comprehensive type definitions
36
- - **Validation**: Request validation using Joi
37
- - **Error Handling**: Consistent error handling and response formatting
38
- - **API Documentation**: Automatic OpenAPI documentation is generated internally via `express-oas-generator`.
39
-
40
- ## Prerequisites
41
-
42
- - Node.js (>=20.9.0)
43
- - PostgreSQL database
44
- - TypeScript (for TypeScript projects)
45
- - Create the relevant tables and data models for your Postgres database. You can use the exposed sequelize utility functions and migrations to do this.
46
- - See <https://github.com/chinaza/generator-honeyjs> to generate a project
47
-
48
- ## Installation
49
-
50
- ```bash
51
- # Using npm
52
- npm install @promind/honey
53
-
54
- # Using yarn
55
- yarn add @promind/honey
56
- ```
57
-
58
- ## Quick Start
59
-
60
- 1. Create a new file (e.g., `server.ts` or `server.js`)
61
-
62
- ```typescript
63
- import { createHoney } from '@promind/honey';
64
-
65
- // Initialize HoneyJS with port and database connection string
66
- const honey = createHoney(
67
- '3000',
68
- 'postgresql://username:password@localhost:5432/database'
69
- );
70
-
71
- // Define a simple CRUD API for 'users' resource
72
- honey.get({
73
- resource: 'users',
74
- fields: ['id', 'name', 'email', 'created_at']
75
- });
76
-
77
- honey.getById({
78
- resource: 'users',
79
- fields: ['id', 'name', 'email', 'created_at']
80
- });
81
-
82
- honey.create({
83
- resource: 'users',
84
- params: {
85
- name: 'string',
86
- email: 'string',
87
- created_at: '@updatedAt'
88
- },
89
- message: 'User created successfully'
90
- });
91
-
92
- honey.updateById({
93
- resource: 'users',
94
- params: {
95
- name: 'replace',
96
- email: 'replace'
97
- },
98
- message: 'User updated successfully'
99
- });
100
-
101
- honey.deleteById({
102
- resource: 'users',
103
- message: 'User deleted successfully'
104
- });
105
-
106
- // Start the server
107
- honey.startServer();
108
- ```
109
-
110
- 1. Run your server:
111
-
112
- ```bash
113
- # If using TypeScript
114
- npx ts-node server.ts
115
-
116
- # If using JavaScript
117
- node server.js
118
- ```
119
-
120
- Your API will be available at `http://localhost:3000/api/users`
121
-
122
- ## Configuration
123
-
124
- ### Database Connection
125
-
126
- You can connect to your PostgreSQL database using either a connection string or a configuration object:
127
-
128
- ```typescript
129
- // Using connection string
130
- const honey = createHoney(
131
- '3000',
132
- 'postgresql://username:password@localhost:5432/database'
133
- );
134
-
135
- // Using configuration object
136
- const honey = createHoney('3000', {
137
- host: 'localhost',
138
- port: 5432,
139
- user: 'username',
140
- password: 'password',
141
- database: 'database'
142
- });
143
- ```
144
-
145
- ### Environment Variables
146
-
147
- HoneyJS supports configuration through environment variables. Create a `.env` file in your project root:
148
-
149
- ```
150
- PORT=3000
151
- DB_URI=postgresql://username:password@localhost:5432/database
152
- ```
153
-
154
- Then initialize HoneyJS without parameters:
155
-
156
- ```typescript
157
- import { createHoney } from '@promind/honey';
158
-
159
- const honey = createHoney(process.env.PORT, process.env.DB_URI);
160
- ```
161
-
162
- ### Built-in Defaults
163
-
164
- HoneyJS automatically configures the following on startup:
165
-
166
- - **CORS**: Enabled for all origins (`*`) with all methods and headers allowed
167
- - **Body parsing**: JSON and URL-encoded bodies up to 50MB
168
- - **Cookie parsing**: Enabled via `cookie-parser`
169
- - **Route prefix**: `/api` (configurable via `metadata.routePrefix`)
170
-
171
- ## API Reference
172
-
173
- ### Core Methods
174
-
175
- #### `createHoney(port, dbOptions, metadata?)`
176
-
177
- Creates a new HoneyJS instance.
178
-
179
- - `port`: Server port number or string
180
- - `dbOptions`: PostgreSQL connection string or configuration object
181
- - `metadata` (optional): Additional configuration options
182
- - `fallbackErrorMessage`: Custom 404 error message
183
- - `routePrefix`: API route prefix (default: `/api`)
184
-
185
- Returns a `Honey` instance.
186
-
187
- #### `honey.startServer()`
188
-
189
- Starts the HTTP server.
190
-
191
- #### `honey.addMiddleware(middleware[])`
192
-
193
- Adds global middleware to all routes.
194
-
195
- #### `honey.db`
196
-
197
- Exposes the underlying Sequelize instance for direct database access.
198
-
199
- ### CRUD Operations
200
-
201
- #### `honey.create(options)`
202
-
203
- Creates a POST endpoint for creating new resources.
204
-
205
- The response `data` field contains `{ id }` (the inserted record's ID) by default unless `processResponseData` is provided.
206
-
207
- ```typescript
208
- honey.create({
209
- resource: 'posts', // Resource name (used in URL path)
210
- table: 'blog_posts', // Optional: Table name if different from resource
211
- params: {
212
- // Request body parameters
213
- title: 'string', // Parameter type validation
214
- content: 'string',
215
- author_id: 'number',
216
- published: 'boolean',
217
- metadata: 'json', // For JSON fields
218
- created_at: '@updatedAt' // Special value to set current timestamp
219
- },
220
- message: 'Post created', // Success message
221
- pathOverride: '/blog/posts', // Optional: Custom path
222
- middleware: [authMiddleware], // Optional: Route-specific middleware
223
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
224
- methodOverride: 'post', // Optional: Override the HTTP method
225
- processResponseData: (data, req) => {
226
- // Optional: Transform response data
227
- return { ...data, extra: 'info' };
228
- },
229
- processErrorResponse: (err) => err // Optional: Customize error response
230
- });
231
- ```
232
-
233
- #### `honey.get(options)`
234
-
235
- Creates a GET endpoint for retrieving a list of resources.
236
-
237
- Automatically supports `?page=` and `?limit=` query parameters for pagination. The response shape is:
238
-
239
- ```json
240
- {
241
- "data": [...],
242
- "meta": {
243
- "pagination": {
244
- "total": 100,
245
- "pageSize": 10,
246
- "page": 1,
247
- "pageCount": 10
248
- }
249
- }
250
- }
251
- ```
252
-
253
- ```typescript
254
- honey.get({
255
- resource: 'posts',
256
- fields: ['id', 'title', 'author_id', 'created_at'], // Fields to return
257
- filter: {
258
- // Optional: Query filters
259
- title: {
260
- operator: 'like', // Filter operator
261
- value: 'string', // Parameter type
262
- location: 'query' // Optional: where to read the value from (default: query string)
263
- },
264
- published: {
265
- operator: '=',
266
- value: 'boolean'
267
- },
268
- author_id: {
269
- operator: '=',
270
- value: 'number',
271
- overrideValue: (req) => req.user?.id // Dynamic value based on request
272
- },
273
- $or: {
274
- // Logical OR condition
275
- title: {
276
- operator: 'like',
277
- value: 'string'
278
- },
279
- content: {
280
- operator: 'like',
281
- value: 'string'
282
- }
283
- }
284
- },
285
- format: {
286
- // Optional: Sorting
287
- sort: 'DESC', // ASC or DESC
288
- sortField: 'created_at' // Field to sort by
289
- },
290
- joins: [
291
- // Optional: SQL joins
292
- {
293
- table: 'users',
294
- type: 'inner', // 'inner' | 'left' | 'right' | 'full' | 'cross' (default: 'inner')
295
- on: {
296
- left: 'posts.author_id',
297
- right: 'users.id',
298
- operator: '=' // '=' | '!=' | '<' | '<=' | '>' | '>='
299
- },
300
- alias: 'author' // Optional alias
301
- }
302
- ],
303
- shouldErrorOnNotFound: false, // Optional: When false, returns empty data instead of 404 (default: true)
304
- middleware: [authMiddleware], // Optional: Route-specific middleware
305
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
306
- methodOverride: 'get' // Optional: Override the HTTP method
307
- });
308
- ```
309
-
310
- **Filter operators:** `'='`, `'!='`, `'<'`, `'<='`, `'>'`, `'>='`, `'in'`, `'not in'`, `'like'`, `'not like'`
311
-
312
- > **Note:** `'like'` uses PostgreSQL `ILIKE` for case-insensitive matching.
313
-
314
- **Filter `value` types:** `'string'`, `'number'`, `'boolean'`, `'json'`, `'csv'` (splits comma-separated string into array), `'as-is'` (passes value through unchanged)
315
-
316
- **Filter `location` options:** `'query'` (default), `'body'`, `'headers'`, `'request'`, `'params'`
317
-
318
- **Filter `overrideValue`:** Can be a static value or a function `(req) => value` for dynamic values based on the request.
319
-
320
- **Using joins with dot-notation:** When using joins, fields can use dot-notation to reference columns from joined tables: `'tableName.fieldName'`.
321
-
322
- #### `honey.getById(options)`
323
-
324
- Creates a GET endpoint for retrieving a single resource by ID.
325
-
326
- ```typescript
327
- honey.getById({
328
- resource: 'posts',
329
- fields: ['id', 'title', 'content', 'author_id', 'created_at'],
330
- idField: 'slug', // Optional: Use a different field as identifier (default: 'id')
331
- filter: {
332
- // Optional: Additional filters
333
- published: {
334
- operator: '=',
335
- overrideValue: true // Force a value regardless of request
336
- }
337
- },
338
- joins: [
339
- // Optional: SQL joins (same as honey.get())
340
- {
341
- table: 'users',
342
- type: 'left',
343
- on: { left: 'posts.author_id', right: 'users.id' }
344
- }
345
- ],
346
- shouldErrorOnNotFound: false, // Optional: When false, returns empty data instead of 404 (default: true)
347
- middleware: [authMiddleware], // Optional: Route-specific middleware
348
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
349
- methodOverride: 'get' // Optional: Override the HTTP method
350
- });
351
- ```
352
-
353
- #### `honey.updateById(options)`
354
-
355
- Creates a PUT endpoint for updating a resource by ID.
356
-
357
- ```typescript
358
- honey.updateById({
359
- resource: 'posts',
360
- params: {
361
- title: 'replace', // Replace the value
362
- views: 'inc', // Increment the value
363
- likes: 'dec', // Decrement the value
364
- updated_at: '@updatedAt' // Set to current timestamp
365
- },
366
- message: 'Post updated',
367
- idField: 'slug', // Optional: Use a different field as identifier
368
- filter: {
369
- // Optional: Additional WHERE conditions beyond the ID
370
- author_id: {
371
- operator: '=',
372
- value: 'number'
373
- }
374
- },
375
- middleware: [authMiddleware], // Optional: Route-specific middleware
376
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
377
- methodOverride: 'put' // Optional: Override the HTTP method
378
- });
379
- ```
380
-
381
- #### `honey.update(options)`
382
-
383
- Creates a PUT endpoint for updating multiple resources based on filters.
384
-
385
- ```typescript
386
- honey.update({
387
- resource: 'posts',
388
- params: {
389
- published: 'replace',
390
- updated_at: '@updatedAt'
391
- },
392
- filter: {
393
- // Required: Filter criteria
394
- author_id: {
395
- operator: '=',
396
- value: 'number'
397
- }
398
- },
399
- message: 'Posts updated',
400
- middleware: [authMiddleware], // Optional: Route-specific middleware
401
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
402
- methodOverride: 'put' // Optional: Override the HTTP method
403
- });
404
- ```
405
-
406
- > **Note:** `filter` is **required** for `honey.update()` to prevent unintended bulk updates.
407
-
408
- #### `honey.upsertById(options)`
409
-
410
- Creates a PUT endpoint for upserting a resource by ID.
411
-
412
- After an upsert, `req.isInsert` is set to `true` if the operation was an INSERT, or `false` if it was an UPDATE. This flag is accessible in `processResponseData` and `exitMiddleware`.
413
-
414
- ```typescript
415
- honey.upsertById({
416
- resource: 'posts',
417
- params: {
418
- title: 'replace',
419
- content: 'replace',
420
- updated_at: '@updatedAt'
421
- },
422
- message: 'Post upserted',
423
- idField: 'id', // Required: Field to use for conflict detection
424
- doNothingOnConflict: false, // Optional: When true, returns existing record unchanged on conflict (default: false)
425
- middleware: [authMiddleware], // Optional: Route-specific middleware
426
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
427
- methodOverride: 'put', // Optional: Override the HTTP method
428
- processResponseData: (data, req) => {
429
- // req.isInsert is true for INSERT, false for UPDATE
430
- return { ...data, wasInserted: req.isInsert };
431
- }
432
- });
433
- ```
434
-
435
- #### `honey.upsert(options)`
436
-
437
- Creates a PUT endpoint for upserting a resource with custom conflict resolution.
438
-
439
- After an upsert, `req.isInsert` is set to `true` if the operation was an INSERT, or `false` if it was an UPDATE. This flag is accessible in `processResponseData` and `exitMiddleware`.
440
-
441
- ```typescript
442
- honey.upsert({
443
- resource: 'posts',
444
- params: {
445
- title: 'replace',
446
- content: 'replace',
447
- slug: 'replace',
448
- updated_at: '@updatedAt'
449
- },
450
- message: 'Post upserted',
451
- conflictTarget: ['slug'], // Fields to check for conflicts
452
- doNothingOnConflict: false, // Optional: When true, returns existing record unchanged on conflict (default: false)
453
- middleware: [authMiddleware], // Optional: Route-specific middleware
454
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
455
- methodOverride: 'put' // Optional: Override the HTTP method
456
- });
457
- ```
458
-
459
- #### `honey.deleteById(options)`
460
-
461
- Creates a DELETE endpoint for deleting a resource by ID.
462
-
463
- ```typescript
464
- honey.deleteById({
465
- resource: 'posts',
466
- message: 'Post deleted',
467
- idField: 'id', // Optional: Field to use as identifier
468
- filter: {
469
- // Optional: Additional filters
470
- author_id: {
471
- operator: '=',
472
- value: 'number'
473
- }
474
- },
475
- middleware: [authMiddleware], // Optional: Route-specific middleware
476
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
477
- methodOverride: 'delete' // Optional: Override the HTTP method
478
- });
479
- ```
480
-
481
- #### `honey.delete(options)`
482
-
483
- Creates a DELETE endpoint for bulk-deleting resources (no ID in path).
484
-
485
- ```typescript
486
- honey.delete({
487
- resource: 'posts',
488
- filter: {
489
- // Filter criteria for records to delete
490
- author_id: {
491
- operator: '=',
492
- value: 'number'
493
- }
494
- },
495
- message: 'Posts deleted',
496
- table: 'blog_posts', // Optional: Table name if different from resource
497
- pathOverride: '/blog/posts', // Optional: Custom path
498
- middleware: [authMiddleware], // Optional: Route-specific middleware
499
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
500
- methodOverride: 'delete', // Optional: Override the HTTP method
501
- processErrorResponse: (err) => err // Optional: Customize error response
502
- });
503
- ```
504
-
505
- #### `honey.query(options)`
506
-
507
- Creates a custom endpoint backed by a raw Knex query. Useful for complex queries that go beyond standard CRUD operations.
508
-
509
- Automatically supports `?page=` and `?limit=` query parameters for pagination.
510
-
511
- ```typescript
512
- honey.query({
513
- resource: 'stats',
514
- methodOverride: 'get', // Optional: HTTP method (default: 'get')
515
- query: (knex, req) => {
516
- return knex('posts')
517
- .select('author_id')
518
- .count('id as post_count')
519
- .groupBy('author_id');
520
- },
521
- processResponseData: (data, req) => {
522
- return data;
523
- },
524
- pathOverride: '/stats/posts', // Optional: Custom path
525
- middleware: [authMiddleware], // Optional: Route-specific middleware
526
- exitMiddleware: [auditMiddleware], // Optional: Middleware that runs after response is sent
527
- processErrorResponse: (err) => err // Optional: Customize error response
528
- });
529
- ```
530
-
531
- **Options:**
532
-
533
- - `resource` — Resource name used in the URL path
534
- - `query` — Function receiving `(knex, req)` and returning a Knex `QueryBuilder`
535
- - `pathOverride` — Optional custom path
536
- - `methodOverride` — HTTP method (default: `'get'`)
537
- - `middleware` — Route-specific middleware
538
- - `exitMiddleware` — Middleware that runs after the response is sent
539
- - `processResponseData` — Transform the response data
540
- - `processErrorResponse` — Customize the error response
541
-
542
- ### Advanced Usage
543
-
544
- #### Custom Response Processing
545
-
546
- ```typescript
547
- honey.getById({
548
- resource: 'users',
549
- fields: ['id', 'name', 'email', 'role_id'],
550
- processResponseData: async (data, req) => {
551
- // Fetch related data
552
- const role = await fetchRoleById(data.role_id);
553
-
554
- // Transform response
555
- return {
556
- ...data,
557
- role: role.name,
558
- _links: {
559
- self: `/api/users/${data.id}`,
560
- posts: `/api/users/${data.id}/posts`
561
- }
562
- };
563
- }
564
- });
565
- ```
566
-
567
- #### Custom Error Handling
568
-
569
- ```typescript
570
- honey.create({
571
- resource: 'users',
572
- params: {
573
- email: 'string',
574
- password: 'string'
575
- },
576
- message: 'User created',
577
- processErrorResponse: (err) => {
578
- // Customize error response
579
- if (err.message.includes('duplicate key')) {
580
- return new HttpError('Email already exists', 409);
581
- }
582
- return err;
583
- }
584
- });
585
- ```
586
-
587
- ## Database Utilities
588
-
589
- HoneyJS provides several utilities for working directly with the database.
590
-
591
- ### Sequelize Model Definition
592
-
593
- ```typescript
594
- import { defineModel, DataTypes } from '@promind/honey';
595
-
596
- const User = defineModel(
597
- 'users',
598
- {
599
- id: {
600
- type: DataTypes.UUID,
601
- defaultValue: DataTypes.UUIDV4,
602
- primaryKey: true
603
- },
604
- name: {
605
- type: DataTypes.STRING,
606
- allowNull: false
607
- },
608
- email: {
609
- type: DataTypes.STRING,
610
- allowNull: false,
611
- unique: true
612
- },
613
- created_at: {
614
- type: DataTypes.DATE,
615
- defaultValue: DataTypes.NOW
616
- }
617
- },
618
- {
619
- // Optional: Sequelize ModelOptions
620
- timestamps: false,
621
- tableName: 'app_users'
622
- }
623
- );
624
-
625
- export default User;
626
- ```
627
-
628
- ### Raw Query Execution
629
-
630
- ```typescript
631
- import { runDbQuery, QueryTypes } from '@promind/honey';
632
-
633
- async function getUsersWithPosts() {
634
- const query = `
635
- SELECT u.id, u.name, COUNT(p.id) as post_count
636
- FROM users u
637
- LEFT JOIN posts p ON u.id = p.author_id
638
- GROUP BY u.id, u.name
639
- `;
640
-
641
- return runDbQuery(query, { type: QueryTypes.SELECT });
642
- }
643
- ```
644
-
645
- ### `createReqTransit`
646
-
647
- `createReqTransit` creates a typed transit object for safely passing data between middleware and controllers via the request object.
648
-
649
- ```typescript
650
- import { createReqTransit } from '@promind/honey';
651
-
652
- // Create a typed transit for passing data between middleware and controllers
653
- const userTransit = createReqTransit<User>('currentUser');
654
-
655
- // In middleware
656
- const authMiddleware = (req, res, next) => {
657
- const user = verifyToken(req.headers.authorization);
658
- userTransit.inject(req, user);
659
- next();
660
- };
661
-
662
- // In processResponseData or exitMiddleware
663
- honey.getById({
664
- resource: 'posts',
665
- fields: ['id', 'title'],
666
- middleware: [authMiddleware],
667
- processResponseData: (data, req) => {
668
- const user = userTransit.extract(req, null);
669
- return { ...data, viewedBy: user?.name };
670
- }
671
- });
672
- ```
673
-
674
- ## Error Handling
675
-
676
- HoneyJS provides a consistent error handling mechanism:
677
-
678
- ```typescript
679
- import { HttpError, handleHttpError } from '@promind/honey';
680
-
681
- // In your middleware or custom controller
682
- try {
683
- // Some operation
684
- if (!user) {
685
- throw new HttpError('User not found', 404);
686
- }
687
- } catch (error) {
688
- handleHttpError(error, res);
689
- }
690
- ```
691
-
692
- ## Middleware
693
-
694
- ### Request Validation
695
-
696
- HoneyJS includes built-in request validation using Joi:
697
-
698
- ```typescript
699
- import { validateRequestData } from '@promind/honey';
700
- import Joi from 'joi';
701
-
702
- const userSchema = Joi.object({
703
- name: Joi.string().required(),
704
- email: Joi.string().email().required(),
705
- age: Joi.number().integer().min(18)
706
- });
707
-
708
- honey.create({
709
- resource: 'users',
710
- params: {
711
- name: 'string',
712
- email: 'string',
713
- age: 'number'
714
- },
715
- message: 'User created',
716
- middleware: [validateRequestData(userSchema)]
717
- });
718
- ```
719
-
720
- `validateRequestData` accepts the following arguments:
721
-
722
- ```typescript
723
- validateRequestData(
724
- schema, // Joi ObjectSchema
725
- location?, // 'body' | 'params' | 'query' | 'headers' | 'cookies' (default: 'body')
726
- options? // Joi ValidationOptions (default: { allowUnknown: true })
727
- )
728
- ```
729
-
730
- ### Custom Middleware
731
-
732
- ```typescript
733
- import { Middleware } from '@promind/honey';
734
-
735
- const authMiddleware: Middleware = (req, res, next) => {
736
- const token = req.headers.authorization?.split(' ')[1];
737
-
738
- if (!token) {
739
- return res.status(401).json({ message: 'Authentication required' });
740
- }
741
-
742
- try {
743
- // Verify token and attach user to request
744
- req.user = verifyToken(token);
745
- next();
746
- } catch (error) {
747
- res.status(401).json({ message: 'Invalid token' });
748
- }
749
- };
750
-
751
- honey.addMiddleware([authMiddleware]);
752
- ```
753
-
754
- ## Examples
755
-
756
- ### Complete API Example
757
-
758
- ```typescript
759
- import { createHoney, HttpError } from '@promind/honey';
760
-
761
- const honey = createHoney(
762
- '3000',
763
- 'postgresql://postgres:password@localhost:5432/blog'
764
- );
765
-
766
- // Authentication middleware
767
- const authMiddleware = (req, res, next) => {
768
- // Implementation omitted for brevity
769
- };
770
-
771
- // Posts API
772
- honey.get({
773
- resource: 'posts',
774
- fields: ['id', 'title', 'excerpt', 'author_id', 'created_at'],
775
- filter: {
776
- title: { operator: 'like', value: 'string' },
777
- author_id: { operator: '=', value: 'number' }
778
- },
779
- format: { sort: 'DESC', sortField: 'created_at' }
780
- });
781
-
782
- honey.getById({
783
- resource: 'posts',
784
- fields: ['id', 'title', 'content', 'author_id', 'created_at', 'updated_at'],
785
- processResponseData: async (data, req) => {
786
- // Fetch author details
787
- const author = await fetchAuthor(data.author_id);
788
- return { ...data, author };
789
- }
790
- });
791
-
792
- honey.create({
793
- resource: 'posts',
794
- params: {
795
- title: 'string',
796
- content: 'string',
797
- excerpt: 'string',
798
- author_id: 'number',
799
- created_at: '@updatedAt'
800
- },
801
- message: 'Post created successfully',
802
- middleware: [authMiddleware]
803
- });
804
-
805
- honey.updateById({
806
- resource: 'posts',
807
- params: {
808
- title: 'replace',
809
- content: 'replace',
810
- excerpt: 'replace',
811
- updated_at: '@updatedAt'
812
- },
813
- message: 'Post updated successfully',
814
- middleware: [authMiddleware]
815
- });
816
-
817
- honey.deleteById({
818
- resource: 'posts',
819
- message: 'Post deleted successfully',
820
- middleware: [authMiddleware]
821
- });
822
-
823
- // Start the server
824
- honey.startServer();
825
- ```
826
-
827
- ### Custom Route and Controller
828
-
829
- ```typescript
830
- import { createHoney } from '@promind/honey';
831
- import express from 'express';
832
-
833
- const honey = createHoney(
834
- '3000',
835
- 'postgresql://postgres:password@localhost:5432/app'
836
- );
837
-
838
- // Access the Express router
839
- const router = honey.routes;
840
-
841
- // Add a custom route
842
- router.get('/dashboard', (req, res) => {
843
- res.json({ status: 'ok', message: 'Welcome to the dashboard' });
844
- });
845
-
846
- // Start the server
847
- honey.startServer();
848
- ```
849
-
850
- ## Custom Express Routes
851
-
852
- While HoneyJS provides declarative methods for common CRUD operations, you can also access the underlying Express router to create custom routes with full flexibility.
853
-
854
- ### Accessing the Express Router
855
-
856
- The Express router is exposed through the `routes` property of your Honey instance:
857
-
858
- ```typescript
859
- const honey = createHoney(
860
- '3000',
861
- 'postgresql://postgres:password@localhost:5432/app'
862
- );
863
- const router = honey.routes;
864
- ```
865
-
866
- ### Adding Custom Routes
867
-
868
- You can add any standard Express route to the router:
869
-
870
- ```typescript
871
- honey.routes.get('/client/sessions', async (req, res) => {
872
- res.send({ message: 'Hello Express' });
873
- });
874
-
875
- honey.routes.post('/auth/login', async (req, res) => {
876
- const { username, password } = req.body;
877
-
878
- // Custom authentication logic
879
- const user = await authenticateUser(username, password);
880
-
881
- if (!user) {
882
- return res.status(401).send({ message: 'Invalid credentials' });
883
- }
884
-
885
- res.send({
886
- token: generateToken(user),
887
- user: { id: user.id, name: user.name }
888
- });
889
- });
890
- ```
891
-
892
- ### Combining HoneyJS and Custom Routes
893
-
894
- You can mix declarative HoneyJS endpoints with custom Express routes in the same application:
895
-
896
- ```typescript
897
- import { createHoney } from '@promind/honey';
898
-
899
- const honey = createHoney(
900
- '3000',
901
- 'postgresql://postgres:password@localhost:5432/app'
902
- );
903
-
904
- // HoneyJS declarative endpoints
905
- honey.get({
906
- resource: 'products',
907
- fields: ['id', 'name', 'price', 'category']
908
- });
909
-
910
- honey.create({
911
- resource: 'products',
912
- params: {
913
- name: 'string',
914
- price: 'number',
915
- category: 'string'
916
- },
917
- message: 'Product created'
918
- });
919
-
920
- // Custom Express routes
921
- honey.routes.get('/stats/dashboard', async (req, res) => {
922
- const stats = await calculateDashboardStats();
923
- res.send(stats);
924
- });
925
-
926
- honey.routes.post('/batch/import', async (req, res) => {
927
- try {
928
- const results = await importDataFromCSV(req.body.fileUrl);
929
- res.send({ message: 'Import successful', results });
930
- } catch (error) {
931
- res.status(500).send({ message: 'Import failed', error: error.message });
932
- }
933
- });
934
-
935
- // Start the server
936
- honey.startServer();
937
- ```
938
-
939
- ### Using Express Middleware
940
-
941
- You can use any Express middleware with your custom routes:
942
-
943
- ```typescript
944
- import multer from 'multer';
945
-
946
- // Configure multer for file uploads
947
- const upload = multer({ dest: 'uploads/' });
948
-
949
- // File upload route with multer middleware
950
- honey.routes.post('/uploads/image', upload.single('image'), (req, res) => {
951
- res.send({
952
- message: 'File uploaded successfully',
953
- file: req.file
954
- });
955
- });
956
-
957
- // Route with multiple middleware functions
958
- honey.routes.get(
959
- '/admin/reports',
960
- checkAdminAuth,
961
- validateReportParams,
962
- async (req, res) => {
963
- const report = await generateReport(req.query.type);
964
- res.send(report);
965
- }
966
- );
967
- ```
968
-
969
- ## TypeScript Support
970
-
971
- HoneyJS is built with TypeScript and provides comprehensive type definitions:
972
-
973
- ```typescript
974
- import { createHoney, Middleware, HttpError } from '@promind/honey';
975
-
976
- // Type-safe middleware
977
- const loggerMiddleware: Middleware = (req, res, next) => {
978
- console.log(`${req.method} ${req.path}`);
979
- next();
980
- };
981
-
982
- // Type-safe error handling
983
- const errorHandler = (err: Error) => {
984
- if (err.message.includes('constraint')) {
985
- return new HttpError('Data validation failed', 422);
986
- }
987
- return err;
988
- };
989
-
990
- const honey = createHoney('3000', 'postgresql://localhost:5432/app');
991
-
992
- honey.create({
993
- resource: 'products',
994
- params: {
995
- name: 'string',
996
- price: 'number',
997
- in_stock: 'boolean'
998
- },
999
- message: 'Product created',
1000
- middleware: [loggerMiddleware],
1001
- processErrorResponse: errorHandler
1002
- });
1003
- ```
1004
-
1005
- ### `ExitMiddleware`
1006
-
1007
- `ExitMiddleware` is middleware that runs after the response has been sent. It receives the response data as its first argument, making it ideal for audit logging, analytics, or post-response side effects.
1008
-
1009
- ```typescript
1010
- import { ExitMiddleware } from '@promind/honey';
1011
-
1012
- const auditMiddleware: ExitMiddleware = (data, req, res, next) => {
1013
- console.log(`Response sent for ${req.method} ${req.path}:`, data);
1014
- next();
1015
- };
1016
-
1017
- honey.create({
1018
- resource: 'users',
1019
- params: { name: 'string', email: 'string' },
1020
- message: 'User created',
1021
- exitMiddleware: [auditMiddleware]
1022
- });
1023
- ```
1024
-
1025
- ## Contributing
1026
-
1027
- Contributions are welcome! Please feel free to submit a Pull Request.
1028
-
1029
- ## License
1030
-
1031
- This project is licensed under the MIT License - see the LICENSE file for details.
1032
-
1033
- ---
1034
-
1035
- ## Demo and Examples
1036
-
1037
- For a complete working example, check out the [honey-example](https://github.com/chinaza/honey-example) repository.
1038
-
1039
- ## Support
1040
-
1041
- If you encounter any issues or have questions, please [open an issue](https://github.com/chinaza/honey/issues) on GitHub.