@promind/honey 1.34.5

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/README.md ADDED
@@ -0,0 +1,795 @@
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
+ - [API Reference](#api-reference)
13
+ - [Core Methods](#core-methods)
14
+ - [CRUD Operations](#crud-operations)
15
+ - [Advanced Usage](#advanced-usage)
16
+ - [Database Utilities](#database-utilities)
17
+ - [Error Handling](#error-handling)
18
+ - [Middleware](#middleware)
19
+ - [Examples](#examples)
20
+ - [Custom Express Routes](#custom-express-routes)
21
+ - [TypeScript Support](#typescript-support)
22
+ - [Contributing](#contributing)
23
+ - [License](#license)
24
+
25
+ ## Features
26
+
27
+ - **Declarative API Definition**: Define your REST endpoints with simple configuration objects
28
+ - **Full CRUD Support**: Built-in controllers for create, read, update, delete, and upsert operations
29
+ - **PostgreSQL Integration**: Seamless connection to PostgreSQL via Sequelize ORM
30
+ - **Query Building**: Flexible filtering, sorting, and pagination
31
+ - **Middleware Support**: Add custom middleware to your routes
32
+ - **TypeScript Ready**: Full TypeScript support with comprehensive type definitions
33
+ - **Validation**: Request validation using Joi
34
+ - **Error Handling**: Consistent error handling and response formatting
35
+ - **Swagger Documentation**: Automatic OpenAPI documentation generation
36
+
37
+ ## Prerequisites
38
+
39
+ - Node.js (>=20.9.0)
40
+ - PostgreSQL database
41
+ - TypeScript (for TypeScript projects)
42
+ - Create the relevant tables and data models for your Postgres database. You can use the exposed sequelize utility functions and migrations to do this.
43
+ - See <https://github.com/chinaza/generator-honeyjs> to generate a project
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ # Using npm
49
+ npm install @promind/honey
50
+
51
+ # Using yarn
52
+ yarn add @promind/honey
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ 1. Create a new file (e.g., `server.ts` or `server.js`)
58
+
59
+ ```typescript
60
+ import { createHoney } from '@promind/honey';
61
+
62
+ // Initialize HoneyJS with port and database connection string
63
+ const honey = createHoney(
64
+ '3000',
65
+ 'postgresql://username:password@localhost:5432/database'
66
+ );
67
+
68
+ // Define a simple CRUD API for 'users' resource
69
+ honey.get({
70
+ resource: 'users',
71
+ fields: ['id', 'name', 'email', 'created_at']
72
+ });
73
+
74
+ honey.getById({
75
+ resource: 'users',
76
+ fields: ['id', 'name', 'email', 'created_at']
77
+ });
78
+
79
+ honey.create({
80
+ resource: 'users',
81
+ params: {
82
+ name: 'string',
83
+ email: 'string',
84
+ created_at: '@updatedAt'
85
+ },
86
+ message: 'User created successfully'
87
+ });
88
+
89
+ honey.updateById({
90
+ resource: 'users',
91
+ params: {
92
+ name: 'replace',
93
+ email: 'replace'
94
+ },
95
+ message: 'User updated successfully'
96
+ });
97
+
98
+ honey.deleteById({
99
+ resource: 'users',
100
+ message: 'User deleted successfully'
101
+ });
102
+
103
+ // Start the server
104
+ honey.startServer();
105
+ ```
106
+
107
+ 2. Run your server:
108
+
109
+ ```bash
110
+ # If using TypeScript
111
+ npx ts-node server.ts
112
+
113
+ # If using JavaScript
114
+ node server.js
115
+ ```
116
+
117
+ Your API will be available at `http://localhost:3000/api/users`
118
+
119
+ ## Configuration
120
+
121
+ ### Database Connection
122
+
123
+ You can connect to your PostgreSQL database using either a connection string or a configuration object:
124
+
125
+ ```typescript
126
+ // Using connection string
127
+ const honey = createHoney(
128
+ '3000',
129
+ 'postgresql://username:password@localhost:5432/database'
130
+ );
131
+
132
+ // Using configuration object
133
+ const honey = createHoney('3000', {
134
+ host: 'localhost',
135
+ port: 5432,
136
+ user: 'username',
137
+ password: 'password',
138
+ database: 'database'
139
+ });
140
+ ```
141
+
142
+ ### Environment Variables
143
+
144
+ HoneyJS supports configuration through environment variables. Create a `.env` file in your project root:
145
+
146
+ ```
147
+ PORT=3000
148
+ DB_URI=postgresql://username:password@localhost:5432/database
149
+ ```
150
+
151
+ Then initialize HoneyJS without parameters:
152
+
153
+ ```typescript
154
+ import { createHoney } from '@promind/honey';
155
+
156
+ const honey = createHoney(process.env.PORT, process.env.DB_URI);
157
+ ```
158
+
159
+ ## API Reference
160
+
161
+ ### Core Methods
162
+
163
+ #### `createHoney(port, dbOptions, metadata?)`
164
+
165
+ Creates a new HoneyJS instance.
166
+
167
+ - `port`: Server port number or string
168
+ - `dbOptions`: PostgreSQL connection string or configuration object
169
+ - `metadata` (optional): Additional configuration options
170
+ - `fallbackErrorMessage`: Custom 404 error message
171
+ - `routePrefix`: API route prefix (default: `/api`)
172
+
173
+ Returns a `Honey` instance.
174
+
175
+ #### `honey.startServer()`
176
+
177
+ Starts the HTTP server.
178
+
179
+ #### `honey.addMiddleware(middleware[])`
180
+
181
+ Adds global middleware to all routes.
182
+
183
+ ### CRUD Operations
184
+
185
+ #### `honey.create(options)`
186
+
187
+ Creates a POST endpoint for creating new resources.
188
+
189
+ ```typescript
190
+ honey.create({
191
+ resource: 'posts', // Resource name (used in URL path)
192
+ table: 'blog_posts', // Optional: Table name if different from resource
193
+ params: {
194
+ // Request body parameters
195
+ title: 'string', // Parameter type validation
196
+ content: 'string',
197
+ author_id: 'number',
198
+ published: 'boolean',
199
+ metadata: 'json', // For JSON fields
200
+ created_at: '@updatedAt' // Special value to set current timestamp
201
+ },
202
+ message: 'Post created', // Success message
203
+ pathOverride: '/blog/posts', // Optional: Custom path
204
+ middleware: [authMiddleware], // Optional: Route-specific middleware
205
+ processResponseData: (data, req) => {
206
+ // Optional: Transform response data
207
+ return { ...data, extra: 'info' };
208
+ }
209
+ });
210
+ ```
211
+
212
+ #### `honey.get(options)`
213
+
214
+ Creates a GET endpoint for retrieving a list of resources.
215
+
216
+ ```typescript
217
+ honey.get({
218
+ resource: 'posts',
219
+ fields: ['id', 'title', 'author_id', 'created_at'], // Fields to return
220
+ filter: {
221
+ // Optional: Query filters
222
+ title: {
223
+ operator: 'like', // Filter operator
224
+ value: 'string' // Parameter type
225
+ },
226
+ published: {
227
+ operator: '=',
228
+ value: 'boolean'
229
+ },
230
+ $or: {
231
+ // Logical OR condition
232
+ title: {
233
+ operator: 'like',
234
+ value: 'string'
235
+ },
236
+ content: {
237
+ operator: 'like',
238
+ value: 'string'
239
+ }
240
+ }
241
+ },
242
+ format: {
243
+ // Optional: Sorting
244
+ sort: 'DESC', // ASC or DESC
245
+ sortField: 'created_at' // Field to sort by
246
+ }
247
+ });
248
+ ```
249
+
250
+ #### `honey.getById(options)`
251
+
252
+ Creates a GET endpoint for retrieving a single resource by ID.
253
+
254
+ ```typescript
255
+ honey.getById({
256
+ resource: 'posts',
257
+ fields: ['id', 'title', 'content', 'author_id', 'created_at'],
258
+ idField: 'slug', // Optional: Use a different field as identifier (default: 'id')
259
+ filter: {
260
+ // Optional: Additional filters
261
+ published: {
262
+ operator: '=',
263
+ overrideValue: true // Force a value regardless of request
264
+ }
265
+ }
266
+ });
267
+ ```
268
+
269
+ #### `honey.updateById(options)`
270
+
271
+ Creates a PUT endpoint for updating a resource by ID.
272
+
273
+ ```typescript
274
+ honey.updateById({
275
+ resource: 'posts',
276
+ params: {
277
+ title: 'replace', // Replace the value
278
+ views: 'inc', // Increment the value
279
+ likes: 'dec', // Decrement the value
280
+ updated_at: '@updatedAt' // Set to current timestamp
281
+ },
282
+ message: 'Post updated',
283
+ idField: 'slug' // Optional: Use a different field as identifier
284
+ });
285
+ ```
286
+
287
+ #### `honey.update(options)`
288
+
289
+ Creates a PUT endpoint for updating multiple resources based on filters.
290
+
291
+ ```typescript
292
+ honey.update({
293
+ resource: 'posts',
294
+ params: {
295
+ published: 'replace',
296
+ updated_at: '@updatedAt'
297
+ },
298
+ filter: {
299
+ // Required: Filter criteria
300
+ author_id: {
301
+ operator: '=',
302
+ value: 'number'
303
+ }
304
+ },
305
+ message: 'Posts updated'
306
+ });
307
+ ```
308
+
309
+ #### `honey.upsertById(options)`
310
+
311
+ Creates a PUT endpoint for upserting a resource by ID.
312
+
313
+ ```typescript
314
+ honey.upsertById({
315
+ resource: 'posts',
316
+ params: {
317
+ title: 'replace',
318
+ content: 'replace',
319
+ updated_at: '@updatedAt'
320
+ },
321
+ message: 'Post upserted',
322
+ idField: 'id' // Field to use for conflict detection
323
+ });
324
+ ```
325
+
326
+ #### `honey.upsert(options)`
327
+
328
+ Creates a PUT endpoint for upserting a resource with custom conflict resolution.
329
+
330
+ ```typescript
331
+ honey.upsert({
332
+ resource: 'posts',
333
+ params: {
334
+ title: 'replace',
335
+ content: 'replace',
336
+ slug: 'replace',
337
+ updated_at: '@updatedAt'
338
+ },
339
+ message: 'Post upserted',
340
+ conflictTarget: ['slug'] // Fields to check for conflicts
341
+ });
342
+ ```
343
+
344
+ #### `honey.deleteById(options)`
345
+
346
+ Creates a DELETE endpoint for deleting a resource by ID.
347
+
348
+ ```typescript
349
+ honey.deleteById({
350
+ resource: 'posts',
351
+ message: 'Post deleted',
352
+ idField: 'id', // Optional: Field to use as identifier
353
+ filter: {
354
+ // Optional: Additional filters
355
+ author_id: {
356
+ operator: '=',
357
+ value: 'number'
358
+ }
359
+ }
360
+ });
361
+ ```
362
+
363
+ ### Advanced Usage
364
+
365
+ #### Custom Response Processing
366
+
367
+ ```typescript
368
+ honey.getById({
369
+ resource: 'users',
370
+ fields: ['id', 'name', 'email', 'role_id'],
371
+ processResponseData: async (data, req) => {
372
+ // Fetch related data
373
+ const role = await fetchRoleById(data.role_id);
374
+
375
+ // Transform response
376
+ return {
377
+ ...data,
378
+ role: role.name,
379
+ _links: {
380
+ self: `/api/users/${data.id}`,
381
+ posts: `/api/users/${data.id}/posts`
382
+ }
383
+ };
384
+ }
385
+ });
386
+ ```
387
+
388
+ #### Custom Error Handling
389
+
390
+ ```typescript
391
+ honey.create({
392
+ resource: 'users',
393
+ params: {
394
+ email: 'string',
395
+ password: 'string'
396
+ },
397
+ message: 'User created',
398
+ processErrorResponse: (err) => {
399
+ // Customize error response
400
+ if (err.message.includes('duplicate key')) {
401
+ return new HttpError('Email already exists', 409);
402
+ }
403
+ return err;
404
+ }
405
+ });
406
+ ```
407
+
408
+ ## Database Utilities
409
+
410
+ HoneyJS provides several utilities for working directly with the database.
411
+
412
+ ### Sequelize Model Definition
413
+
414
+ ```typescript
415
+ import { defineModel, DataTypes } from '@promind/honey';
416
+
417
+ const User = defineModel('users', {
418
+ id: {
419
+ type: DataTypes.UUID,
420
+ defaultValue: DataTypes.UUIDV4,
421
+ primaryKey: true
422
+ },
423
+ name: {
424
+ type: DataTypes.STRING,
425
+ allowNull: false
426
+ },
427
+ email: {
428
+ type: DataTypes.STRING,
429
+ allowNull: false,
430
+ unique: true
431
+ },
432
+ created_at: {
433
+ type: DataTypes.DATE,
434
+ defaultValue: DataTypes.NOW
435
+ }
436
+ });
437
+
438
+ export default User;
439
+ ```
440
+
441
+ ### Raw Query Execution
442
+
443
+ ```typescript
444
+ import { runDbQuery, QueryTypes } from '@promind/honey';
445
+
446
+ async function getUsersWithPosts() {
447
+ const query = `
448
+ SELECT u.id, u.name, COUNT(p.id) as post_count
449
+ FROM users u
450
+ LEFT JOIN posts p ON u.id = p.author_id
451
+ GROUP BY u.id, u.name
452
+ `;
453
+
454
+ return runDbQuery(query, { type: QueryTypes.SELECT });
455
+ }
456
+ ```
457
+
458
+ ## Error Handling
459
+
460
+ HoneyJS provides a consistent error handling mechanism:
461
+
462
+ ```typescript
463
+ import { HttpError, handleHttpError } from '@promind/honey';
464
+
465
+ // In your middleware or custom controller
466
+ try {
467
+ // Some operation
468
+ if (!user) {
469
+ throw new HttpError('User not found', 404);
470
+ }
471
+ } catch (error) {
472
+ handleHttpError(error, res);
473
+ }
474
+ ```
475
+
476
+ ## Middleware
477
+
478
+ ### Request Validation
479
+
480
+ HoneyJS includes built-in request validation using Joi:
481
+
482
+ ```typescript
483
+ import { validateRequestData } from '@promind/honey';
484
+ import Joi from 'joi';
485
+
486
+ const userSchema = Joi.object({
487
+ name: Joi.string().required(),
488
+ email: Joi.string().email().required(),
489
+ age: Joi.number().integer().min(18)
490
+ });
491
+
492
+ honey.create({
493
+ resource: 'users',
494
+ params: {
495
+ name: 'string',
496
+ email: 'string',
497
+ age: 'number'
498
+ },
499
+ message: 'User created',
500
+ middleware: [validateRequestData(userSchema)]
501
+ });
502
+ ```
503
+
504
+ ### Custom Middleware
505
+
506
+ ```typescript
507
+ import { Middleware } from '@promind/honey';
508
+
509
+ const authMiddleware: Middleware = (req, res, next) => {
510
+ const token = req.headers.authorization?.split(' ')[1];
511
+
512
+ if (!token) {
513
+ return res.status(401).json({ message: 'Authentication required' });
514
+ }
515
+
516
+ try {
517
+ // Verify token and attach user to request
518
+ req.user = verifyToken(token);
519
+ next();
520
+ } catch (error) {
521
+ res.status(401).json({ message: 'Invalid token' });
522
+ }
523
+ };
524
+
525
+ honey.addMiddleware([authMiddleware]);
526
+ ```
527
+
528
+ ## Examples
529
+
530
+ ### Complete API Example
531
+
532
+ ```typescript
533
+ import { createHoney, HttpError } from '@promind/honey';
534
+
535
+ const honey = createHoney(
536
+ '3000',
537
+ 'postgresql://postgres:password@localhost:5432/blog'
538
+ );
539
+
540
+ // Authentication middleware
541
+ const authMiddleware = (req, res, next) => {
542
+ // Implementation omitted for brevity
543
+ };
544
+
545
+ // Posts API
546
+ honey.get({
547
+ resource: 'posts',
548
+ fields: ['id', 'title', 'excerpt', 'author_id', 'created_at'],
549
+ filter: {
550
+ title: { operator: 'like', value: 'string' },
551
+ author_id: { operator: '=', value: 'number' }
552
+ },
553
+ format: { sort: 'DESC', sortField: 'created_at' }
554
+ });
555
+
556
+ honey.getById({
557
+ resource: 'posts',
558
+ fields: ['id', 'title', 'content', 'author_id', 'created_at', 'updated_at'],
559
+ processResponseData: async (data, req) => {
560
+ // Fetch author details
561
+ const author = await fetchAuthor(data.author_id);
562
+ return { ...data, author };
563
+ }
564
+ });
565
+
566
+ honey.create({
567
+ resource: 'posts',
568
+ params: {
569
+ title: 'string',
570
+ content: 'string',
571
+ excerpt: 'string',
572
+ author_id: 'number',
573
+ created_at: '@updatedAt'
574
+ },
575
+ message: 'Post created successfully',
576
+ middleware: [authMiddleware]
577
+ });
578
+
579
+ honey.updateById({
580
+ resource: 'posts',
581
+ params: {
582
+ title: 'replace',
583
+ content: 'replace',
584
+ excerpt: 'replace',
585
+ updated_at: '@updatedAt'
586
+ },
587
+ message: 'Post updated successfully',
588
+ middleware: [authMiddleware]
589
+ });
590
+
591
+ honey.deleteById({
592
+ resource: 'posts',
593
+ message: 'Post deleted successfully',
594
+ middleware: [authMiddleware]
595
+ });
596
+
597
+ // Start the server
598
+ honey.startServer();
599
+ ```
600
+
601
+ ### Custom Route and Controller
602
+
603
+ ```typescript
604
+ import { createHoney } from '@promind/honey';
605
+ import express from 'express';
606
+
607
+ const honey = createHoney(
608
+ '3000',
609
+ 'postgresql://postgres:password@localhost:5432/app'
610
+ );
611
+
612
+ // Access the Express router
613
+ const router = honey.routes;
614
+
615
+ // Add a custom route
616
+ router.get('/dashboard', (req, res) => {
617
+ res.json({ status: 'ok', message: 'Welcome to the dashboard' });
618
+ });
619
+
620
+ // Start the server
621
+ honey.startServer();
622
+ ```
623
+
624
+ ## Custom Express Routes
625
+
626
+ While HoneyJS provides declarative methods for common CRUD operations, you can also access the underlying Express router to create custom routes with full flexibility.
627
+
628
+ ### Accessing the Express Router
629
+
630
+ The Express router is exposed through the `routes` property of your Honey instance:
631
+
632
+ ```typescript
633
+ const honey = createHoney(
634
+ '3000',
635
+ 'postgresql://postgres:password@localhost:5432/app'
636
+ );
637
+ const router = honey.routes;
638
+ ```
639
+
640
+ ### Adding Custom Routes
641
+
642
+ You can add any standard Express route to the router:
643
+
644
+ ```typescript
645
+ honey.routes.get('/client/sessions', async (req, res) => {
646
+ res.send({ message: 'Hello Express' });
647
+ });
648
+
649
+ honey.routes.post('/auth/login', async (req, res) => {
650
+ const { username, password } = req.body;
651
+
652
+ // Custom authentication logic
653
+ const user = await authenticateUser(username, password);
654
+
655
+ if (!user) {
656
+ return res.status(401).send({ message: 'Invalid credentials' });
657
+ }
658
+
659
+ res.send({
660
+ token: generateToken(user),
661
+ user: { id: user.id, name: user.name }
662
+ });
663
+ });
664
+ ```
665
+
666
+ ### Combining HoneyJS and Custom Routes
667
+
668
+ You can mix declarative HoneyJS endpoints with custom Express routes in the same application:
669
+
670
+ ```typescript
671
+ import { createHoney } from '@promind/honey';
672
+
673
+ const honey = createHoney(
674
+ '3000',
675
+ 'postgresql://postgres:password@localhost:5432/app'
676
+ );
677
+
678
+ // HoneyJS declarative endpoints
679
+ honey.get({
680
+ resource: 'products',
681
+ fields: ['id', 'name', 'price', 'category']
682
+ });
683
+
684
+ honey.create({
685
+ resource: 'products',
686
+ params: {
687
+ name: 'string',
688
+ price: 'number',
689
+ category: 'string'
690
+ },
691
+ message: 'Product created'
692
+ });
693
+
694
+ // Custom Express routes
695
+ honey.routes.get('/stats/dashboard', async (req, res) => {
696
+ const stats = await calculateDashboardStats();
697
+ res.send(stats);
698
+ });
699
+
700
+ honey.routes.post('/batch/import', async (req, res) => {
701
+ try {
702
+ const results = await importDataFromCSV(req.body.fileUrl);
703
+ res.send({ message: 'Import successful', results });
704
+ } catch (error) {
705
+ res.status(500).send({ message: 'Import failed', error: error.message });
706
+ }
707
+ });
708
+
709
+ // Start the server
710
+ honey.startServer();
711
+ ```
712
+
713
+ ### Using Express Middleware
714
+
715
+ You can use any Express middleware with your custom routes:
716
+
717
+ ```typescript
718
+ import multer from 'multer';
719
+
720
+ // Configure multer for file uploads
721
+ const upload = multer({ dest: 'uploads/' });
722
+
723
+ // File upload route with multer middleware
724
+ honey.routes.post('/uploads/image', upload.single('image'), (req, res) => {
725
+ res.send({
726
+ message: 'File uploaded successfully',
727
+ file: req.file
728
+ });
729
+ });
730
+
731
+ // Route with multiple middleware functions
732
+ honey.routes.get(
733
+ '/admin/reports',
734
+ checkAdminAuth,
735
+ validateReportParams,
736
+ async (req, res) => {
737
+ const report = await generateReport(req.query.type);
738
+ res.send(report);
739
+ }
740
+ );
741
+ ```
742
+
743
+ ## TypeScript Support
744
+
745
+ HoneyJS is built with TypeScript and provides comprehensive type definitions:
746
+
747
+ ```typescript
748
+ import { createHoney, Middleware, HttpError } from '@promind/honey';
749
+
750
+ // Type-safe middleware
751
+ const loggerMiddleware: Middleware = (req, res, next) => {
752
+ console.log(`${req.method} ${req.path}`);
753
+ next();
754
+ };
755
+
756
+ // Type-safe error handling
757
+ const errorHandler = (err: Error) => {
758
+ if (err.message.includes('constraint')) {
759
+ return new HttpError('Data validation failed', 422);
760
+ }
761
+ return err;
762
+ };
763
+
764
+ const honey = createHoney('3000', 'postgresql://localhost:5432/app');
765
+
766
+ honey.create({
767
+ resource: 'products',
768
+ params: {
769
+ name: 'string',
770
+ price: 'number',
771
+ in_stock: 'boolean'
772
+ },
773
+ message: 'Product created',
774
+ middleware: [loggerMiddleware],
775
+ processErrorResponse: errorHandler
776
+ });
777
+ ```
778
+
779
+ ## Contributing
780
+
781
+ Contributions are welcome! Please feel free to submit a Pull Request.
782
+
783
+ ## License
784
+
785
+ This project is licensed under the MIT License - see the LICENSE file for details.
786
+
787
+ ---
788
+
789
+ ## Demo and Examples
790
+
791
+ For a complete working example, check out the [honey-example](https://github.com/chinaza/honey-example) repository.
792
+
793
+ ## Support
794
+
795
+ If you encounter any issues or have questions, please [open an issue](https://github.com/chinaza/honey/issues) on GitHub.