1 | ## node-mu
|
2 |
|
3 | A node.js minimalistic microservice framework. At this stage node-mu it is not yet production-ready. It is under heavy testing.
|
4 |
|
5 | ### Release notes
|
6 | As of this 0.1.1 version, we introduce Inversion of Control using the [inversify](https://github.com/inversify) IoC container library.
|
7 |
|
8 | We introduce the new _Producer_ and _Service_ concept.
|
9 | All of the _node-mu_ components are now _injectable_ thanks to the relative decorator functions:
|
10 |
|
11 | * _injectable_
|
12 | * _componente_
|
13 | * _provider_
|
14 | * _route_
|
15 | * _controller_
|
16 | * _service_
|
17 | * _repository_
|
18 | * _factoryFunction_
|
19 | * _application_
|
20 | * _inject_
|
21 |
|
22 | ### Installation
|
23 | Using npm:
|
24 | ````
|
25 | $ npm install --save node-mu
|
26 | ````
|
27 | using Yarn:
|
28 | ````
|
29 | $ yarn add node-mu
|
30 | ````
|
31 |
|
32 | ### Example
|
33 |
|
34 | config/default.yml
|
35 | ````
|
36 | ########################################################################
|
37 | # Service configuration.
|
38 | #
|
39 | # This configuration will be overridden by the NODE_ENV profile you use,
|
40 | # for example development.yml for development profile or production.yml
|
41 | # for production a so on.
|
42 | #
|
43 | ########################################################################
|
44 |
|
45 | service:
|
46 | group: Examples
|
47 | name: SimplerService
|
48 | version:
|
49 | major: 0
|
50 | minor: 0
|
51 | status: 0 # 0: alpha, 1: beta, 2: release, 3: final
|
52 |
|
53 | api:
|
54 | endpoint:
|
55 | port: 5001
|
56 | baseRoutingPath: /api/v2
|
57 | security:
|
58 | enabled: true
|
59 | jwt:
|
60 | secret: configure-here-jwt-secret-for-the-service
|
61 | expiration:
|
62 | enabled: true
|
63 | minutes: 13149000
|
64 |
|
65 | management:
|
66 | endpoint:
|
67 | port: 5101
|
68 | baseRoutingPath: /mgmt
|
69 | health:
|
70 | path: /health
|
71 | full: true
|
72 |
|
73 |
|
74 | jwt:
|
75 | secret: configure-here-jwt-secret-for-the-service
|
76 | expiration:
|
77 | enabled: true
|
78 | minutes: 13149000
|
79 |
|
80 | # optional configuration to open a db connection
|
81 | db:
|
82 | client: mysql2
|
83 | connection:
|
84 | host: set-here-the-host-of-your-db
|
85 | database: set-here-the-name-of-your-db-schema
|
86 | user: set-here-the-user-name
|
87 | password: set-here-the-user-password
|
88 | charset: utf8
|
89 |
|
90 | # optional configurtion to open and AMQP connection
|
91 | amqp:
|
92 | url: amqp://set-here-a-user:set-here-a-pwd@set-here-the-host:5672/set-here-a-VHOST
|
93 | exchange:
|
94 | name: set_here_the_name_of_an_exchange
|
95 |
|
96 | events:
|
97 | mapFile: set/here/your/json/file
|
98 |
|
99 | log:
|
100 | path: ../path/for/log/file
|
101 | console: true|false
|
102 | level: set-here-your-log-level_(INFO,DEBUG,...)
|
103 | json: true|false
|
104 | requests:
|
105 | console: true|false
|
106 | errors:
|
107 | console: true|false
|
108 |
|
109 |
|
110 | # should match your Git repo version
|
111 | info:
|
112 | version: your.service.version
|
113 | ````
|
114 |
|
115 | events-map.json
|
116 | ````
|
117 | {
|
118 | "events": [
|
119 | {
|
120 | "name": "new_user",
|
121 | "amqpConfig": {
|
122 | "exchange": {
|
123 | "name": "uaa_events",
|
124 | "route": "uaa_new_user_route"
|
125 | }
|
126 | }
|
127 | },
|
128 | {
|
129 | "name": "user_updated",
|
130 | "amqpConfig": {
|
131 | "exchange": {
|
132 | "name": "uaa_events",
|
133 | "route": "uaa_new_user_route"
|
134 | }
|
135 | }
|
136 | },
|
137 | {
|
138 | "name": "user_removed",
|
139 | "amqpConfig": {
|
140 | "exchange": {
|
141 | "name": "uaa_events",
|
142 | "route": "uaa_new_user_route"
|
143 | }
|
144 | }
|
145 | }
|
146 | ]
|
147 | }
|
148 | ````
|
149 |
|
150 | index.js
|
151 | ```javascript
|
152 | const build = require('../../lib');
|
153 | const SimpleService = require('./simple-service');
|
154 |
|
155 | const start = async () => {;
|
156 | try {
|
157 | const service = build(SimpleService);
|
158 | await service.run();
|
159 | } catch (err) {
|
160 | throw err;
|
161 | }
|
162 | };
|
163 |
|
164 | start()
|
165 | .then(() => {
|
166 | console.log(`\uD83D\uDE80 node-\u03BC service started [pid: ${process.pid}]... bring me some \uD83C\uDF7A \uD83C\uDF7A \uD83C\uDF7A`);
|
167 | }).catch((err) => {
|
168 | console.error(`\uD83D\uDD25 service crashed at startup: ${err}`);
|
169 | process.exit(1);
|
170 | });
|
171 | ```
|
172 |
|
173 | simple-service.js
|
174 | ```javascript
|
175 | const {container, injectable, component, application, inject} = require('../../lib').ioc;
|
176 | const {DbConnectionManager, Api, AmqpConnectionManager, EventsEmitter} = require('../../lib').Providers;
|
177 | const Application = require('../../lib').Application;
|
178 | const SimpleRoute = require('./simple-route');
|
179 |
|
180 | module.exports =
|
181 | inject([
|
182 | DbConnectionManager,
|
183 | AmqpConnectionManager,
|
184 | EventsEmitter,
|
185 | Api
|
186 | ],
|
187 | application(
|
188 | class SimpleService extends Application {
|
189 | constructor(dbConnectionManager, amqpConnectionManager, eventsEmitter, api) {
|
190 | super();
|
191 | this.dbConnectionManager = dbConnectionManager;
|
192 | this.amqpConnectionManager = amqpConnectionManager;
|
193 | this.eventsEmitter = eventsEmitter;
|
194 | this.api = api;
|
195 | this._logger.info('SimpleServie started');
|
196 | }
|
197 |
|
198 | $bootstrap() {
|
199 | // DO HERE WHAT YOU NEED DURING SERVICE INITIALIZATION
|
200 | }
|
201 | }
|
202 | )
|
203 | );
|
204 | ```
|
205 |
|
206 | simple-route.js
|
207 | ```javascript
|
208 | 'use strict';
|
209 |
|
210 | const { inject, route } = require('../../lib').ioc;
|
211 | const { Route } = require('../../lib');
|
212 | const SimpleController = require('./simple-controller');
|
213 | const Joi = require('joi');
|
214 |
|
215 | const path = '/simple';
|
216 |
|
217 | module.exports =
|
218 | inject(
|
219 | [SimpleController],
|
220 | route(
|
221 | class SimpleRoute extends Route {
|
222 | constructor(simpleController) {
|
223 | super(path);
|
224 | this._simpleController = simpleController;
|
225 |
|
226 | this._setRoutes();
|
227 | }
|
228 |
|
229 | _setRoutes() {
|
230 | this.route('/simple').get('/info', this._simpleController.info);
|
231 |
|
232 | this.route('/complex', {
|
233 | '/first': {
|
234 | method: ['POST'],
|
235 | headers: {
|
236 | 'host': Joi.string().required(),
|
237 | 'user-agent': Joi.string().required()
|
238 | },
|
239 | body: {
|
240 | username: Joi.string().required()
|
241 | }
|
242 | }
|
243 | }
|
244 | ).post('/first', (req, res) => {
|
245 | this._logger.debug('****** VALIDATION OK: ' + req.body.username);
|
246 | res.send(req.body.username);
|
247 | }).get('/first', (req, res) => {
|
248 | res.send('/first endpoint OK');
|
249 | });
|
250 | }
|
251 | }
|
252 | )
|
253 | );
|
254 | ```
|
255 |
|
256 | simple-controller.js
|
257 | ```javascript
|
258 | 'use strict';
|
259 |
|
260 | const {inject, controller} = require('../../lib').ioc;
|
261 | const {ApiEventsEmitterController} = require('../../lib').Controllers;
|
262 | const SimpleBusinessService = require('./services/simple-business-service');
|
263 |
|
264 | module.exports =
|
265 | inject(
|
266 | [SimpleBusinessService],
|
267 | controller(
|
268 | class SimpleController extends ApiEventsEmitterController {
|
269 | constructor(simpleBusinessService) {
|
270 | super();
|
271 | this._simpleBusinessService = simpleBusinessService;
|
272 | }
|
273 |
|
274 | async info(req, res, next) {
|
275 | this._logger.info('Request to get info');
|
276 | try {
|
277 | const info = await this._simpleBusinessService.info();
|
278 | res.json(info);
|
279 | } catch (err) {
|
280 | this._logger.error(err);
|
281 | next(err);
|
282 | }
|
283 | }
|
284 | }
|
285 | )
|
286 | );
|
287 | ```
|
288 |
|
289 | services/simple-business-service.js
|
290 | ```javascript
|
291 | 'use strict'
|
292 |
|
293 | const { inject, service } = require('../../../lib').ioc;
|
294 | const { Service } = require('../../../lib');
|
295 | const SimpleRepository = require('../simple-repository');
|
296 |
|
297 | module.exports =
|
298 | inject(
|
299 | [SimpleRepository],
|
300 | service(
|
301 | class SimpleBusinessService extends Service {
|
302 | constructor(simpleRepository) {
|
303 | super();
|
304 | this._simpleRepository = simpleRepository;
|
305 | }
|
306 |
|
307 | async info() {
|
308 | return new Promise(async (resolve, reject) => {
|
309 | try {
|
310 | const user = await this._simpleRepository.findOne({login: 'jstest2111111111111'}, ['authorities']);
|
311 | console.log('\n\nUSER: ' + JSON.stringify(user, null, 2) + '\n\n');
|
312 | resolve(user);
|
313 | } catch (err) {
|
314 | reject(err);
|
315 | }
|
316 | });
|
317 | }
|
318 | }
|
319 | )
|
320 | );
|
321 | ```
|
322 |
|
323 | user-model.js
|
324 | ```javascript
|
325 | 'use strict';
|
326 |
|
327 | const Model = require('objection').Model;
|
328 | const Authority = require('./authority-model');
|
329 |
|
330 |
|
331 | class User extends Model {
|
332 |
|
333 | static get tableName() {
|
334 | return 'USERS';
|
335 | }
|
336 |
|
337 | static get jsonSchema() {
|
338 | return {
|
339 | type: 'object',
|
340 | required: ['login', 'activated', 'created_by', 'created_date'],
|
341 | properties: {
|
342 | id: { type: 'bigInteger' },
|
343 | login: { type: 'string', minLength: 1, maxLength:50 },
|
344 | password_hash: { type: 'string', minLength: 1, maxLength: 60 },
|
345 | first_name: { type: 'string', minLength: 1, maxLength: 50 },
|
346 | last_name: { type: 'string', minLength: 1, maxLength: 50 },
|
347 | email: { type: 'string', minLength: 7, maxLength: 100 },
|
348 | image_url: { type: 'string', minLength: 1, maxLength: 256 },
|
349 | activated: { type: 'bit'},
|
350 | lang_key: { type: 'string', minLength: 2, maxLength: 6 },
|
351 | activation_key: { type: 'string', minLength: 1, maxLength: 20 },
|
352 | reset_key: { type: 'string', minLength: 1, maxLength: 20 },
|
353 | created_by: { type: 'string', minLength: 1, maxLength: 50 },
|
354 | created_date: { type: 'timestamp' },
|
355 | reset_date: { type: 'timestamp' },
|
356 | last_modified_by: { type: 'string', minLength: 1, maxLength: 50 },
|
357 | last_modified_date: { type: 'timestamp' }
|
358 | }
|
359 | };
|
360 | }
|
361 |
|
362 | static get relationMappings() {
|
363 | return {
|
364 | authorities: {
|
365 | relation: Model.ManyToManyRelation,
|
366 | modelClass: Authority,
|
367 | join: {
|
368 | from: 'user.id',
|
369 | through: {
|
370 | from: 'user_authority.user_id',
|
371 | to: 'user_authority.authority_name'
|
372 | },
|
373 | to: 'authority.name'
|
374 | }
|
375 | }
|
376 | };
|
377 | }
|
378 |
|
379 | }
|
380 |
|
381 | module.exports = User;
|
382 | ```
|
383 |
|
384 | authority-model.js
|
385 | ```javascript
|
386 | 'use strict';
|
387 |
|
388 | const Model = require('objection').Model;
|
389 |
|
390 | class Authority extends Model {
|
391 |
|
392 | static get tableName() {
|
393 | return 'AUTHORITIES';
|
394 | }
|
395 |
|
396 | static get jsonSchema() {
|
397 | return {
|
398 | type: 'object',
|
399 | required: ['name'],
|
400 | properties: {
|
401 | name: { type: 'string', minLength: 1, maxLength: 50 }
|
402 | }
|
403 | };
|
404 | }
|
405 |
|
406 | static get relationMappings() {
|
407 | return {
|
408 | users: {
|
409 | relation: Model.ManyToManyRelation,
|
410 | modelClass: __dirname + '/user-model',
|
411 | join: {
|
412 | from: 'authority.name',
|
413 | through: {
|
414 | from: 'user_authority.authority_name',
|
415 | to: 'user_authority.user_id'
|
416 | },
|
417 | to: 'user.id'
|
418 | }
|
419 | }
|
420 | };
|
421 | }
|
422 | }
|
423 |
|
424 | module.exports = Authority;
|
425 | ```
|
426 |
|
427 | simple-repository.js
|
428 | ```javascript
|
429 | 'use strict';
|
430 |
|
431 | const {repository} = require('../../lib').ioc;
|
432 | const {Repository} = require('../../lib');
|
433 | const User = require('./user-model');
|
434 |
|
435 | module.exports =
|
436 | repository(
|
437 | class SimpleRepository extends Repository {
|
438 | constructor() {
|
439 | super(User);
|
440 | }
|
441 | }
|
442 | );
|
443 | ```
|
444 |
|
445 |
|
446 | ### License
|
447 | Licensed under the [MIT license](https://github.com/ITResourcesOSS/node-mu/blob/master/LICENSE). |
\ | No newline at end of file |