UNPKG

44.4 kBMarkdownView Raw
1# qewd-up: A new fast-track route to Building APIs Using QEWD
2
3Rob Tweed <rtweed@mgateway.com>
411 May 2020, M/Gateway Developments Ltd [http://www.mgateway.com](http://www.mgateway.com)
5
6Twitter: @rtweed
7
8Google Group for discussions, support, advice etc: [http://groups.google.co.uk/group/enterprise-web-developer-community](http://groups.google.co.uk/group/enterprise-web-developer-community)
9
10# What is QEWD-Up?
11
12Previously, in order to use QEWD, you needed to create a mixture of configuration settings and application code, and in doing so you needed to have a relatively detailed understanding of how QEWD worked under the covers. This meant:
13
14- a learning curve which took time and effort
15- it could be difficult and time-consuming to construct the necessary moving parts that make up an API
16- the scalability of this approach could become more problematic with increasing numbers of APIs to support
17- the maintainability of APIs could be tricky and difficult to trace through the moving parts of QEWD itself. Someone else picking up your APIs wouldn�t necessarily find it intuitively clear what they were doing and how they worked.
18
19QEWD-Up is a new layer of abstraction on top of QEWD that aims to hide all these issues away, leaving you with just a simple description of your API routes and the associated code that they invoke at the QEWD back-end. You now just describe the What, and QEWD-up looks after the How. Under the covers, it�s all standard QEWD as before - it�s just all been automated and abstracted out of the way for you!
20
21# QEWD-Up Modes
22
23QEWD-Up can be used in three key architectural modes:
24
25- [**Dockerised QEWD Monolith**](#dockerised-qewd-monolith): QEWD running as a Dockerised Container, and as a monolithic application defined as a set of REST APIs
26
27- [**Dockerised QEWD MicroServices**](#dockerised-qewd-microservices): A set of Dockerised QEWD Containers, implementing your REST APIs across a set of MicroServices
28
29- [**Native QEWD Monolith**](#native-qewd-monolith): If you prefer a native, un-Dockerised platform, this option has QEWD installed and running natively on your system (along with Node.js and YottaDB), allowing you to create a monolithic application defined as a set of REST APIs
30
31In the first two modes, *all* you need to install on your hardware is Docker. Everything else that is needed by QEWD is encapsulated within the QEWD Docker Container(s). These two Docker-based modes allow you to be up and running with the minimum of fuss and and within a few minutes, rapidly building APIs that are as complex as you require, but which are easy to maintain and understand.
32
33Getting started with each of the QEWD-Up modes is described below. They all follow a similar pattern, but there are some mode-specific differences.
34
35For advanced use of QEWD-Up, see the [detailed documentation here.](https://github.com/robtweed/qewd/tree/master/up/docs)
36
37
38
39# Dockerised QEWD Monolith
40
41## Pre-requisites
42
43It's very quick and simple to get started in this mode, because all you need to install on your machine is Docker. Everything else is handled by the QEWD Docker Container (which is available from the Docker Hub as rtweed/docker-server (or, if you use a Raspberry Pi: rtweed/docker-server-rpi )
44
45There are lots of documents on the Internet that describe how to install Docker. If you use Ubuntu 18.04, I�ve found [this to be an excellent set of instructions](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04).
46
47However, if you�re using a different flavour of Linux, a simple Google search will quickly find what you need.
48
49if you want to try out QEWD-Up on a Raspberry Pi, you can install Docker by simply typing:
50
51 curl -sSL https://get.docker.com | sh
52
53
54## Defining Your APIs
55
56Create a folder for your application / suite of APIs. I'm going to name my example *dockerExample* (eg using the file path *~/dockerExample*). Also create sub-folders beneath it as shown below (use the sub-folder names exactly as shown):
57
58
59 ~/dockerExample
60 |
61 |_ configuration
62 |
63 |_ apis
64 |
65
66Create two text files in the configuration folder:
67
68
69 ~/dockerExample
70 |
71 |_ configuration
72 | |
73 | |_ routes.json
74 | |
75 | |_ config.json
76 |
77 |_ apis
78 |
79
80
81### The *config.json* File
82
83The *config.json* file provides QEWD-Up with instructions on how to configure QEWD.
84
85As its file extension implies, its contents must be correctly-structured JSON.
86
87QEWD-Up will apply sensible default values unless overridden by information in your *config.json* file.
88
89At the very least, your *config.json* file should contain the following:
90
91 {
92 "qewd_up": true
93 }
94
95See later in this document for more details on the *config.json* file and how to configure QEWD differently from the QEWD-Up defaults.
96
97
98### The *routes.json* File
99
100You MUST create a file named *routes.json*. This file contains the definitions of your REST routes, expressed as a JSON array of route objects. Each route is described in terms of:
101
102- the URI path
103- the HTTP method
104- the name of the handler module that will perform the work of this API
105
106*eg* here�s the definition of a single API:
107
108 [
109 {
110 "uri": "/api/info",
111 "method": "GET",
112 "handler": "getInfo"
113 }
114 ]
115
116You can describe as many routes as you wish in this file. The syntax MUST be strict JSON - ie all property names and text-string values must be double-quoted.
117
118
119### Defining Your API Handlers
120
121You define your API Handlers within the *apis* folder. First create a sub-folder for each *handler* property name that you defined in the *routes.json* file (see above). For example, using the *routes.json* example above, you'd create:
122
123 ~/dockerExample
124 |
125 |� configuration
126 | |
127 | |� routes.json
128 | |
129 | |� config.json
130 |
131 |� apis
132 | |
133 | |� getInfo
134 |
135
136
137Within each handler sub-folder, you now create a module file that defines the code that will be invoked when the API route is requested. This module file can be named either *handler.js* or *index.js". For example:
138
139 ~/dockerExample
140 |
141 |� configuration
142 | |
143 | |� routes.json
144 | |
145 | |� config.json
146 |
147 |� apis
148 | |
149 | |� getInfo
150 | |
151 | |� handler.js
152
153
154
155Here's an example *handler.js* file for the *getInfo* API:
156
157 module.exports = function(args, finished) {
158 finished({
159 info: {
160 server: 'Qewd-Up Container',
161 arch: process.arch,
162 platform: process.platform,
163 versions: process.versions,
164 memory: process.memoryUsage(),
165 }
166 });
167 };
168
169If you�ve written QEWD REST handlers, you�ll recognise this format.
170
171In summary, simply export a function with 2 arguments:
172
173- **args**: an object that contains the content of your REST request, including its path, headers, HTTP method, any querystring values and any body payload
174
175- **finished**: the QEWD function that you use to end your handler. This function releases the QEWD worker process that handled your module back to QEWD's available worker pool, and tells QEWD to return the object you provide as its argument as a JSON response to the REST client that sent the original request.
176
177
178## Starting your QEWD Container
179
180Fire up the QEWD Docker Container, eg:
181
182 docker run -it --name qewdup --rm -p 8080:8080 -v ~/dockerExample:/opt/qewd/mapped rtweed/qewd-server
183
184Note: you may need to add sudo to the start of this command, depending on how you configured your Docker environment.
185
186If you�re using a Raspberry Pi:
187
188 docker run -it --name qewdup --rm -p 8080:8080 -v ~/dockerExample:/opt/qewd/mapped rtweed/qewd-server-rpi
189
190By default, QEWD listens within the Docker Container on port 8080. The command examples above are mapping this to port 8080 on your host machine. If you want to use a different host port, just change the -p directive, eg to listen on port 3000:
191
192 docker run -it --name qewdup --rm -p 3000:8080 -v ~/dockerExample:/opt/qewd/mapped rtweed/qewd-server
193
194
195Your application's configuration and API files are mapped into the Docker Container using this parameter:
196
197 -v ~/dockerExample:/opt/qewd/mapped
198
199**Always** map your QEWD-Up Application Folder to */opt/qewd/mapped*
200
201
202## Running Your APIs
203
204Now you can try out your API(s). By default, if you specified port 8080 as your host listener port, point a browser at:
205
206 http://192.168.1.100:8080/api/info
207
208 (change the IP address as appropriate)
209
210and back should come the results from your API handler module, eg:
211
212 {
213 "info": {
214 "server": "Qewd-Up Container",
215 "arch": "x64",
216 "platform": "linux",
217 "versions": {
218 "http_parser": "2.8.0",
219 "node": "10.12.0",
220 "v8": "6.8.275.32-node.35",
221 "uv": "1.23.2",
222 "zlib": "1.2.11",
223 "ares": "1.14.0",
224 "modules": "64",
225 "nghttp2": "1.34.0",
226 "napi": "3",
227 "openssl": "1.1.0i",
228 "icu": "62.1",
229 "unicode": "11.0",
230 "cldr": "33.1",
231 "tz": "2018e"
232 },
233 "memory": {
234 "rss": 52396032,
235 "heapTotal": 11780096,
236 "heapUsed": 7756656,
237 "external": 28323
238 }
239 }
240 }
241
242That�s it - your API is up and working!
243
244You can now add more routes and their associated handler methods. If you do, you must restart QEWD-Up.
245
246To stop your running QEWD instance, just type CTRL & C.
247
248To restart QEWD-Up, just re-use the *docker run* command (as shown above) again.
249
250
251## Installing Additional Node.js Modules
252
253if you need to use additional Node.js modules (ie ones not automatically included with QEWD itself), you can do this by adding the file *install_modules.json*, eg:
254
255 ~/dockerExample
256 |
257 |� install_modules.json
258 |
259 |� configuration
260 | |
261 | |� routes.json
262 | |
263 | |� config.json
264 |
265
266This file should contain a JSON array, listing the names of the modules you want loaded from NPM. For example:
267
268 [
269 "a-find",
270 "body-parser-xml",
271 "multer"
272 ]
273
274The modules in this file are loaded from NPM when the *qewd-server* Docker Container is next started. Modules are saved into a *node_modules* folder in your mapped host file volume. As a result, the next time(s) you start the Docker Container, your additional modules are already available and don't need to be reloaded.
275
276So, after starting the Container, you'll see the *node_modules* folder that will have been created, eg:
277
278 ~/dockerExample
279 |
280 |� install_modules.json
281 |
282 |� node_modules
283 | |
284 | a_find
285 | ...etc
286 |
287 |� configuration
288 | |
289 | |� routes.json
290 | |
291 | |� config.json
292
293
294If you later modify your *install_modules.json* file and add new modules, they will be loaded from NPM into the *node_modules* folder when the Container is next restarted.
295
296# Dockerised QEWD MicroServices
297
298This mode allows you to split out your APIs and run them as separate, discrete MicroServices. For example, you might want to separate out your APIs by overall business function, so, for example, all the APIs that are related to user authentication could be in one single MicroService.
299
300Your MicroServices can run on separate physical machines, or they can all run on one host machine, or any combination between - it�s a very scalable and powerful approach.
301
302## Pre-requisites
303
304It's very quick and simple to get started in this mode, because all you need to install on each machine that will host a MicroService is Docker. Everything else is handled by each instance of the QEWD Docker Container (which is available from the Docker Hub as rtweed/docker-server (or, if you use a Raspberry Pi: rtweed/docker-server-rpi )
305
306There are lots of documents on the Internet that describe how to install Docker. If you use Ubuntu 18.04, I�ve found [this to be an excellent set of instructions](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04).
307
308However, if you�re using a different flavour of Linux, a simple Google search will quickly find what you need.
309
310if you want to try out QEWD-Up on a Raspberry Pi, you can install Docker by simply typing:
311
312 curl -sSL https://get.docker.com | sh
313
314Finally, create a bridged Docker network that will be used exclusively by your QEWD Orchestrator and MicroService instances:
315
316 sudo docker network create qewd-net
317
318
319## Key Concepts
320
321In a QEWD-Up MicroService architecture, all external-facing communication is via a MicroService that is known as the Orchestrator (*aka* Orchestrator MicroService). REST clients will send their requests to the Orchestrator MicroService.
322
323All the other MicroServices are named according to how you want to name them, and each request sent by a REST client to the Orchestrator service is forwarded by it to the MicroService that has been specified to handle the API.
324
325Usually, the Orchestrator does not handle any APIs directly itself, but it can if you wish.
326
327In our example we�ll have 3 MicroServices:
328
329- **orchestrator**
330- **login_service** - this will handle the login API
331- **info_service** - once logged in, you�ll be able to send a query to this MicroService
332
333
334## JSON Web Tokens
335
336QEWD automatically uses JSON Web Tokens (JWTs) to maintain security between the MicroServices, and returns its JWTs to the REST client as part of the response. Your REST client can make use of the JWT for its own purposes, but the JWT **must** be returned as a Bearer Token with all REST requests, except for APIs that you specifically tell QEWD-Up to ignore the JWT.
337
338The QEWD-generated JWT is added to your requests using the HTTP Authorization Header:
339
340 Authorization: Bearer {{JWT}}
341
342For example:
343
344 Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDI2MzMwO��etc
345
346See later how this works.
347
348## Defining Your APIs
349
350Create a folder for your application / suite of APIs. I'm going to name my example *microserviceExample* (eg using the file path *~/microserviceExample*). Also create sub-folders beneath it as shown below (use the sub-folder names exactly as shown):
351
352
353 ~/microserviceExample
354 |
355 |� configuration
356 |
357 |� login_service
358 |
359 |� info_service
360 |
361
362You **always** have a *configuration* folder. The other folder names are up to you - create one for each of your MicroServices.
363
364
365Create two text files in the configuration folder:
366
367
368 ~/microserviceExample
369 |
370 |� configuration
371 | |
372 | |� routes.json
373 | |
374 | |� config.json
375 |
376 |� login_service
377 |
378 |� info_service
379 |
380
381
382### The *config.json* File
383
384You **MUST** create a file named *config.json*. This file tells QEWD to use the QEWD-Up mechanism and also defines the names of your MicroServices, for example we�ll use:
385
386 {
387 "qewd_up": true,
388 "microservices": [
389 {
390 "name": "login_service",
391 "qewd": {
392 "serverName": "Login MicroService"
393 }
394 },
395 {
396 "name": "info_service",
397 "qewd": {
398 "serverName": "Info MicroService"
399 }
400 }
401 ]
402 }
403
404You can see that the MicroServices are defined in the *microservices* array which contains one or more MicroService objects, each defining the MicroService name (which must match the MicroService folder names that we previously created)
405
406Provided we use the *qewd-net* bridged Docker network that we created earlier, and provided we use these names when we start up each Docker instance, the *Orchestrator* can use Docker's automatic service discovery to find and connect to the MicroServices.
407
408Optionally, if you want to use the *qewd-monitor* application with the MicroService (it is enabled by default), then you can specify the name that will appear in the *qewd-monitor* main page. In the example above you can see we�re specifying this using the **qewd.serverName** property for each MicroService.
409
410We�re going to run the Orchestrator and the two MicroServices on the same physical host machine, but they�ll each listen on a different port.
411
412QEWD-Up will automatically generate a unique Uid-formatted Secret string that will be used to sign and authenticate the QEWD-generated JWTs. You'll see this appearing in your *config.json* file after you run up the Orchestrator MicroService for the first time, eg:
413
414 "jwt": {
415 "secret": "b3de07f4-d2be-4337-b77c-4caf977f5199"
416 }
417
418This ensures that all the MicroServices share this same secret.
419
420Optionally, the *config.json* file also allows you to specify how you want to configure QEWD itself on each MicroService. QEWD-Up will apply sensible default settings that will be good enough for now. See later in this document for more details on the *config.json* file and how to configure QEWD differently from the QEWD-Up defaults.
421
422
423### The *routes.json* File
424
425You **MUST** create a file named *routes.json*. This will contain the definition of your REST routes. As its file extension implies, its contents must be correctly-structured JSON, describing the list of REST routes that your system will support. Each route is described in terms of:
426
427- **uri**: the URI path
428- **method**: the HTTP method
429- **handler**: the name of the handler module that will perform the work of this API
430- **on_microservice**: the name of the MicroService in which the handler module is defined and on which it will run
431- **authenticate**: optionally, set this property to *false* in order to allow the API to be invoked without a JWT being present in the incoming request.
432
433The routes are described as an array of route objects, eg let�s use this example
434
435 [
436 {
437 "uri": "/api/info/info",
438 "method": "GET",
439 "handler": "getInfo",
440 "on_microservice": "info_service"
441 },
442 {
443 "uri": "/api/login",
444 "method": "POST",
445 "handler": "login",
446 "on_microservice": "login_service",
447 "authenticate": false
448 }
449 ]
450
451You can describe as many routes as you wish in this file. The syntax MUST be strict JSON - ie all property names and text-string values must be double-quoted.
452
453In the example above:
454
455- the first route defines an API whose handler method will be invoked on the Orchestrator MicroService.
456
457- the second and third routes include the **on_microservice** property which tells QEWD-Up the MicroService on which to invoke the specified **handler** module.
458
459- the third route includes the property **authenticate: false** which tells QEWD-Up that it will be invoked without first checking for a valid JWT
460
461
462### Defining Your MicroService API Handlers
463
464#### Location
465
466You define your API Handlers within the MicroService folder in which it will execute. You have two options:
467
468- if your handler modules are simple and you don't want or need to break them into smaller sub-modules, you can define each one using sub-folder names that correspond to the handler names, placing each sub-folder under the MicroService folder.
469
470- in more complex situations, you may find it neater to add an intermediate sub-folder named *apis*, below which you define your handler modules.
471
472So, first create a sub-folder for each *handler* property name that you defined in the *routes.json* file (see above). Then, within each of these handler sub-folders, create the module file that defines what the handler actually does. You can name this file either *handler.js* or *index.js*.
473
474For example, using the *routes.json* example above, you'd create:
475
476 ~/microserviceExample
477 |
478 |� configuration
479 | |
480 | |� routes.json
481 | |
482 | |� config.json
483 |
484 |� login_service
485 | |
486 | |� login
487 | |
488 | |� handler.js
489 |
490 |� info_service
491 | |
492 | |� getInfo
493 | |
494 | |� handler.js
495 |
496
497Alternatively, you could use an intermediate *apis* sub-folder:
498
499 ~/microserviceExample
500 |
501 |� configuration
502 | |
503 | |� routes.json
504 | |
505 | |� config.json
506 |
507 |� login_service
508 | |
509 | |� apis
510 | |
511 | |� login
512 | |
513 | |� index.js
514 |
515 |� info_service
516 | |
517 | |� apis
518 | |
519 | |� getInfo
520 | |
521 | |� index.js
522 |
523
524This latter structure allows you to add other MicroService folders in which to place your own modules on which your handler modules depend, eg:
525
526 ~/microserviceExample
527 |
528 |
529 |� login_service
530 | |
531 | |� apis
532 | | |
533 | | |� login
534 | | |
535 | | |� index.js
536 | |� utils
537 | | |
538 | | |� encrypt.js
539 | | |
540 | | |� decrypt.js
541
542
543So in the above example, your login handler and other API handler methods may need to use encryption and decryption logic. We can put the modules for these in a *utils* folder (apart from *apis*, any other folder names are up to you), keeping them neatly separated from the main handler modules.
544
545#### API Handler Module Arguments
546
547QEWD REST handler modules export a function with 2 arguments:
548
549- **args**: an object that contains the content of your REST request, including its path, headers, HTTP method, any querystring values and any body payload. It also contains the QEWD MicroService JWT.
550
551- **finished**: the QEWD function that you use to end your handler. This function releases the QEWD worker process that handled your module back to QEWD's available worker pool, and tells QEWD to return the object you provide as its argument as a JSON response to the REST client that sent the original request.
552
553
554#### Examples
555
556Here's the example handler modules:
557
558##### login/handler.js
559
560 module.exports = function(args, finished) {
561 var username = args.req.body.username;
562 var password = args.req.body.password;
563 var jwt = args.session;
564 if (username === 'rob' && password === 'secret') {
565 jwt.userText = 'Welcome Rob';
566 jwt.username = username;
567 jwt.authenticated = true;
568 jwt.timeout = 1200;
569 finished({ok: true});
570 }
571 else {
572 finished({error: 'Invalid login'});
573 }
574 };
575
576
577In this example I'm simply hard-coding the authentication logic, allowing only a username of *rob* and a password of *secret*. Any other username/password will return an error JSON object.
578
579If login is successful, then I'm adding information to the JWT. The *authenticated* and *timeout* properties (or *claim* in JWT parlance) are special, QEWD-reserved ones that are used by QEWD's JWT authentication:
580
581- **authenticated**: determines that the user has been correctly authenticated each time the JWT is tested within the QEWD MicroServices
582- **timeout**: sets the JWT timeout which, in turn, updates the JWT *exp* claim each time a handler module is invoked
583
584
585##### getInfo/handler.js
586
587 module.exports = function(args, finished) {
588 finished({
589 info: {
590 microService: 'info_service',
591 arch: process.arch,
592 platform: process.platform,
593 versions: process.versions,
594 memory: process.memoryUsage(),
595 }
596 });
597 };
598
599In this simple example, I'm simply returning some information about the info_service MicroService run-time environment.
600
601## Starting the MicroServices
602
603First fire up the Orchestrator QEWD Docker Container, eg:
604
605 docker run -it --name orchestrator --rm --net qewd-net -p 8080:8080 -v ~/microserviceExample:/opt/qewd/mapped rtweed/qewd-server
606
607Note: you may need to add sudo to the start of this command, depending on how you configured your Docker environment.
608
609If you�re using a Raspberry Pi, simply substitute the Docker container name *rtweed/qewd-server-rpi*, eg:
610
611 docker run -it --name orchestrator --rm --net qewd-net -p 8080:8080 -v ~/microserviceExample:/opt/qewd/mapped rtweed/qewd-server-rpi
612
613
614By default, QEWD listens within the Docker Container on port 8080. The command examples above are mapping this to port 8080 on your host machine. If you want to use a different host port, just change the -p directive, eg to listen on port 3000:
615
616 docker run -it --name orchestrator --rm --net qewd-net -p 3000:8080 -v ~/microserviceExample:/opt/qewd/mapped rtweed/qewd-server
617
618Your application's configuration and API files are mapped into the Docker Container using this parameter:
619
620 -v ~/microServiceExample:/opt/qewd/mapped
621
622**Always** map your QEWD-Up Application Folder to */opt/qewd/mapped*
623
624
625Secondly, in a separate terminal window, start up the *login_service* MicroService:
626
627 docker run -it --name login_service --rm --net qewd-net -v ~/microserviceExample:/opt/qewd/mapped -e microservice="login_service" rtweed/qewd-server
628
629Note how we�ve specified an environment variable to tell QEWD-Up which MicroService this is:
630
631 -e microservice="login_service"
632
633
634And finally, in a third terminal window, start up the *info_service* MicroService:
635
636 docker run -it --name info_service --rm --net qewd-net -v ~/microserviceExample:/opt/qewd/mapped -e microservice=�info_service" rtweed/qewd-server
637
638**IMPORTANT**: Make sure the MicroService name in the *--name** and *-e* parameters match exactly with each other and also with the MicroService name used in the *routes.json* file.
639
640[See here](https://github.com/robtweed/qewd/blob/master/up/docs/MicroServices.md) for more details on setting up QEWD-Up MicroServices.
641
642## Running Your APIs
643
644With everything running, you can now try out the APIs.
645
646First we must use the Login API. Using a REST client:
647
648 POST http://192.168.1.100:8080/api/login
649 Content-type: application/json
650
651 with a body payload containing
652
653 {
654 "username": "rob",
655 "password": "secret"
656 }
657
658**Note**: Change the IP address to match that of the host machine that is running the Orchestrator Service.
659
660You should see activity in the console logs for both the Orchestrator and Login QEWD instances, and back should come a response looking something like this:
661
662 {
663 "ok": true,
664 "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDI2Mz��etc�
665 }
666
667You're now ready to try the other API, but in order for it to work, you need to include the JWT that was returned to us as the token property in the */api/login* response. As described earlier, you need to add it as a *Bearer Token* in the HTTP *Authorization* request header: *ie* the *Authorization* header value must be the word *Bearer * followed by the JWT value.
668
669So try this:
670
671
672 GET http://192.168.1.100:8080/api/info/info
673 Content-type: application/json
674 Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDI2Mz��etc
675
676
677and you should get the response:
678
679 {
680 "info": {
681 "microService": "info_service",
682 "arch": "x64",
683 "platform": "linux",
684 "versions": {
685 "http_parser": "2.8.0",
686 "node": "10.12.0",
687 "v8": "6.8.275.32-node.35",
688 "uv": "1.23.2",
689 "zlib": "1.2.11",
690 "ares": "1.14.0",
691 "modules": "64",
692 "nghttp2": "1.34.0",
693 "napi": "3",
694 "openssl": "1.1.0i",
695 "icu": "62.1",
696 "unicode": "11.0",
697 "cldr": "33.1",
698 "tz": "2018e"
699 },
700 "memory": {
701 "rss": 52363264,
702 "heapTotal": 19120128,
703 "heapUsed": 9825976,
704 "external": 177816
705 }
706 },
707 "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDI3MjA4MzcsI....etc�
708 }
709
710Notice how the response includes a new, updated JWT, returned as a property named *token*.
711
712
713## Inspecting the JWT
714
715One of the features of JWTs is that you can decode them and see what they contain. Try copying the JWT value returned in the *token* property of the */api/login* response. Then paste it into the JWT Debugging window at *https://jwt.io*. Look in the *Decoded* panel at the *PAYLOAD* section and you'll see something like:
716
717 {
718 "exp": 1544607641,
719 "iat": 1544606441,
720 "iss": "qewd.jwt",
721 "application": "login_service",
722 "timeout": 1200,
723 "qewd": "9585569d6b0acef7b320ca5f200a47ec99b55a29f7808f3d84a937d138497a123d67174a7e56fca9b1626ed040c27f2feef094b25e522ec47cfb3152784fe292edc5d410bfe5f661bb7b2b31779c86e1e747b98741f7175b8671a1953fae0542",
724 "userText": "Welcome Rob",
725 "username": "rob"
726 }
727
728Some of these JWT claims (or properties) are created and maintains internally within QEWD, but you should recognise three that we created in our *login/handler.js* module's logic:
729
730 jwt.userText = 'Welcome Rob';
731 jwt.username = username;
732 jwt.timeout = 1200;
733
734**Note**: although you can read the contents of a JWT, you can't modify its contents without knowing the secret that is used to digitally sign the JWT.
735
736
737## How JWTs Protect Your APIs
738
739Without you needing to write anything other than the login credential checking, QEWD protects access to your APIs automatically via the JWT it creates and maintains for you across the MicroServices. The following examples will demonstrate how this works:
740
741Try sending the */api/login* request with invalid username/password credentials. You should get back an error response with an HTTP Status Code of 400 and a response payload of:
742
743 {
744 "error": "Invalid login"
745 }
746
747Notice that no JWT is returned with error responses, and without a JWT, you can't access the other APIs.
748
749For example, try sending the */api/info/info* request without an *Authorization* header. You should get back an error response with an HTTP Status Code of 400 and a response payload of:
750
751 {
752 "error": "Authorization Header missing or JWT not found in header (expected format: Bearer {{JWT}}"
753 }
754
755Because JWTs are digitally signed using a secret that your REST client doesn't have access to, you can't tamper with the JWT or make one up yourself. For example, try sending the */api/info/info* request with a valid *Authorization* header, but make a change to one or two of the characters within the JWT. You should get back an error response with an HTTP Status Code of 400 and a response payload of:
756
757 {
758 "error": "Invalid JWT: Error: Signature verification failed"
759 }
760
761Finally, see what happens if you try to use a valid but expired JWT in the Authorization header (ie wait for 20 minutes or more after */api/login*). You should get back an error response with an HTTP Status Code of 400 and a response payload of:
762
763 {
764 "error": "Invalid JWT: Error: Token expired"
765 }
766
767
768## Installing Additional Node.js Modules
769
770if you need to use additional Node.js modules (ie ones not automatically included with QEWD itself) in any of your application's MicroServices, you can do this by adding the file *install_modules.json*, eg:
771
772 ~/microserviceExample
773 |
774 |� install_modules.json
775 |
776 |� configuration
777 | |
778 | |� routes.json
779 | |
780 | |� config.json
781 |
782
783This file should contain a JSON array, listing the names of the modules you want loaded from NPM. For example:
784
785 [
786 "a-find",
787 "body-parser-xml",
788 "multer"
789 ]
790
791The modules in this file are loaded from NPM when one of your MicroService Containers is next started. Modules are saved into a *node_modules* folder in your mapped host file volume. As a result, the next time(s) you start/restart any of your MicroService Containers, your additional modules are already available for use and don't need to be reloaded.
792
793If you later modify your *install_modules.json* file and add new modules, they will be loaded from NPM when a MicroService Container is next restarted.
794
795So, after starting one of your MicroServices, you'll see the *node_modules* folder that will have been created, eg:
796
797 ~/microserviceExample
798 |
799 |� install_modules.json
800 |
801 |� node_modules
802 | |
803 | a_find
804 | ...etc
805 |
806 |� configuration
807 | |
808 | |� routes.json
809 | |
810 | |� config.json
811
812
813# Native QEWD Monolith
814
815## Pre-requisites
816
817To use this mode, you must first install Node.js (version 8 or later is recommended) and the [YottaDB](https://yottadb.com/) database. QEWD-Up's Native mode is designed to run on Linux or Unix platforms (including Virtual Machines).
818
819Don't worry if you've never installed Node.js or YottaDB before: you can simply run this automated script on a new Linux machine or VM:
820
821 cd ~
822 wget https://raw.githubusercontent.com/robtweed/qewd/master/installers/install_ydb_and_node.sh
823 source install_ydb_and_node.sh
824
825This script file will first install something called NVM and then use that to install the latest version of Node.js 10.x. It also installs the latest version of YottaDB and configures it ready for use with QEWD.
826
827
828## Setting Up QEWD-Up
829
830Create a folder for your application/suite of APIs. I'm going to name my example *nativeExample* (eg using the file path *~/nativeExample*). Also create sub-folders beneath it as shown below (use the sub-folder names exactly as shown):
831
832
833 ~/nativeExample
834 |
835 |� configuration
836 |
837 |� apis
838 |
839
840In the configuration folder, create a text file named *routes.json*:
841
842
843 ~/nativeExample
844 |
845 |� configuration
846 | |
847 | |� routes.json
848 |
849 |� apis
850 |
851
852
853### The *routes.json* File
854
855This file contains the definitions of your REST routes, expressed as a JSON array of route objects. Each route is described in terms of:
856
857- the URI path
858- the HTTP method
859- the name of the handler module that will perform the work of this API
860
861*eg* here�s the definition of a single API:
862
863 [
864 {
865 "uri": "/api/info",
866 "method": "GET",
867 "handler": "getInfo"
868 }
869 ]
870
871You can describe as many routes as you wish in this file. The syntax MUST be strict JSON - ie all property names and text-string values must be double-quoted.
872
873
874### The *config.json* File
875
876By Default, QEWD-Up applies a set of sensible default settings to the QEWD environment that will run your APIs. If the defaults are satisfactory, then you don't need to create a *config.json* file. If you want different settings for QEWD (eg a larger Worker pool size), then here's what to do:
877
878Create a text file named *config.json* within your configuration sub-folder, ie:
879
880 ~/nativeExample
881 |
882 |� configuration
883 | |
884 | |� routes.json
885 | |
886 | |� config.json
887 |
888 |� apis
889 |
890
891
892The *config.json* file provides QEWD-Up with instructions on how to configure QEWD. As its file extension implies, its contents must be correctly-structured JSON. Here's a simple example that increases the QEWD Worker pool size from the default of 2 to 4, and tells QEWD's integrated Express Web Server to listen on port 3000:
893
894 {
895 qewd: {
896 poolSize: 4,
897 port: 3000
898 }
899 }
900
901
902## Defining Your API Handlers
903
904You define your API Handlers within the *apis* folder. First create a sub-folder for each *handler* property name that you defined in the *routes.json* file (see above). For example, using the *routes.json* example above, you'd create:
905
906 ~/nativeExample
907 |
908 |� configuration
909 | |
910 | |� routes.json
911 |� apis
912 | |
913 | |� getInfo
914 |
915
916
917Within each handler sub-folder, you now create a module file that defines the code that will be invoked when the API route is requested. This module file can be named either *handler.js* or *index.js". For example:
918
919 ~/nativeExample
920 |
921 |� configuration
922 | |
923 | |� routes.json
924 |� apis
925 | |
926 | |� getInfo
927 | |
928 | |� handler.js
929
930
931
932Here's an example *handler.js* file for the *getInfo* API:
933
934 module.exports = function(args, finished) {
935 finished({
936 info: {
937 server: 'Qewd-Up Native',
938 arch: process.arch,
939 platform: process.platform,
940 versions: process.versions,
941 memory: process.memoryUsage(),
942 }
943 });
944 };
945
946If you�ve written QEWD REST handlers, you�ll recognise this format.
947
948In summary, simply export a function with 2 arguments:
949
950- **args**: an object that contains the content of your REST request, including its path, headers, HTTP method, any querystring values and any body payload
951
952- **finished**: the QEWD function that you use to end your handler. This function releases the QEWD worker process that handled your module back to QEWD's available worker pool, and tells QEWD to return the object you provide as its argument as a JSON response to the REST client that sent the original request.
953
954
955## Create a *package.json* File
956
957Create a file named package.json in your top-level application directory, eg in our case:
958
959 ~/nativeExample
960 |
961 |� package.json
962 |
963 |� configuration
964 | |
965 | |� routes.json
966 |� apis
967 | |
968 | |� getInfo
969 | |
970 | |� handler.js
971
972The easiest thing is to copy the example below and use it as a template for your package.json:
973
974 {
975 "name": "qewd-up",
976 "version": "1.0.0",
977 "description": "Automated QEWD Builder",
978 "author": "Rob Tweed <rtweed@mgateway.com>",
979 "scripts": {
980 "start": "node node_modules/qewd/up/run_native"
981 },
982 "dependencies": {
983 "qewd": "",
984 "qewd-transform-json": "",
985 "git-clone": "^0.1.0"
986 }
987 }
988
989If your handler modules have additional *dependencies*, add them to the package.json, otherwise just use the template shown above and leave it unchanged.
990
991
992## Starting QEWD-Up
993
994You're now ready to start up QEWD-Up.
995
996First you need to use NPM to install everything that�s needed by QEWD-Up and QEWD itself. When you installed Node.js, NPM will have also been installed automatically.
997
998This is a one-off step that you don�t need to repeat (unless you add more dependencies to your handlers later):
999
1000 cd ~/nativeExample
1001 npm install
1002
1003Once that�s finished, you�re ready to go! Just type:
1004
1005 npm start
1006
1007
1008QEWD is now ready and you can try out your APIs.
1009
1010
1011## Running Your APIs
1012
1013Now you can try out your API(s). If you used the default QEWD-Up configuration, QEWD will be listening on port 8080. So, point a browser at:
1014
1015 http://192.168.1.100:8080/api/info
1016
1017 (change the IP address as appropriate)
1018
1019and back should come the results from your API handler module, eg:
1020
1021 {
1022 "info": {
1023 "server": "Qewd-Up Native",
1024 "arch": "x64",
1025 "platform": "linux",
1026 "versions": {
1027 "http_parser": "2.8.0",
1028 "node": "10.12.0",
1029 "v8": "6.8.275.32-node.35",
1030 "uv": "1.23.2",
1031 "zlib": "1.2.11",
1032 "ares": "1.14.0",
1033 "modules": "64",
1034 "nghttp2": "1.34.0",
1035 "napi": "3",
1036 "openssl": "1.1.0i",
1037 "icu": "62.1",
1038 "unicode": "11.0",
1039 "cldr": "33.1",
1040 "tz": "2018e"
1041 },
1042 "memory": {
1043 "rss": 52396032,
1044 "heapTotal": 11780096,
1045 "heapUsed": 7756656,
1046 "external": 28323
1047 }
1048 }
1049 }
1050
1051That�s it - your API is up and working!
1052
1053You can now add more routes and their associated handler methods. If you do, you must restart QEWD-Up.
1054
1055To stop your running QEWD instance, just type CTRL & C.
1056
1057To restart QEWD-Up, just type *npm start* again.
1058
1059
1060## Installing Additional Node.js Modules
1061
1062if you need to use additional Node.js modules (ie ones not automatically included with QEWD itself), you can do this by adding them to the *package.json* **dependencies**. For example:
1063
1064 {
1065 "name": "qewd-up",
1066 "version": "1.0.0",
1067 "description": "Automated QEWD Builder",
1068 "author": "Rob Tweed <rtweed@mgateway.com>",
1069 "scripts": {
1070 "start": "node node_modules/qewd/up/run_native"
1071 },
1072 "dependencies": {
1073 "qewd": "",
1074 "qewd-transform-json": "",
1075 "git-clone": "^0.1.0",
1076 "a-find": "",
1077 "multer": ""
1078 }
1079 }
1080
1081
1082
1083