rpc-express-toolkit 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/LICENSE +21 -0
- package/README.md +796 -0
- package/examples/example.js +41 -0
- package/examples/server-client-example.js +114 -0
- package/package.json +95 -0
- package/src/batch.js +237 -0
- package/src/clients/rpc-client.js +249 -0
- package/src/clients/rpc-client.min.js +1 -0
- package/src/clients/rpc-client.min.mjs +1 -0
- package/src/clients/rpc-client.mjs +275 -0
- package/src/index.d.ts +147 -0
- package/src/index.js +580 -0
- package/src/logger.js +153 -0
- package/src/middleware.js +224 -0
- package/src/validation.js +221 -0
package/README.md
ADDED
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
# RPC Express Toolkit
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://www.npmjs.com/package/rpc-express-toolkit)
|
|
5
|
+
[](https://github.com/n-car/rpc-express-toolkit/actions)
|
|
6
|
+
[](https://coveralls.io/github/n-car/rpc-express-toolkit?branch=main)
|
|
7
|
+
|
|
8
|
+
An enterprise-ready JSON-RPC 2.0 toolkit for Express.js applications with simplified APIs, structured logging, middleware system, schema validation, batch processing, and comprehensive BigInt/Date serialization support.
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Features](#features)
|
|
13
|
+
- [Installation](#installation)
|
|
14
|
+
- [Quick Start](#quick-start)
|
|
15
|
+
- [Advanced Usage](#advanced-usage)
|
|
16
|
+
- [Configuration Options](#configuration-options)
|
|
17
|
+
- [Schema Validation](#schema-validation)
|
|
18
|
+
- [Middleware System](#middleware-system)
|
|
19
|
+
- [Batch Requests](#batch-requests)
|
|
20
|
+
- [Structured Logging](#structured-logging)
|
|
21
|
+
- [Built-in Middlewares](#built-in-middlewares)
|
|
22
|
+
- [API Reference](#api-reference)
|
|
23
|
+
- [Examples](#examples)
|
|
24
|
+
- [Contributing](#contributing)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
### Core Features
|
|
30
|
+
- **JSON-RPC 2.0 Compliance:** Fully adheres to the JSON-RPC 2.0 specification
|
|
31
|
+
- **Server & Client Support:** Provides both server endpoint and client classes
|
|
32
|
+
- **Asynchronous Support:** Handle asynchronous operations seamlessly with Promises
|
|
33
|
+
- **BigInt & Date Serialization:** Robust serialization/deserialization with timezone support
|
|
34
|
+
- **Cross-Platform:** Works in both browser and Node.js environments
|
|
35
|
+
- **Error Handling:** Comprehensive error responses with sanitization options
|
|
36
|
+
|
|
37
|
+
### Enterprise Features
|
|
38
|
+
- **🔧 Structured Logging:** Configurable logging with multiple transports and levels
|
|
39
|
+
- **⚡ Middleware System:** Extensible middleware with built-in rate limiting, CORS, auth
|
|
40
|
+
- **✅ Schema Validation:** JSON Schema validation with Ajv and schema builder utilities
|
|
41
|
+
- **📦 Batch Processing:** Efficient batch request handling with concurrent processing
|
|
42
|
+
- **📊 Health & Metrics:** Built-in health checks and metrics endpoints
|
|
43
|
+
- **🔒 Security:** Method whitelisting, authentication, and error sanitization
|
|
44
|
+
- **🎯 Performance:** Request timing, caching support, and optimized serialization
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install rpc-express-toolkit
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or using yarn:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
yarn add rpc-express-toolkit
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Note:** For Node.js environments older than v18, you may need to install `node-fetch` for HTTP requests:
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
### Basic Setup
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
const express = require('express');
|
|
66
|
+
const { RpcEndpoint } = require('rpc-express-toolkit');
|
|
67
|
+
|
|
68
|
+
const app = express();
|
|
69
|
+
app.use(express.json());
|
|
70
|
+
|
|
71
|
+
// Context object to pass to method handlers
|
|
72
|
+
const context = { database: db, config: config };
|
|
73
|
+
|
|
74
|
+
// Create JSON-RPC server with clean API
|
|
75
|
+
const rpc = new RpcEndpoint(app, context);
|
|
76
|
+
|
|
77
|
+
// Add simple methods
|
|
78
|
+
rpc.addMethod('add', (req, ctx, params) => {
|
|
79
|
+
const { a, b } = params;
|
|
80
|
+
return a + b;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
rpc.addMethod('getUser', async (req, ctx, params) => {
|
|
84
|
+
const { userId } = params;
|
|
85
|
+
const user = await ctx.database.users.findById(userId);
|
|
86
|
+
return user;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
app.listen(3000, () => {
|
|
90
|
+
console.log('Server running on port 3000');
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Client Usage
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
const { RpcClient } = require('rpc-express-toolkit');
|
|
98
|
+
|
|
99
|
+
// Node.js client
|
|
100
|
+
const client = new RpcClient('http://localhost:3000/api');
|
|
101
|
+
|
|
102
|
+
// Make RPC calls
|
|
103
|
+
const result = await client.call('add', { a: 5, b: 3 });
|
|
104
|
+
console.log(result); // 8
|
|
105
|
+
|
|
106
|
+
// Browser client (include the client script)
|
|
107
|
+
const client = new RpcClient('/api');
|
|
108
|
+
const user = await client.call('getUser', { userId: 123 });
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Advanced Usage
|
|
112
|
+
|
|
113
|
+
### Configuration Options
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
const rpc = new RpcEndpoint(app, context, {
|
|
117
|
+
endpoint: '/api',
|
|
118
|
+
|
|
119
|
+
// Logging configuration
|
|
120
|
+
logging: {
|
|
121
|
+
level: 'info',
|
|
122
|
+
format: 'json',
|
|
123
|
+
transports: ['console', 'file'],
|
|
124
|
+
file: {
|
|
125
|
+
filename: 'rpc.log',
|
|
126
|
+
maxsize: 10485760, // 10MB
|
|
127
|
+
maxFiles: 5
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Built-in middleware
|
|
132
|
+
cors: {
|
|
133
|
+
origin: ['http://localhost:3000'],
|
|
134
|
+
credentials: true
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
rateLimit: {
|
|
138
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
139
|
+
max: 100 // limit each IP to 100 requests per windowMs
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
auth: {
|
|
143
|
+
required: true,
|
|
144
|
+
verify: (req) => {
|
|
145
|
+
const token = req.headers.authorization;
|
|
146
|
+
return verifyToken(token);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
methodWhitelist: ['add', 'subtract', 'getUser'],
|
|
151
|
+
|
|
152
|
+
// Schema validation
|
|
153
|
+
validation: {
|
|
154
|
+
strict: true,
|
|
155
|
+
coerceTypes: true,
|
|
156
|
+
removeAdditional: true
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Feature flags
|
|
160
|
+
healthCheck: true,
|
|
161
|
+
metrics: true,
|
|
162
|
+
timing: true
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Schema Validation
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
const { SchemaBuilder, commonSchemas } = require('rpc-express-toolkit');
|
|
170
|
+
|
|
171
|
+
// Using schema builder
|
|
172
|
+
const addSchema = SchemaBuilder.object({
|
|
173
|
+
a: SchemaBuilder.number().required(),
|
|
174
|
+
b: SchemaBuilder.number().required()
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Register method with schema
|
|
178
|
+
rpc.addMethod('add', {
|
|
179
|
+
handler: (req, ctx, params) => params.a + params.b,
|
|
180
|
+
schema: addSchema
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Using common schemas
|
|
184
|
+
rpc.addMethod('getUser', {
|
|
185
|
+
handler: async (req, ctx, params) => {
|
|
186
|
+
return await ctx.database.users.findById(params.userId);
|
|
187
|
+
},
|
|
188
|
+
schema: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
properties: {
|
|
191
|
+
userId: commonSchemas.positiveInteger
|
|
192
|
+
},
|
|
193
|
+
required: ['userId']
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Middleware System
|
|
199
|
+
|
|
200
|
+
## API Reference
|
|
201
|
+
|
|
202
|
+
### RpcEndpoint Class
|
|
203
|
+
|
|
204
|
+
#### Constructor
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
new RpcEndpoint(router, context, options)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Parameters:**
|
|
211
|
+
- `router` (Express.Router): Express router instance
|
|
212
|
+
- `context` (Object): Context object passed to method handlers
|
|
213
|
+
- `options` (Object|string): Configuration options or endpoint path
|
|
214
|
+
|
|
215
|
+
**Options:**
|
|
216
|
+
- `endpoint` (string): Endpoint path (default: '/api')
|
|
217
|
+
- `logging` (Object): Logging configuration
|
|
218
|
+
- `cors` (Object): CORS configuration
|
|
219
|
+
- `rateLimit` (Object): Rate limiting configuration
|
|
220
|
+
- `auth` (Object): Authentication configuration
|
|
221
|
+
- `methodWhitelist` (Array): Allowed method names
|
|
222
|
+
- `validation` (Object): Schema validation options
|
|
223
|
+
- `healthCheck` (boolean): Enable health check endpoint
|
|
224
|
+
- `metrics` (boolean): Enable metrics endpoint
|
|
225
|
+
- `timing` (boolean): Enable request timing
|
|
226
|
+
|
|
227
|
+
#### Methods
|
|
228
|
+
|
|
229
|
+
##### `addMethod(name, handlerOrConfig, schema)`
|
|
230
|
+
|
|
231
|
+
Register a new JSON-RPC method.
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
// Simple method
|
|
235
|
+
rpc.addMethod('add', (req, ctx, params) => {
|
|
236
|
+
return params.a + params.b;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Method with schema validation
|
|
240
|
+
rpc.addMethod('getUser', {
|
|
241
|
+
handler: async (req, ctx, params) => {
|
|
242
|
+
return await ctx.db.users.findById(params.userId);
|
|
243
|
+
},
|
|
244
|
+
schema: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
properties: {
|
|
247
|
+
userId: { type: 'number', minimum: 1 }
|
|
248
|
+
},
|
|
249
|
+
required: ['userId']
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
##### `use(hook, middleware)`
|
|
255
|
+
|
|
256
|
+
Add middleware for specific execution hooks.
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
rpc.use('beforeCall', async (context) => {
|
|
260
|
+
// Pre-processing
|
|
261
|
+
return context;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
rpc.use('afterCall', async (context) => {
|
|
265
|
+
// Post-processing
|
|
266
|
+
return context;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
rpc.use('onError', async (context) => {
|
|
270
|
+
// Error handling
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
##### `removeMethod(name)`
|
|
275
|
+
|
|
276
|
+
Remove a registered method.
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
rpc.removeMethod('methodName');
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
##### `getMethod(name)`
|
|
283
|
+
|
|
284
|
+
Get method configuration.
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
const config = rpc.getMethod('methodName');
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
##### `listMethods()`
|
|
291
|
+
|
|
292
|
+
List all registered methods.
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
const methods = rpc.listMethods();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
##### `getMetrics()`
|
|
299
|
+
|
|
300
|
+
Get current metrics data.
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
const metrics = rpc.getMetrics();
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### Properties
|
|
307
|
+
|
|
308
|
+
- `endpoint`: The endpoint path
|
|
309
|
+
- `methods`: All registered methods
|
|
310
|
+
- `logger`: Logger instance
|
|
311
|
+
- `middleware`: Middleware manager
|
|
312
|
+
- `validator`: Schema validator
|
|
313
|
+
|
|
314
|
+
### RpcClient Class
|
|
315
|
+
|
|
316
|
+
#### Constructor
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
new RpcClient(baseUrl, options)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Methods
|
|
323
|
+
|
|
324
|
+
##### `call(method, params, options)`
|
|
325
|
+
|
|
326
|
+
Make a single RPC call.
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
const result = await client.call('add', { a: 1, b: 2 });
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
##### `batch(requests)`
|
|
333
|
+
|
|
334
|
+
Make batch RPC calls.
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
const results = await client.batch([
|
|
338
|
+
{ method: 'add', params: { a: 1, b: 2 } },
|
|
339
|
+
{ method: 'subtract', params: { a: 5, b: 3 } }
|
|
340
|
+
]);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
##### `notify(method, params)`
|
|
344
|
+
|
|
345
|
+
Send notification (no response expected).
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
await client.notify('logEvent', { event: 'user_login' });
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Logger Class
|
|
352
|
+
|
|
353
|
+
Structured logging with configurable transports and levels.
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
const logger = new Logger({
|
|
357
|
+
level: 'info',
|
|
358
|
+
format: 'json',
|
|
359
|
+
transports: ['console', 'file']
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
logger.info('Message', { key: 'value' });
|
|
363
|
+
logger.error('Error', { error: err.message });
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### SchemaValidator Class
|
|
367
|
+
|
|
368
|
+
JSON Schema validation with Ajv.
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
const validator = new SchemaValidator({
|
|
372
|
+
strict: true,
|
|
373
|
+
coerceTypes: true
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const result = validator.validate(data, schema);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### SchemaBuilder
|
|
380
|
+
|
|
381
|
+
Utility for building JSON schemas.
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
const schema = SchemaBuilder.object({
|
|
385
|
+
name: SchemaBuilder.string().required(),
|
|
386
|
+
age: SchemaBuilder.number().min(0).max(150),
|
|
387
|
+
email: SchemaBuilder.string().format('email')
|
|
388
|
+
});
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### BatchHandler
|
|
392
|
+
|
|
393
|
+
Handles batch request processing with concurrency control.
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
const batchHandler = new BatchHandler(endpoint, {
|
|
397
|
+
maxConcurrency: 10,
|
|
398
|
+
timeout: 30000
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Examples
|
|
403
|
+
|
|
404
|
+
### Enterprise Configuration
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
const express = require('express');
|
|
408
|
+
const { RpcEndpoint, SchemaBuilder } = require('rpc-express-toolkit');
|
|
409
|
+
|
|
410
|
+
const app = express();
|
|
411
|
+
app.use(express.json());
|
|
412
|
+
|
|
413
|
+
const rpc = new RpcEndpoint(app, { db, config }, {
|
|
414
|
+
endpoint: '/api/v1',
|
|
415
|
+
logging: {
|
|
416
|
+
level: 'info',
|
|
417
|
+
format: 'json',
|
|
418
|
+
transports: ['console', 'file'],
|
|
419
|
+
file: { filename: 'api.log' }
|
|
420
|
+
},
|
|
421
|
+
cors: {
|
|
422
|
+
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
|
|
423
|
+
credentials: true
|
|
424
|
+
},
|
|
425
|
+
rateLimit: {
|
|
426
|
+
windowMs: 15 * 60 * 1000,
|
|
427
|
+
max: 100
|
|
428
|
+
},
|
|
429
|
+
auth: {
|
|
430
|
+
required: true,
|
|
431
|
+
verify: async (req) => {
|
|
432
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
433
|
+
return await verifyJWT(token);
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
validation: {
|
|
437
|
+
strict: true,
|
|
438
|
+
coerceTypes: true
|
|
439
|
+
},
|
|
440
|
+
healthCheck: true,
|
|
441
|
+
metrics: true
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Add methods with validation
|
|
445
|
+
rpc.addMethod('createUser', {
|
|
446
|
+
handler: async (req, ctx, params) => {
|
|
447
|
+
const user = await ctx.db.users.create(params);
|
|
448
|
+
return { id: user.id, message: 'User created successfully' };
|
|
449
|
+
},
|
|
450
|
+
schema: SchemaBuilder.object({
|
|
451
|
+
name: SchemaBuilder.string().min(1).max(100).required(),
|
|
452
|
+
email: SchemaBuilder.string().format('email').required(),
|
|
453
|
+
age: SchemaBuilder.number().min(0).max(150).optional()
|
|
454
|
+
})
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
app.listen(3000);
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### BigInt and Date Handling
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
// BigInt serialization
|
|
464
|
+
rpc.addMethod('getBigNumber', () => {
|
|
465
|
+
return BigInt('9007199254740991123456789'); // Automatically serialized to string
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Date serialization
|
|
469
|
+
rpc.addMethod('getCurrentTime', () => {
|
|
470
|
+
return new Date(); // Automatically serialized to ISO string
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// Client receives properly typed data
|
|
474
|
+
const client = new RpcClient('http://localhost:3000/api');
|
|
475
|
+
const bigNum = await client.call('getBigNumber'); // Returns BigInt
|
|
476
|
+
const date = await client.call('getCurrentTime'); // Returns Date object
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Error Handling
|
|
480
|
+
|
|
481
|
+
```javascript
|
|
482
|
+
rpc.addMethod('mayFail', (req, ctx, params) => {
|
|
483
|
+
if (params.shouldFail) {
|
|
484
|
+
const error = new Error('Something went wrong');
|
|
485
|
+
error.code = -32001; // Custom error code
|
|
486
|
+
error.data = { details: 'Additional error info' };
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
return 'success';
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Client error handling
|
|
493
|
+
try {
|
|
494
|
+
await client.call('mayFail', { shouldFail: true });
|
|
495
|
+
} catch (error) {
|
|
496
|
+
console.log(error.code); // -32001
|
|
497
|
+
console.log(error.message); // 'Something went wrong'
|
|
498
|
+
console.log(error.data); // { details: 'Additional error info' }
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
userId: 123,
|
|
502
|
+
action: 'login',
|
|
503
|
+
ip: req.ip
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
logger.error('Database error', {
|
|
507
|
+
error: err.message,
|
|
508
|
+
query: 'SELECT * FROM users',
|
|
509
|
+
duration: 150
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// RPC calls are automatically logged
|
|
513
|
+
// [2024-01-01T10:00:00.000Z] INFO: RPC call - {"method":"getUser","params":{"userId":123},"id":"req-123","ip":"192.168.1.1"}
|
|
514
|
+
// [2024-01-01T10:00:00.050Z] INFO: RPC success - {"method":"getUser","id":"req-123","duration":50,"resultSize":245}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Built-in Middlewares
|
|
518
|
+
|
|
519
|
+
```javascript
|
|
520
|
+
const { builtInMiddlewares } = require('rpc-express-toolkit');
|
|
521
|
+
|
|
522
|
+
// Rate limiting
|
|
523
|
+
rpc.use('beforeCall', builtInMiddlewares.rateLimit({
|
|
524
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
525
|
+
max: 100, // limit each IP to 100 requests per windowMs
|
|
526
|
+
message: 'Too many requests from this IP'
|
|
527
|
+
}));
|
|
528
|
+
|
|
529
|
+
// CORS
|
|
530
|
+
rpc.use('beforeCall', builtInMiddlewares.cors({
|
|
531
|
+
origin: ['http://localhost:3000', 'https://myapp.com'],
|
|
532
|
+
credentials: true,
|
|
533
|
+
methods: ['POST']
|
|
534
|
+
}));
|
|
535
|
+
|
|
536
|
+
// Authentication
|
|
537
|
+
rpc.use('beforeCall', builtInMiddlewares.auth({
|
|
538
|
+
required: true,
|
|
539
|
+
verify: async (req) => {
|
|
540
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
541
|
+
return await verifyJWT(token);
|
|
542
|
+
},
|
|
543
|
+
onError: (req, error) => {
|
|
544
|
+
console.log('Auth failed:', error.message);
|
|
545
|
+
}
|
|
546
|
+
}));
|
|
547
|
+
|
|
548
|
+
// Method whitelisting
|
|
549
|
+
rpc.use('beforeCall', builtInMiddlewares.methodWhitelist([
|
|
550
|
+
'add', 'subtract', 'getUser', 'updateUser'
|
|
551
|
+
]));
|
|
552
|
+
|
|
553
|
+
// Request timing
|
|
554
|
+
rpc.use('beforeCall', builtInMiddlewares.timing());
|
|
555
|
+
```
|
|
556
|
+
if (!token || !isValidToken(token)) { // Replace `isValidToken` with your validation logic
|
|
557
|
+
throw new Error('Invalid or missing token');
|
|
558
|
+
}
|
|
559
|
+
return 'OK';
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// Example token validation function (replace with your actual logic)
|
|
563
|
+
function isValidToken(token) {
|
|
564
|
+
// Simple example: Check if the token matches a predefined value
|
|
565
|
+
const expectedToken = 'my-secret-token'; // Replace with your actual token handling logic
|
|
566
|
+
return token === expectedToken;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Start the server
|
|
570
|
+
const PORT = process.env.PORT || 3000;
|
|
571
|
+
app.listen(PORT, () => {
|
|
572
|
+
console.log(`Server is running on port ${PORT}`);
|
|
573
|
+
});
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Adding Methods
|
|
577
|
+
|
|
578
|
+
You can add RPC methods using the `addMethod` function. Each method receives the `context` and `params` as arguments.
|
|
579
|
+
|
|
580
|
+
```javascript
|
|
581
|
+
// Synchronous method
|
|
582
|
+
rpc.addMethod('multiply', (req, ctx, params) => {
|
|
583
|
+
const { a, b } = params;
|
|
584
|
+
return a * b;
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// Asynchronous method
|
|
588
|
+
rpc.addMethod('divide', async (req, ctx, params) => {
|
|
589
|
+
const { a, b } = params;
|
|
590
|
+
if (b === 0) {
|
|
591
|
+
throw new Error('Division by zero');
|
|
592
|
+
}
|
|
593
|
+
// Simulate async operation
|
|
594
|
+
return Promise.resolve(a / b);
|
|
595
|
+
});
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
## API
|
|
599
|
+
|
|
600
|
+
### RpcEndpoint Class
|
|
601
|
+
|
|
602
|
+
A class to handle JSON-RPC 2.0 requests in Express.js applications.
|
|
603
|
+
|
|
604
|
+
#### Constructor
|
|
605
|
+
|
|
606
|
+
```javascript
|
|
607
|
+
new RpcEndpoint(router, context, endpoint = '/api')
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
- **router**: *(Express.Router)* - The Express router to attach the endpoint to.
|
|
611
|
+
- **context**: *(Object)* - The context object to pass to the method handlers.
|
|
612
|
+
- **endpoint** *(optional)*: *(string)* - The endpoint path (default is `/api`).
|
|
613
|
+
|
|
614
|
+
#### addMethod
|
|
615
|
+
|
|
616
|
+
```javascript
|
|
617
|
+
addMethod(name, handler)
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
- **name**: *(string)* - The name of the RPC method.
|
|
621
|
+
- **handler**: *(Function)* - The function that handles the method. Receives `req` (the Request object) `context` and `params` as arguments.
|
|
622
|
+
|
|
623
|
+
```javascript
|
|
624
|
+
(req, context, params) => { /* ... */ }
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
#### Properties
|
|
628
|
+
|
|
629
|
+
- **endpoint**: *(string)* - Returns the endpoint path.
|
|
630
|
+
- **methods**: *(Object)* - Returns the registered methods.
|
|
631
|
+
|
|
632
|
+
#### reply
|
|
633
|
+
|
|
634
|
+
```javascript
|
|
635
|
+
reply(res, { id, result, error })
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
- **res**: *(Express.Response)* - The Express response object.
|
|
639
|
+
- **responsePayload**: *(Object)* - The JSON-RPC response payload.
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
{
|
|
643
|
+
id: string | number | null,
|
|
644
|
+
result: any,
|
|
645
|
+
error: {
|
|
646
|
+
code: number,
|
|
647
|
+
message: string,
|
|
648
|
+
data?: any
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
#### serializeBigInts
|
|
654
|
+
|
|
655
|
+
```javascript
|
|
656
|
+
serializeBigInts(value)
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
- **value**: *(any)* - The value to serialize.
|
|
660
|
+
- **returns**: *(any)* - The serialized value with `BigInt` converted to strings.
|
|
661
|
+
|
|
662
|
+
Recursively converts `BigInt` values to strings to ensure JSON serialization compatibility.
|
|
663
|
+
|
|
664
|
+
## Examples
|
|
665
|
+
|
|
666
|
+
Check out the [examples](./examples) directory for more usage examples.
|
|
667
|
+
|
|
668
|
+
### Example: Complete Express Server Integration
|
|
669
|
+
|
|
670
|
+
```javascript
|
|
671
|
+
const express = require('express');
|
|
672
|
+
const { RpcEndpoint } = require('rpc-express-toolkit');
|
|
673
|
+
|
|
674
|
+
const app = express();
|
|
675
|
+
|
|
676
|
+
app.use(express.json());
|
|
677
|
+
|
|
678
|
+
const context = { user: 'admin' };
|
|
679
|
+
|
|
680
|
+
// Attach to the app router at POST /api endpoint
|
|
681
|
+
const rpc = new RpcEndpoint(app, context);
|
|
682
|
+
|
|
683
|
+
rpc.addMethod('greet', (req, ctx, params) => {
|
|
684
|
+
const { name } = params;
|
|
685
|
+
return `Hello, ${name}!`;
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
rpc.addMethod('getTime', () => {
|
|
689
|
+
return new Date().toISOString();
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const PORT = 3000;
|
|
693
|
+
app.listen(PORT, () => {
|
|
694
|
+
console.log(`Server listening on port ${PORT}`);
|
|
695
|
+
});
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
## Contributing
|
|
699
|
+
|
|
700
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
701
|
+
|
|
702
|
+
### Development Setup
|
|
703
|
+
|
|
704
|
+
1. Fork the repository
|
|
705
|
+
2. Clone your fork: `git clone https://github.com/yourusername/rpc-express-toolkit.git`
|
|
706
|
+
3. Install dependencies: `npm install`
|
|
707
|
+
4. Run tests: `npm test`
|
|
708
|
+
5. Run linting: `npm run lint`
|
|
709
|
+
|
|
710
|
+
### Running Tests
|
|
711
|
+
|
|
712
|
+
```bash
|
|
713
|
+
npm test # Run all tests
|
|
714
|
+
npm run test:watch # Run tests in watch mode
|
|
715
|
+
npm run test:coverage # Run tests with coverage report
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### Code Style
|
|
719
|
+
|
|
720
|
+
This project uses ESLint and Prettier for code formatting. Run `npm run lint` to check code style.
|
|
721
|
+
|
|
722
|
+
## Security
|
|
723
|
+
|
|
724
|
+
### Vulnerability Reporting
|
|
725
|
+
|
|
726
|
+
If you discover a security vulnerability, please send an email to security@example.com. All security vulnerabilities will be promptly addressed.
|
|
727
|
+
|
|
728
|
+
### Best Practices
|
|
729
|
+
|
|
730
|
+
- Always validate input parameters using schema validation
|
|
731
|
+
- Use authentication middleware for sensitive operations
|
|
732
|
+
- Enable rate limiting to prevent abuse
|
|
733
|
+
- Sanitize error messages in production environments
|
|
734
|
+
- Use HTTPS in production
|
|
735
|
+
- Implement proper logging for audit trails
|
|
736
|
+
|
|
737
|
+
## Performance
|
|
738
|
+
|
|
739
|
+
### Optimization Tips
|
|
740
|
+
|
|
741
|
+
- Use batch requests for multiple operations
|
|
742
|
+
- Implement caching where appropriate
|
|
743
|
+
- Use connection pooling for database operations
|
|
744
|
+
- Monitor metrics and logs for performance bottlenecks
|
|
745
|
+
- Use clustering for high-load applications
|
|
746
|
+
|
|
747
|
+
### Benchmarks
|
|
748
|
+
|
|
749
|
+
Performance benchmarks on a typical server setup:
|
|
750
|
+
|
|
751
|
+
- Single request: ~1-2ms overhead
|
|
752
|
+
- Batch request (10 items): ~5-10ms total
|
|
753
|
+
- Memory usage: ~50MB for 10,000 concurrent connections
|
|
754
|
+
- Throughput: ~5,000 requests/second
|
|
755
|
+
|
|
756
|
+
## License
|
|
757
|
+
|
|
758
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
759
|
+
|
|
760
|
+
## Changelog
|
|
761
|
+
|
|
762
|
+
### v2.0.0 (Enterprise Edition)
|
|
763
|
+
|
|
764
|
+
- ✨ **BREAKING**: Clean API with `RpcEndpoint` and `RpcClient` classes (100% backward compatible)
|
|
765
|
+
- ✨ **BREAKING**: Renamed package to `rpc-express-toolkit` for better clarity
|
|
766
|
+
- ✨ Added structured logging with configurable transports
|
|
767
|
+
- ✨ Implemented middleware system with built-in middlewares
|
|
768
|
+
- ✨ Added schema validation with Ajv and schema builder
|
|
769
|
+
- ✨ Implemented batch request processing
|
|
770
|
+
- ✨ Added health check and metrics endpoints
|
|
771
|
+
- ✨ Enhanced BigInt/Date serialization with timezone support
|
|
772
|
+
- ✨ Added authentication and authorization middleware
|
|
773
|
+
- ✨ Implemented rate limiting and CORS support
|
|
774
|
+
- ✨ Added method whitelisting and security features
|
|
775
|
+
- ✨ Clean API: `RpcEndpoint`/`RpcClient` classes
|
|
776
|
+
- 🔧 Improved error handling and sanitization
|
|
777
|
+
- 🔧 Enhanced client with batch and notification support
|
|
778
|
+
- 🔧 Added comprehensive test coverage
|
|
779
|
+
- 📚 Updated documentation with enterprise features
|
|
780
|
+
|
|
781
|
+
## Support
|
|
782
|
+
|
|
783
|
+
- 📖 [Documentation](https://github.com/n-car/rpc-express-toolkit/wiki)
|
|
784
|
+
- 🐛 [Issue Tracker](https://github.com/n-car/rpc-express-toolkit/issues)
|
|
785
|
+
- 💬 [Discussions](https://github.com/n-car/rpc-express-toolkit/discussions)
|
|
786
|
+
|
|
787
|
+
## Acknowledgments
|
|
788
|
+
|
|
789
|
+
- JSON-RPC 2.0 Specification
|
|
790
|
+
- Express.js community
|
|
791
|
+
- Ajv JSON Schema validator
|
|
792
|
+
- All contributors and users
|
|
793
|
+
|
|
794
|
+
---
|
|
795
|
+
|
|
796
|
+
Made with ❤️ by n-car
|