UNPKG

51.9 kBMarkdownView Raw
1<img src="https://raw.githubusercontent.com/jota-one/drosse/master/Drosse.svg" style="width:350px;max-width:100%;margin: 25px 0;"/>
2
3## What is it ?
4> Drosse is the last mock server you'll ever need.
5
6## Table of contents
7* [Installation](#installation)
8* [Usage](#usage)
9 * [As npm dependency in your node project](#as-npm-dependency-in-your-node-project)
10 * [As a global npm package](#as-a-global-npm-package)
11 * [As a Docker image](#as-a-docker-image)
12* [The routes.json file](#the-routesjson-file)
13 * [The DROSSE object](#the-drosse-object)
14 * [Throttling](#throttling)
15 * [Proxies](#proxies)
16 * [Advanced proxy settings](#advanced-proxy-settings)
17 * [Assets](#assets)
18 * [Templates](#templates)
19* [Inline mocks](#inline-mocks)
20* [Static mocks (in separated files)](#static-mocks-in-separated-files)
21* [Services (aka dynamic mocks)](#services-aka-dynamic-mocks)
22 * [The service file](#the-service-file)
23* [Drosse db API](#drosse-db-api)
24 * [Identify your documents](#identify-your-documents)
25 * [Reference documents](#reference-documents)
26 * [API](#api)
27* [The .drosserc.js file (configure your Drosse)](#the-drossercjs-file-configure-your-drosse)
28 * [All configuration keys](#all-configuration-keys)
29 * [Custom middlewares](#custom-middlewares)
30* [Endpoints scraping](#endpoints-scraping)
31 * [Static scraping](#static-scraping)
32 * [Dynamic scraping](#dynamic-scraping)
33* [The CLI](#the-cli)
34* [Features](#features)
35* [In progress](#in-progress)
36* [Future features](#future-features)
37
38## Installation
39### As [npm](https://www.npmjs.com/package/@jota-one/drosse) dependency in your node project
40
411. Simply install it as a dev dependency of the project you want to mock.
42```
43npm install --save-dev @jota-one/drosse
44```
452. Define a script in your `package.json` file for simpler usage
46```json
47{
48 "name": "my-node-project",
49 "scripts": {
50 "mock-server": "npx drosse serve path/to/mocks-directory"
51 },
52 "devDependencies": {
53 "@jota-one/drosse": "^1.0.0"
54 }
55}
56```
57
58### As a global [npm](https://www.npmjs.com/package/@jota-one/drosse) package
591. Install drosse globally.
60```
61npm i -g @jota-one/drosse
62```
63
642. Run it via the `serve` command and your drosse folder as root via the `-r` param or directly as a value after the `serve` command.
65```
66drosse serve -r /path/to/my/my/mocks
67```
68or
69```
70drosse serve /path/to/my/my/mocks
71```
72
73### As a [Docker image](https://hub.docker.com/r/jotaone/drosse)
74You can use a docker image if you don't want to install nodejs runtime on your machine.
75**Note** though that you'll have to map your local folder to the container's `data` volume as well as the port your drosse is configured on.
76
771. Pull the docker image on your computer
78```
79docker pull jotaone/drosse
80```
81
822. Run the container and map your mocks' path and port
83```
84docker run -v /path/to/my/mocks:/data -p 8000:8000 jotaone/drosse
85```
86
87
88## Usage
89You need a directory where you will store all your mocks definitions.
901. Create a directory anywhere in your project repository (or anywhere else).
912. Update the `package.json` script you just added in the Installation phase to target your new mocks directory.
923. In your mocks directory, create a `routes.json` file. This file will hold every single mocked route of your server.
934. In the same directory, you can also create a `.drosserc.js` file. This file allows you to configure your mock server ([see below](#drosserc)). It's optional but you will very likely need it.
94
95Yes there are a couple of things to do yourself. But you're a developer, right? You know how to edit a JSON file or a JS file. In the upcoming releases, we will add a CLI and a UI, don't worry!
96
97Let's focus first on these 2 files.
98
99## The routes.json file
100This file is mandatory but you can customize its name in the `.drosserc.js` file ([see below](#drosserc)). This is where you define all the routes you want to mock. Each route is defined as a tree, with _slash_ as separator.
101
102> :dizzy_face: Woot ? Tree ? Slash ? I just want to mock a couple of routes...
103
104Ooook, an example worth a thousand sentences of bad explanation. Let's say you want to mock these 2 routes:
105
106```
107GET /api/users
108GET /api/users/:id
109```
110
111You would create a _tree_ in your `routes.json` file like this:
112
113```json
114{
115 "api": {
116 "users": {
117 ":id": {
118
119 }
120 }
121 }
122}
123```
124
125That's what we mean by "a tree". We consider that an API can be completely described in such a structure.
126
127> :smirk: Okay, but where do the actual mocks go ? And if have a GET **AND** a POST on `/api/users` ?
128
129Good questions, thanks for asking.
130
131### The DROSSE object
132That's where the `DROSSE` object comes in and saves our souls. Look at this:
133
134```json
135{
136 "api": {
137 "users": {
138 "DROSSE": {
139 "get": {},
140 "post": {}
141 },
142 ":id": {
143 "DROSSE": {
144 "get": {}
145 }
146 }
147 }
148 }
149}
150```
151
152Everything happens inside the `DROSSE` object. You can insert a `DROSSE` object anywhere in the tree. A `DROSSE` object can contain HTTP verbs, like in the above example and a couple of other keys (more on these later).
153
154> :triumph: I WANT TO MOCK MY ROUTES!!! WHERE DO I PUT MY MOCKED CONTENTS???
155
156There we go! You can mock your datas in 3 different ways:
1571. directly inside the `routes.json` file, using the `body` key (see [inline mocks](#inline-mocks)).
1582. in a static JSON file with a constrained name (see [Static mocks](#static-mocks))
1593. in a dynamic JS file (we consider it a service) with a constrained name (see [Services](#dynamic-mocks))
160
161But before we move to the mock contents, let's have a look to the other stuffs you can put into the `DROSSE` object.
162
163### Throttling
164A must have feature if you want to detect your race condition and test your lovely loading animations. You can throttle any endpoint by adding the `throttle` configuration object into it. Just like this:
165
166```json
167{
168 "api": {
169 "users": {
170 "DROSSE": {
171 "get": {
172 "throttle": {
173 "min": 1000,
174 "max": 2000
175 }
176 },
177 "post": {}
178 },
179 ":id": {
180 "DROSSE": {
181 "get": {}
182 }
183 }
184 }
185 }
186}
187```
188
189In the example above, the route `GET /api/users` will be throttled between 1 and 2 seconds (randomly chosen in the boundaries).
190
191:cherries: Of course, you can put your throttle at any level of your routes tree and it will be inherited in all the sub routes that don't have their own `throttle` configuration.
192
193```json
194{
195 "api": {
196 "DROSSE": {
197 "throttle": {
198 "min": 5000,
199 "max": 10000
200 }
201 },
202 "users": {
203 "DROSSE": {
204 "get": {
205 "throttle": {
206 "min": 1000,
207 "max": 2000
208 }
209 },
210 "post": {}
211 },
212 ":id": {
213 "DROSSE": {
214 "get": {}
215 }
216 }
217 }
218 }
219}
220```
221
222Here all the routes under `/api` will be throttled between 5 and 10 seconds, except the `GET /api/users` that keeps its own 1-2 seconds throttling.
223
224### Proxies
225
226In Drosse, proxies let you escape from your mock-server for a while. How does it work ?
227
228Anywhere in your routes `tree`, you can define a proxy (inside the `DROSSE` object, like always). All the routes matching the path where your proxy is defined, but NOT matching a subsequent route will be proxied to... your proxy. Okay this sentence is really f***-up. Let's have a look to the `routes.json`.
229
230```json
231{
232 "api": {
233 "users": {
234 "DROSSE": {
235 "get": {
236 "throttle": {
237 "min": 1000,
238 "max": 2000
239 }
240 },
241 "post": {}
242 },
243 ":id": {
244 "DROSSE": {
245 "get": {}
246 }
247 }
248 },
249 "countries": {
250 "DROSSE": {
251 "proxy": "https://restcountries.eu/rest/v2"
252 }
253 }
254 }
255}
256```
257
258In the above example, any request made on `http://localhost:8000/api/countries` will be done instead on `https://restcountries.eu/rest/v2`. The subsequent path will be applied as well.
259
260So `http://localhost:8000/api/countries/name/indonesia` will be proxied to `https://restcountries.eu/rest/v2/name/indonesia` and return the expected result.
261
262Of course you can still define subroutes in a proxied path. They will take precedence on the proxy. Let's change our `countries` block like this:
263
264```json
265 "countries": {
266 "DROSSE": {
267 "proxy": "https://restcountries.eu/rest/v2"
268 },
269 "name": {
270 "switzerland": {
271 "DROSSE": {
272 "get": {
273 "body": {
274 "name": "Switzerland",
275 "description": "Best country in the World!"
276 }
277 }
278 }
279 }
280 }
281 }
282```
283
284If we call `http://localhost:8000/api/countries/name/switzerland`, Drosse will not proxy the request as there is a fully qualified path in our routes definition. We will then get the response defined just above and not a proxied response.
285
286#### Advanced proxy settings
287
288If you need advanced proxy settings, you can use an object instead of a string, like this:
289```json
290 "countries": {
291 "DROSSE": {
292 "proxy": {
293 "target": "https://localhost:8081/api",
294 "secure": false,
295 "ws": true
296 }
297 }
298```
299
300As we use [node-http-proxy](https://www.npmjs.com/package/http-proxy) in the background, please refer to its documentation for [all available options](https://github.com/http-party/node-http-proxy#options).
301
302### Assets
303
304You can use the `assets` property in the `DROSSE` object to tell a path to serve only static contents. Behind the scene, it will apply the `express.static` middleware to this path. Let's check the following example:
305
306```json
307{
308 "content": {
309 "DROSSE": {
310 "assets": true
311 },
312 "imgs": {
313 "background_*.jpg": {
314 "DROSSE": {
315 "assets": "content/images/background_mocked.png"
316 }
317 }
318 }
319 }
320}
321```
322
323In this example, all calls done to `/content` will be done statically on the `assets/content` directory. For example, if you call `http://localhost:8000/content/fonts/myfont.woff2`, Drosse will look up for a file in `[your mocks root]/assets/content/fonts/myfont.woff2` and serve it statically.
324
325The example reveals another feature: you can rewrite the path through the `assets` property. If you call `http://localhost:8000/content/imgs/background_whatever.jpg`, Drosse will statically serve the `[your mocks root]/assets/content/imgs/background_whatever.jpg` file.
326
327:fire: You can redefine this `assets` directory name in your `.drosserc.js` file ([see below](#drosserc)).
328
329<a name="templates"></a>
330### Templates
331
332A small yet very handsome feature. Templates help you to DRY your mocked endpoints. In Drosse, a template is a simple function that takes a content and transforms it to something else. Let's take an example:
333
334```json
335{
336 "api": {
337 "DROSSE": {
338 "template": "response"
339 },
340 "products": {
341 "v1": {
342 "DROSSE": {
343 "template": "responseV1"
344 }
345 }
346 },
347 "other": {},
348 "routes": {}
349 }
350}
351```
352
353When passing a `template` to a node, you tell Drosse to apply this template to all endpoint results from this node path and all subnodes. Except if you redefine another template. In that case it will take precedence. In our example, any call to `/api`, `/api/products`, `/api/other` or `/api/routes` will have their response passed to the `response` template. But a call to `/api/products/v1` will have its response passed to `responseV1` template.
354
355To use a template, you need to create a JS file, store it somewhere in your `[mocks root]` directory and reference it in your `.drosserc.js` file. Here is an example of `.drosserc.js` file with our 2 templates registered:
356
357```js
358// .drosserc.js
359const response = require('./templates/response')
360const responseV1 = require('./templates/responseV1')
361
362module.exports = {
363 name: 'My awesome app',
364 port: 8004,
365 templates: { response, responseV1 }
366}
367```
368
369Here we stored the templates in a `templates` directory, but you can load them from wherever you prefer. You simply need to register each template function in a `templates` property.
370
371
372Here is how the `response` and `responseV1` template could look like:
373
374```js
375// response.js
376module.exports = function (response) {
377 return {
378 version: 'v2',
379 data: response
380 }
381}
382```
383
384```js
385// responseV1.js
386module.exports = function (response) {
387 return {
388 version: 'v1',
389 ...response
390 }
391}
392```
393
394You can also use these templates to perform intermediate transformations in your services (see [dynamic mocks section](#dynamic-mocks)) as they are also simple JS functions...
395
396:fire: You need to register your templates in the `.drosserc.js` file only if you want to use them in the `routes.json` file.
397
398<a name="inline-mocks"></a>
399## Inline mocks
400
401Let's focus first on the `body` key, by far the simplest but by far the less cool. If you calm down, you'll be allowed to know about the 2 other solutions. Here's how you can mock your routes with inlined mocks.
402
403```json
404{
405 "api": {
406 "users": {
407 "DROSSE": {
408 "get": {
409 "body": [
410 {"id": 1, "name": "Jorinho", "premium": false},
411 {"id": 2, "name": "Tadai", "premium": true}
412 ]
413 },
414 "post": {
415 "body": {"success": true}
416 }
417 },
418 ":id": {
419 "DROSSE": {
420 "get": {
421 "body": {"id": 1, "name": "Jorinho", "premium": false}
422 }
423 }
424 }
425 }
426 }
427}
428```
429
430The above JSON is a fully working `routes.json` file. If you run your mock-server with this file, you will see something like this in your console (amongst other things):
431
432```bash
4334:26:27 PM -> GET /api/users/:id
4344:26:27 PM -> GET /api/users
4354:26:27 PM -> POST /api/users
436
4374:26:27 PM App Example JSON app running at:
4384:26:27 PM - http://localhost:8000
439```
440
441Note that the routes are defined in the right order to make sure that a less precise route won't take over a more precise one. Let's create a new route to illustrate this better:
442
443```json
444{
445 "api": {
446 "users": {
447 "DROSSE": {
448 "get": {
449 "body": [
450 {"id": 1, "name": "Jorinho", "premium": false},
451 {"id": 2, "name": "Tadai", "premium": true}
452 ]
453 },
454 "post": {
455 "body": {"success": true}
456 }
457 },
458 ":id": {
459 "DROSSE": {
460 "get": {
461 "body": {"id": 1, "name": "Jorinho", "premium": false}
462 }
463 }
464 },
465 "premiums": {
466 "DROSSE": {
467 "get": {
468 "body": [{"id": 2, "name": "Tadai"}]
469 }
470 }
471 }
472 }
473 }
474}
475```
476We defined a new route corresponding to this one `GET /api/users/premiums`. Of course, if Drosse was stupid it would define it in the same order as what we did in the `routes.json` file, which would make the route unreachable, because it would always be captured by the `GET /api/users/:id`, passing "premiums" as the `:id` parameter. Let's see what happens if we reload our mock server.
477
478```bash
4794:40:59 PM -> GET /api/users/premiums
4804:40:59 PM -> GET /api/users/:id
4814:40:59 PM -> GET /api/users
4824:40:59 PM -> POST /api/users
483
4844:40:59 PM App Example JSON app running at:
4854:40:59 PM - http://localhost:8000
486```
487
488:tada::tada::tada: The `/premiums` route was declared before the generic `/:id` route! Conclusion: you don't have to worry about sorting your routes when you define them inside `routes.json`.
489
490> :open_mouth: That's awesome! It calmed me down totally... I'm ready to know more about the 2 other ways to mock my stuffs!
491
492<a name="static-mocks"></a>
493## Static mocks (in separated files)
494
495As you've probably noticed, the inline mocks are not that dynamic... For instance, if we take the `GET /api/users/:id` route, you can call it with any value for `:id`, you will always get the same response. Although it can be enough for most usecases, sometimes we want a little bit more.
496
497That's where the so-called static mocks can help you. Where you have only one mock possibility with the inline (`body`) mock even for parametrized routes, static mocks offer you the possibility to have a different mock for each value of each parameter!
498
499That is why these mocks are stored in separated files. It would otherwise bloat your `routes.json` file.
500
501To define a `static mock`, simply set the `static` property of your `DROSSE` object to `true`. Let's take the previous example and replace the parametrized route inline mock by a static mock:
502
503```json
504{
505 "api": {
506 "users": {
507 "DROSSE": {
508 "get": {
509 "body": [
510 {"id": 1, "name": "Jorinho", "premium": false},
511 {"id": 2, "name": "Tadai", "premium": true}
512 ]
513 },
514 "post": {
515 "body": {"success": true}
516 }
517 },
518 ":id": {
519 "DROSSE": {
520 "get": {
521 "static": true,
522 "extensions": ["json"]
523 }
524 }
525 }
526 }
527 }
528}
529```
530
531With such a definition, when you call `GET /api/users/65?withDetails=1`, drosse will look for a specific file in the `static` subdirectory of your mocks directory.
532
533:fire: You can redefine this `static` directory name in your `.drosserc.js` file ([see below](#drosserc)).
534
535Drosse will look for different filenames, from the more precise to the more generic until it finds one that matches. Let's keep the above example and see which filenames will be looked for:
536
537```
538api.users.65.get&&withDetails=1.json
539api.users.65.get.json
540api.users.65.json
541api.users.{id}.json
542```
543
544You can pass more than one extension to check for. By default, if you don't pass the `extensions` key next to the `static` key, it will fallback to `["json"]`. If you search
545for other file types, like images for example, Drosse will automatically return the file instead of a JSON response.
546
547If you have a route with several path parameters, drosse will ignore them from left to right. Example, for this route:
548```
549GET /api/users/:id/posts/:type
550```
551
552Assuming that you have 2 types of posts, `unread` and `read` and a big quantity of users, it's more convenient to be able to define a mocked list of `read` posts and another mocked list of `unread` posts, independently of the user. For that usecase you can then create only 2 files in your `static` directory:
553
554```
555api.users.{id}.posts.read.get.json
556api.users.{id}.posts.unread.get.json
557```
558
559:fire: If you are not sure of the precedence for a given route, just try and check the drosse console. It will log each failed attempts.
560
561If we try to call `GET /api/users/3` and we have defined the following static mocks files in our `static` directory.
562
563```
564api.users.1.json
565api.users.2.json
566api.users.{id}.json
567```
568
569```bash
5701:11:29 AM App Example JSON static app running at:
5711:11:29 AM - http://localhost:8000
5721:11:29 AM - http://172.22.22.178:8000
573
5741:11:29 AM Mocks root: /some/path/mymocks
575
5761:17:27 AM loadStatic: tried with [/some/path/mymocks/static/api.users.3.get.json]. File not found.
5771:17:27 AM loadStatic: tried with [/some/path/mymocks/static/api.users.3.json]. File not found.
5781:17:27 AM loadStatic: tried with [/some/path/mymocks/static/api.users.{id}.get.json]. File not found.
579```
580You can see above that the system has first tried with the most precise `api.users.3.get.json` (resolved parameter + verb). Then it tries the same without verb (`api.users.3.json`). As it still fails, it tries without resolving the parameter, but again with the verb (`api.users.{id}.get.json`) and finally find a corresponding mock file with `api.users.{id}.json`. Of course this last one is not logged as it was found.
581
582:bulb: if Drosse really doesn't find any file corresponding to the requested endpoint, it will give an ultimate look in the `scraped` static files. More on this in the __endpoints scraping__ dedicated section ([-> right here](#endpoints-scraping)).
583
584<a name="dynamic-mocks"></a>
585## Services (aka dynamic mocks)
586
587With the services, we cross some sort of line between pure mocking and an actual alternative backend for our frontend app. But sometimes it can be really useful. For example when you want to test interactivity in your app, or you don't have the backend yet because you're on a big project with separated teams and the backend will be implemented after the frontend, but you still have to deliver a working frontend at the same time as the backend, etc.
588
589Drosse provides everything you need to implement an interactive mocked backend and let you focus on your frontend usecases.
590
591To define a service, you have to do pretty much the same as to define a static mock. Look at this example where we replace the previous "POST users" inline mock by a service:
592
593```json
594{
595 "api": {
596 "users": {
597 "DROSSE": {
598 "get": {
599 "body": [
600 {"id": 1, "name": "Jorinho", "premium": false},
601 {"id": 2, "name": "Tadai", "premium": true}
602 ]
603 },
604 "post": {
605 "service": true
606 }
607 },
608 ":id": {
609 "DROSSE": {
610 "get": {
611 "static": true
612 }
613 }
614 }
615 }
616 }
617}
618```
619
620See ? From the `routes.json` file, it's quite a piece of cake :cake: !
621
622Now of course, there is no magic (yet!) here. You have to create a file with the proper name and put it in the `services` subdirectory of your mocks directory.
623
624:fire: Like for the others subdirectories, you can redefine this `services` directory name in your `.drosserc.js` file ([see below](#drosserc)).
625
626To name your file, the rule is even simpler as for the static files. Here you just take in account the non-parameter nodes of your route path. In our case we don't have any parameter node. Our `POST /api/users` route will resolve into a `api.users.post.js` service file.
627
628Let's just take another example to make it clear. For example for a `PUT /api/users/:id/superpowers/:name` the service file will be `api.users.superpowers.put.js`.
629
630Now let's have a look inside these service files.
631
632### The service file
633
634A service file must export a function that takes one object argument.
635
636```js
637module.exports = function ({ req, res, db }) {
638 // a lot of cool stuffs...
639}
640```
641
642As you can see, the object argument gives you access to the well known `req` and `res` objects from Express. With those two and the full power of javascript, you can already do more than what you will ever need in a mock-server.
643
644:star: The return value of your function will be passed to the associated route response (optionally modified by a template, see later).
645
646Let's take a full example.
647
6481. You call `POST /api/users` and pass this payload: `{ name: "John" }`.
6492. The function in the file `services/api.users.post.js` is executed.
6503. Let's say it contains this code:
651
652
653```js
654module.exports = function ({ req, res, db }) {
655 const payload = req.body
656 // do whatever you want with your payload
657 return { added: payload.name }
658}
659```
660
6614. Your call returns: `{ added: "John" }`.
6625. That's all folks!
663
664:fire: But there is more! The `db` object gives you access to the embbedded Drosse database and its super-fancy API. This part requires a full chapter.
665
666## Drosse db API
667
668Drosse leverages [LokiJS](https://github.com/techfort/LokiJS) power to allow stateful interactive mocking. But don't worry, no need for database configuration or any complicated stuffs. Basically, LokiJS is a nosql document database (a bit like mongo, but way simpler).
669
670When you start your mock server for the first time, Drosse will create a database and store it in a `mocks.json` file in your `[mocks root]` directory. Yes! You've read it, it's a simple JSON file. You can open it in your IDE and see the content of your database. But don't modify it manually, it will probably break the database. To be precise, LokiJS is a in-memory database and it will simply dump its content every 4 seconds in the `mocks.json` file.
671
672> :anguished: How do I update it, then ?
673
674LokiJS is collections-based. You can consider each collection as an array of objects, each object being a document.
675
676To provide some contents to your database, you need to create a `collections` directory in your `[mocks root]` directory. In this directory you will define your collections. A collection can be either:
677
678- A directory that contains JSON files. These files must contain a JSON object (not an array).
679- A JSON files that must contain an array of objects.
680
681In the end it's the same. Drosse will process either the directory or the big JSON file and insert it as a new collection full of documents in your database.
682
683:fire: You can redefine the `mocks.json` database file and the `collections` directory name in your `.drosserc.js` file ([see below](#drosserc)).
684
685By default, on each startup Drosse will check the `collections` directory and see if the collection already exists in the database. If not, it will import it. If the collection already exists, Drosse won't import it again; even if you added new files in the collection directory.
686
687If you want a fresh database, simply delete the `mocks.json` file and restart Drosse.
688
689> :sweat_smile: That's a bit violent! Is there a smoother way?
690
691You ask it, we provide.
692
693You can define a `shallowCollections` key in your `.drosserc.js` file. It must contain an array with collection names. All the collections listed in this array will be overriden on each Drosse startup.
694
695<a name="db-identify"></a>
696### Identify your documents
697One more thing. To make Drosse aware of how you identify each of your document, you should provide in each document a `DROSSE` property which contains an `ids` property, which itself contains a list of what you consider the document identifiers.
698
699It can be very useful to have different identifiers for your documents. As the identifiers don't have to be unique, it's not an issue. Don't forget that Drosse is a mock server. You aim to mock a real system that lives somewhere out there. And in the real world, it happens often that the same entity is retrieved through different ways. Imagine a Project entity. It could be retrieved by its unique ID, but maybe also by a customer unique code or whatever else.
700
701The real backend has probably different methods to fetch the project accordingly. But here we want to be fast and we don't care about all that. So we can store all the potential identifiers of a document in our `DROSSE.ids` array. It will look like this:
702
703```json
704{
705 "id": 1980,
706 "name": "Construction of a skyscraper",
707 "customer": {
708 "id": 888,
709 "name": "ACME corp",
710 "projectCode": "SKYSCRAPER-999"
711 },
712 "budget": 98000000,
713 "DROSSE": {
714 "ids": [1980, "SKYSCRAPER-999"]
715 }
716}
717```
718Like this, it will be easier to find our document and we won't have to ask ourselves which identifier was sent to our service.
719
720<a name="db-ref"></a>
721### Reference documents
722In the above example we have a customer inside a project. But what if we want to list all the customers in our app ? We could duplicate the customer informations into a `customers` collection, but that would mean that the customer's informations displayed in the project document are duplicated. Not good to maintain our mocks...
723
724Here come reference documents to the rescue!
725
726Assuming you have a `customers` collection with this customer document in it.
727
728```json
729{
730 "id": 888,
731 "name": "ACME corp",
732 "address": {
733 "street": "Undefined or null 1",
734 "zip": "00001",
735 "town": "North Pole City"
736 },
737 "activity": "Secretely conquer the world by not being evil... at first.",
738 "DROSSE": {
739 "ids": [1980, "SKYSCRAPER-999"]
740 }
741}
742```
743
744You can redefine your project like this:
745
746```json
747{
748 "id": 1980,
749 "name": "Construction of a skyscraper",
750 "customer": {
751 "collection": "customers",
752 "id": 888,
753 "projectCode": "SKYSCRAPER-999"
754 },
755 "budget": 98000000,
756 "DROSSE": {
757 "ids": [1980, "SKYSCRAPER-999"]
758 }
759}
760```
761
762The company name is not duplicated anymore.
763
764When you've fetched the project document, you can easily query the linked customer by calling the [`db.get.byRef()`](#db-get-byRef) method and pass it the `project.customer` object. Drosse will return the corresponding customer document. You can then overwrite `project.customer` with this result.
765
766
767### API
768
769Once your documents are stored in the database, here is how you can query them or even dynamically insert new documents programmatically. As you've maybe already read above in the [dynamic mocks section](#dynamic-mocks), when you define a service function, it takes an object as argument and this object contains a `db` property. This `db` property exposes the whole Drosse DB API. Let's have a look to it in detail.
770
771<a name="db-list-all"></a>
772**db.list.all(collection, cleanFields)**
773
774List all documents in a collection.
775| Argument | Required | Type | Description |
776|-------------------------|------------|-----------|---------------------------------|
777| collection | _Required_ | String | The collection name |
778| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
779
780Returns an _Array_ of documents.
781
782<a name="db-list-byId"></a>
783**db.list.byId(collection, id, cleanFields)**
784
785List all documents in a collection that have the provided identifier.
786| Argument | Required | Type | Description |
787|-------------------------|------------|-----------|---------------------------------|
788| collection | _Required_ | String | The collection name |
789| id | _Required_ | Mixed | A [document identifier](#db-identify) |
790| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
791
792Returns an _Array_ of documents.
793
794<a name="db-list-byFields"></a>
795**db.list.byFields(collection, fields, value, cleanFields)**
796
797List all documents in a collection having at least one of the provided fields that contains the provided value.
798| Argument | Required | Type | Description |
799|-------------------------|------------|-----------|---------------------------------|
800| collection | _Required_ | String | The collection name |
801| fields | _Required_ | String[] | A list of fields |
802| value | _Required_ | Mixed | A value to test for. Should be a string or number |
803| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
804
805Returns an _Array_ of documents.
806
807<a name="db-list-byField"></a>
808**db.list.byField(collection, field, value, cleanFields)**
809
810List all documents in a collection having the provided field that contains the provided value.
811| Argument | Required | Type | Description |
812|-------------------------|------------|-----------|---------------------------------|
813| collection | _Required_ | String | The collection name |
814| field | _Required_ | String | A field |
815| value | _Required_ | Mixed | A value to test for. Should be a string or number |
816| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
817
818Returns an _Array_ of documents.
819
820<a name="db-list-find"></a>
821**db.list.find(collection, query, cleanFields)**
822
823List all documents in a collection matching the provided query.
824| Argument | Required | Type | Description |
825|-------------------------|------------|-----------|---------------------------------|
826| collection | _Required_ | String | The collection name |
827| query | _Required_ | Object | A lokiJS query object |
828| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
829
830Returns an _Array_ of documents.
831
832<a name="db-list-where"></a>
833**db.list.where(collection, searchFn, cleanFields)**
834
835List all documents in a collection for which the searchFn callback returns a truthy value
836| Argument | Required | Type | Description |
837|-------------------------|------------|-----------|---------------------------------|
838| collection | _Required_ | String | The collection name |
839| searchFn | _Required_ | Function | A function that will be called for each document and take the document in argument. |
840| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
841
842Returns an _Array_ of documents.
843
844<a name="db-get-byId"></a>
845**db.get.byId(collection, id, cleanFields)**
846
847Find first document in a collection that have the provided identifier.
848| Argument | Required | Type | Description |
849|-------------------------|------------|-----------|---------------------------------|
850| collection | _Required_ | String | The collection name |
851| id | _Required_ | Mixed | A [document identifier](#db-identify) |
852| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
853
854Returns a document.
855
856<a name="db-get-byFields"></a>
857**db.get.byFields(collection, fields, value, cleanFields)**
858
859Find first document in a collection having at least one of the provided fields that contains the provided value.
860| Argument | Required | Type | Description |
861|-------------------------|------------|-----------|---------------------------------|
862| collection | _Required_ | String | The collection name |
863| fields | _Required_ | String[] | A list of fields |
864| value | _Required_ | Mixed | A value to test for. Should be a string or number |
865| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
866
867Returns a document.
868
869<a name="db-get-byField"></a>
870**db.get.byField(collection, field, value, cleanFields)**
871
872Find first document in a collection having the provided field that contains the provided value.
873| Argument | Required | Type | Description |
874|-------------------------|------------|-----------|---------------------------------|
875| collection | _Required_ | String | The collection name |
876| field | _Required_ | String | A field |
877| value | _Required_ | Mixed | A value to test for. Should be a string or number |
878| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
879
880Returns a document.
881
882<a name="db-get-find"></a>
883**db.get.find(collection, query, cleanFields)**
884
885Find first document in a collection matching the provided query.
886| Argument | Required | Type | Description |
887|-------------------------|------------|-----------|---------------------------------|
888| collection | _Required_ | String | The collection name |
889| query | _Required_ | Object | A lokiJS query object |
890| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
891
892Returns a document.
893
894<a name="db-get-where"></a>
895**db.get.where(collection, searchFn, cleanFields)**
896
897Find first document in a collection for which the searchFn callback returns a truthy value
898| Argument | Required | Type | Description |
899|-------------------------|------------|-----------|---------------------------------|
900| collection | _Required_ | String | The collection name |
901| searchFn | _Required_ | Function | A function that will be called for each document and take the document in argument. |
902| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
903
904Returns a document.
905
906<a name="db-get-byRef"></a>
907**db.get.byRef(refObj, dynamicId, cleanFields)**
908
909Find first document in a collection matching the provided query.
910| Argument | Required | Type | Description |
911|-------------------------|------------|-----------|---------------------------------|
912| refObj | _Required_ | Object | An object that contains a `collection` property and an `id` property. See [Ref documents](#db-ref) documentation. |
913| dynamicId | _Optional_ | Mixed | A [document identifier](#db-identify) |
914| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
915
916Returns a document.
917
918```js
919const getDetailedProject = projectId => {
920 const myProject = db.get.byId('projects', projectId)
921 myProject.customer = db.get.byRef(myProject.customer)
922 return myProject
923}
924
925const detailedProject = getDetailedProject(1980)
926```
927
928<a name="db-query-getMapId"></a>
929**db.query.getMapId(collection, fieldname, firstOnly)**
930
931Generate a hash to link a specific document field value to the document ids (or first id)
932| Argument | Required | Type | Description |
933|-------------------------|------------|-----------|---------------------------------|
934| collection | _Required_ | String | The collection name |
935| fieldname | _Required_ | String | A field |
936| firstOnly | _Optional_ | Boolean | If `true`, will consider only the first `id` found in the `DROSSE.ids` array. |
937
938Returns a correspondance hash with the chosen field value as key and the corresponding id as value.
939
940:warning: Be aware that if the chosen `fieldname` hasn't unique values for each document in collection, the later documents will overwrite the formers.
941
942<a name="db-query-chain"></a>
943**db.query.chain(collection)**
944
945Exposes the LokiJS chain method ([see LokiJS documentation](https://techfort.github.io/LokiJS/Collection.html) for more details)
946| Argument | Required | Type | Description |
947|-------------------------|------------|-----------|---------------------------------|
948| collection | _Required_ | String | The collection name |
949
950Returns a [LokiJS ResultSet](https://techfort.github.io/LokiJS/Resultset.html)
951
952<a name="db-query-clean"></a>
953**db.query.clean([...fields])**
954
955Creates a cleaning function that will remove all listed fields from the passed object
956| Argument | Required | Type | Description |
957|-------------------------|------------|-----------|---------------------------------|
958| ...fields | _Optional_ | ...String | Any number of fieldnames |
959
960Returns a function that takes a javascript Object as unique argument
961
962:fire: Even if no fields are passed, the function will be configured to remove the `reseserved words` from Drosse and Loki, aka: $loki, meta and DROSSE.
963
964:fire::fire: This function is used in all other methods to clean up the results and merges the optional `cleanFields` with the `reserved words`.
965
966<a name="db-insert"></a>
967**db.insert(collection, ids, payload)**
968
969Inserts a document in a collection
970| Argument | Required | Type | Description |
971|-------------------------|------------|-----------|---------------------------------|
972| collection | _Required_ | String | The collection name |
973| ids | _Required_ | Array | An array of identifiers for your new document (will be stored in `DROSSE.ids`) |
974| payload | _Required_ | Object | The document |
975
976Returns the inserted document
977
978<a name="db-update-byId"></a>
979**db.update.byId(collection, id, newValue)**
980
981Updates a document in a collection
982| Argument | Required | Type | Description |
983|-------------------------|------------|-----------|---------------------------------|
984| collection | _Required_ | String | The collection name |
985| id | _Required_ | Mixed | One of the document identifiers (from `DROSSE.ids`) |
986| newValue | _Required_ | Object | A hash with keys being of type `field.subfield.subsubfield` ([lodash.set](https://lodash.com/docs#set) is used to apply the changes) |
987
988Returns _nothing_
989
990<a name="db-update-subItem-append"></a>
991**db.update.subItem.append(collection, id, subPath, payload)**
992
993Insert (append) a new item in some of the identified document subItems list
994| Argument | Required | Type | Description |
995|-------------------------|------------|-----------|---------------------------------|
996| collection | _Required_ | String | The collection name |
997| id | _Required_ | Mixed | One of the document identifiers (from `DROSSE.ids`) |
998| subPath | _Required_ | String | A string of type `field.subfield.subsubfield` pointing to the field to alter |
999| payload | _Required_ | Object | The sub item to insert |
1000
1001Returns _nothing_
1002
1003
1004<a name="db-update-subItem-prepend"></a>
1005**db.update.subItem.prepend(collection, id, subPath, payload)**
1006
1007Insert (prepend) a new item in some of the identified document subItems list
1008| Argument | Required | Type | Description |
1009|-------------------------|------------|-----------|---------------------------------|
1010| collection | _Required_ | String | The collection name |
1011| id | _Required_ | Mixed | One of the document identifiers (from `DROSSE.ids`) |
1012| subPath | _Required_ | String | A string of type `field.subfield.subsubfield` pointing to the field to alter |
1013| payload | _Required_ | Object | The sub item to insert |
1014
1015Returns _nothing_
1016
1017<a name="db-update-byId"></a>
1018**db.remove.byId(collection, id)**
1019
1020Removes (delete) a document from a collection
1021| Argument | Required | Type | Description |
1022|-------------------------|------------|-----------|---------------------------------|
1023| collection | _Required_ | String | The collection name |
1024| id | _Required_ | Mixed | One of the document identifiers (from `DROSSE.ids`) |
1025
1026Returns _nothing_ or `false` if the document was not found.
1027
1028<a name="drosserc"></a>
1029## The .drosserc.js file (configure your Drosse)
1030This file holds your mock server general configuration. It's optional as all its keys have default values. It must simply export a configuration object.
1031
1032Here is a typical example of what it could contain.
1033```js
1034module.exports = {
1035 name: 'My mocks app',
1036 port: 8000
1037}
1038```
1039
1040### All configuration keys
1041| Key | Default value | Description |
1042|----------------------|---------------|-------------|
1043| `name` | **(empty)** | The name of your app. Mostly used to recognize it in your console or in [drosse UI](https://github.com/jota-one/drosse-ui). |
1044| `port` | **8000** | The port on which your mock server will run.<br>If not specified in `.drosserc.js` and already in use, Drosse will use the next available port if finds (8001, 8002, etc.) |
1045| `baseUrl` | **(empty)** | The base URL (ex. http://my.domain.com) for the routes |
1046| `basePath` | **(empty)** | A prefix (ex. /api/v2) that will be added to each route path |
1047| `routesFile` | **routes** | Name of the routes definition file. |
1048| `collectionsPath` | **collections** | Relative path to the loki collections directory from your mocks directory. |
1049| `shallowCollections` | **[]** | List of collections that should be recreated/overriden on each server restart. |
1050| `assetsPath` | **assets** | Relative path to the assets directory from your mocks directory. |
1051| `servicesPath` | **services** | Relative path to the services directory from your mocks directory. |
1052| `staticPath` | **static** | Relative path to the static files directory from your mocks directory. |
1053| `scraperServicesPath`| **scrapers** | Relative path to the scraper services files directory from your mocks directory. |
1054| `scrapedPath` | **scraped** | Relative path to the scraped files directory from your mocks directory. |
1055| `database` | **mocks.db** | Name of your loki database dump file. |
1056| `dbAdapter` | **LokiFsAdapter** | IO adapter to use for database persistence. |
1057| `middlewares` | **['morgan']** | List of global middlewares. Drosse provides 2 built-in middlewares, 1 being added by default. The second one is 'open-cors'. |
1058| `templates` | **{}** | Templates to be used in `routes.json`. See [templates](#templates) documentation. |
1059| `errorHandler` | **(empty)** | A custom express error handler. Must be a function with the following signature: function (err, req, res, next) { ... } (see [express documentation](https://expressjs.com/en/guide/error-handling.html#the-default-error-handler)) |
1060| `configureExpress` | **(empty)** | Used to set custom instructions to the express app. Must be a function with the following signature: function ({ server, app, db }) {}. `server` being the node http.Server instance, `app` the express instance and `db` the [drosse db api](#drosse-db-api). |
1061| `onHttpUpgrade` | **null** | A function that initiates a websocket connection. This is happening once during HTTP protocol upgrade handshake. Must be a function with the following signature: function (request, socket, head) { ... }. |
1062| `commands` | **(empty)** | Used to extend Drosse CLI with custom commands. Must be a function with the following signature: function (vorpal, drosse) { ... }. See [the cli](#cli) documentation. |
1063
1064### Custom middlewares
1065
1066You can create your own middlewares. Simply create a JS file that exports a classic express middleware function. Something like this:
1067
1068```js
1069module.exports = function (req, res, next) {
1070 // put your middleware code here
1071 next()
1072}
1073```
1074
1075You can also define the middleware with a supplementary argument, to place at the first position. It will
1076expose the Drosse API inside your middleware, letting you access the `db` instance for example.
1077
1078```js
1079module.exports = function (api, req, res, next) {
1080 // (very) naive role checking :)
1081 const { db } = api
1082 const user = db.get.byId('users', req.params.id)
1083 if (user.role !== 'admin') {
1084 return next({ some: 'error'})
1085 }
1086 next()
1087}
1088```
1089
1090<a name="endpoints-scraping"></a>
1091## Endpoints scraping
1092Do you remember, back in the days, these webscrapers ? You just turn them on then browse a website and they will eventually save the whole website on your machine? Well Drosse provides something similar, but a little less brutal as you can define precisely which endpoint you would like to scrape.
1093
1094:warning: Endpoint scraping come along with the `proxy` feature. You won't scrape your own defined mocks, right?
1095
1096There are 2 ways to scrape your proxied endpoints responses. The `static` and the `dynamic`.
1097
1098### Static scraping
1099The easiest one. Just indicate in your `routes.json` file, which endpoint you want to scrape.
1100
1101```json
1102 "countries": {
1103 "DROSSE": {
1104 "proxy": "https://restcountries.eu/rest/v2"
1105 },
1106 "name": {
1107 "DROSSE": {
1108 "scraper": {
1109 "static": true
1110 }
1111 }
1112 }
1113 }
1114```
1115In the snippet above, we've told Drosse to scrape any call to the `.../countries/name/....` endpoint.
1116
1117Concretely, it means that Drosse will copy & save the response of any of those calls into a static JSON file in the `scraped` directory of your `mocks`.
1118
1119:fire: As usual, you can redefine this `scraped` directory name in your `.drosserc.js` file ([see above](#drosserc)).
1120
1121This can be a convenient way to populate your mocks contents if the backend API already exists. Just configure your mocks to proxy the existing API and activate the scraper. When you have enought contents, remove the proxy and redefine your mocked routes as `static` mocks.
1122
1123Ideally you would rework your scraped contents and create relevant `static` file mocks out of it, maybe add some `templates`, etc. But you can also let them as they are, in the `scraped` directory: Drosse will always fallback on this directory if it doesn't find any match in the `static` directory.
1124
1125### Dynamic scraping
1126The dynamic scraping will let you rework the scraped content and save it exactly how and where you want to.
1127
1128```json
1129 "countries": {
1130 "DROSSE": {
1131 "proxy": "https://restcountries.eu/rest/v2"
1132 },
1133 "name": {
1134 "DROSSE": {
1135 "scraper": {
1136 "service": true
1137 }
1138 }
1139 }
1140 }
1141```
1142
1143In contrast with the Static scraping, you simply have to replace the `static` by `service`; see above.
1144
1145When Drosse encounter that configuration, it will look for a dedicated scraper service in the `scrapers` directory. The file must be named accordingly with the scraped endpoint. It's the same logic as for the normal `services` naming. You take each route node, remove the path parameters and replace `/` with `.`. And you ignore the verb.
1146
1147If we take the same example as for the services. For a `GET /api/users/:id/superpowers/:name` the scraper service file will be `api.users.superpowers.js`. No parameters, no verb.
1148
1149
1150:fire: As always, the scrapers directory can be renamed in the `.drosserc.js` file, with the `scraperServicesPath` property ([see above](#drosserc)).
1151
1152Your service must export a function which takes 2 parameters. The first one is the response of your scraped endpoint. It will be a JS object. The second one is the same `api` object as the one you get in a normal Drosse service.
1153
1154This gives you then access to the `db` object, the whole drosse `config` object, the `req`, etc...
1155
1156```js
1157module.exports = function (json, { db, config, req }) {
1158 // rework your json
1159 // save it in the database
1160 // create a proper JSON file in the `collections` directory to have your scraped content automatically reloaded in your DB even if you delete your db file.
1161}
1162```
1163
1164<a name="cli"></a>
1165## The CLI
1166
1167Drosse, once started, is a REPL console where you can type commands. It uses [Vorpal](https://github.com/dthree/vorpal).
1168
1169This feature is still under active development, but you can already create your own commands and register them in the `.drosserc.js`.
1170
1171### Built-in commands
1172`rs`: restarts the server
1173
1174`db drop`: Resets the database to the initial state (json files) and restarts the server.
1175
1176## Features
1177- Cascading Proxies
1178- Fallback to static JSON mocks
1179- In-memory db
1180- Endpoints scraping
1181- Seamless integration to your project OR
1182- Standalone global mode
1183
1184## In progress
1185- CLI + UI
1186- Persist state (in-memory db dump to JSON file)
1187
1188## Future features
1189- Anonymize data
1190- Journey
1191- Sync with OpenAPI (Swagger) ?
1192- GraphQL support ?