UNPKG

51.3 kBMarkdownView Raw
1<img src="https://raw.githubusercontent.com/jota-one/drosse/master/packages/core/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 }
523 }
524 }
525 }
526 }
527}
528```
529
530With such a definition, when you call `GET /api/users/65?withDetails=1`, drosse will look for a specific JSON file in the `static` subdirectory of your mocks directory.
531
532:fire: You can redefine this `static` directory name in your `.drosserc.js` file ([see below](#drosserc)).
533
534Drosse 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:
535
536```
537api.users.65.get&&withDetails=1.json
538api.users.65.get.json
539api.users.65.json
540api.users.{id}.json
541```
542
543If you have a route with several path parameters, drosse will ignore them from left to right. Example, for this route:
544```
545GET /api/users/:id/posts/:type
546```
547
548Assuming 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:
549
550```
551api.users.{id}.posts.read.get.json
552api.users.{id}.posts.unread.get.json
553```
554
555: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.
556
557If we try to call `GET /api/users/3` and we have defined the following static mocks files in our `static` directory.
558
559```
560api.users.1.json
561api.users.2.json
562api.users.{id}.json
563```
564
565```bash
5661:11:29 AM App Example JSON static app running at:
5671:11:29 AM - http://localhost:8000
5681:11:29 AM - http://172.22.22.178:8000
569
5701:11:29 AM Mocks root: /some/path/mymocks
571
5721:17:27 AM loadStatic: tried with [/some/path/mymocks/static/api.users.3.get.json]. File not found.
5731:17:27 AM loadStatic: tried with [/some/path/mymocks/static/api.users.3.json]. File not found.
5741:17:27 AM loadStatic: tried with [/some/path/mymocks/static/api.users.{id}.get.json]. File not found.
575```
576You 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.
577
578: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)).
579
580<a name="dynamic-mocks"></a>
581## Services (aka dynamic mocks)
582
583With 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.
584
585Drosse provides everything you need to implement an interactive mocked backend and let you focus on your frontend usecases.
586
587To 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:
588
589```json
590{
591 "api": {
592 "users": {
593 "DROSSE": {
594 "get": {
595 "body": [
596 {"id": 1, "name": "Jorinho", "premium": false},
597 {"id": 2, "name": "Tadai", "premium": true}
598 ]
599 },
600 "post": {
601 "service": true
602 }
603 },
604 ":id": {
605 "DROSSE": {
606 "get": {
607 "static": true
608 }
609 }
610 }
611 }
612 }
613}
614```
615
616See ? From the `routes.json` file, it's quite a piece of cake :cake: !
617
618Now 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.
619
620:fire: Like for the others subdirectories, you can redefine this `services` directory name in your `.drosserc.js` file ([see below](#drosserc)).
621
622To 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.
623
624Let'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`.
625
626Now let's have a look inside these service files.
627
628### The service file
629
630A service file must export a function that takes one object argument.
631
632```js
633module.exports = function ({ req, res, db }) {
634 // a lot of cool stuffs...
635}
636```
637
638As 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.
639
640:star: The return value of your function will be passed to the associated route response (optionally modified by a template, see later).
641
642Let's take a full example.
643
6441. You call `POST /api/users` and pass this payload: `{ name: "John" }`.
6452. The function in the file `services/api.users.post.js` is executed.
6463. Let's say it contains this code:
647
648
649```js
650module.exports = function ({ req, res, db }) {
651 const payload = req.body
652 // do whatever you want with your payload
653 return { added: payload.name }
654}
655```
656
6574. Your call returns: `{ added: "John" }`.
6585. That's all folks!
659
660: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.
661
662## Drosse db API
663
664Drosse 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).
665
666When 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.
667
668> :anguished: How do I update it, then ?
669
670LokiJS is collections-based. You can consider each collection as an array of objects, each object being a document.
671
672To 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:
673
674- A directory that contains JSON files. These files must contain a JSON object (not an array).
675- A JSON files that must contain an array of objects.
676
677In 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.
678
679:fire: You can redefine the `mocks.json` database file and the `collections` directory name in your `.drosserc.js` file ([see below](#drosserc)).
680
681By 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.
682
683If you want a fresh database, simply delete the `mocks.json` file and restart Drosse.
684
685> :sweat_smile: That's a bit violent! Is there a smoother way?
686
687You ask it, we provide.
688
689You 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.
690
691<a name="db-identify"></a>
692### Identify your documents
693One 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.
694
695It 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.
696
697The 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:
698
699```json
700{
701 "id": 1980,
702 "name": "Construction of a skyscraper",
703 "customer": {
704 "id": 888,
705 "name": "ACME corp",
706 "projectCode": "SKYSCRAPER-999"
707 },
708 "budget": 98000000,
709 "DROSSE": {
710 "ids": [1980, "SKYSCRAPER-999"]
711 }
712}
713```
714Like this, it will be easier to find our document and we won't have to ask ourselves which identifier was sent to our service.
715
716<a name="db-ref"></a>
717### Reference documents
718In 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...
719
720Here come reference documents to the rescue!
721
722Assuming you have a `customers` collection with this customer document in it.
723
724```json
725{
726 "id": 888,
727 "name": "ACME corp",
728 "address": {
729 "street": "Undefined or null 1",
730 "zip": "00001",
731 "town": "North Pole City"
732 },
733 "activity": "Secretely conquer the world by not being evil... at first.",
734 "DROSSE": {
735 "ids": [1980, "SKYSCRAPER-999"]
736 }
737}
738```
739
740You can redefine your project like this:
741
742```json
743{
744 "id": 1980,
745 "name": "Construction of a skyscraper",
746 "customer": {
747 "collection": "customers",
748 "id": 888,
749 "projectCode": "SKYSCRAPER-999"
750 },
751 "budget": 98000000,
752 "DROSSE": {
753 "ids": [1980, "SKYSCRAPER-999"]
754 }
755}
756```
757
758The company name is not duplicated anymore.
759
760When 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.
761
762
763### API
764
765Once 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.
766
767<a name="db-list-all"></a>
768**db.list.all(collection, cleanFields)**
769
770List all documents in a collection.
771| Argument | Required | Type | Description |
772|-------------------------|------------|-----------|---------------------------------|
773| collection | _Required_ | String | The collection name |
774| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
775
776Returns an _Array_ of documents.
777
778<a name="db-list-byId"></a>
779**db.list.byId(collection, id, cleanFields)**
780
781List all documents in a collection that have the provided identifier.
782| Argument | Required | Type | Description |
783|-------------------------|------------|-----------|---------------------------------|
784| collection | _Required_ | String | The collection name |
785| id | _Required_ | Mixed | A [document identifier](#db-identify) |
786| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
787
788Returns an _Array_ of documents.
789
790<a name="db-list-byFields"></a>
791**db.list.byFields(collection, fields, value, cleanFields)**
792
793List all documents in a collection having at least one of the provided fields that contains the provided value.
794| Argument | Required | Type | Description |
795|-------------------------|------------|-----------|---------------------------------|
796| collection | _Required_ | String | The collection name |
797| fields | _Required_ | String[] | A list of fields |
798| value | _Required_ | Mixed | A value to test for. Should be a string or number |
799| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
800
801Returns an _Array_ of documents.
802
803<a name="db-list-byField"></a>
804**db.list.byField(collection, field, value, cleanFields)**
805
806List all documents in a collection having the provided field that contains the provided value.
807| Argument | Required | Type | Description |
808|-------------------------|------------|-----------|---------------------------------|
809| collection | _Required_ | String | The collection name |
810| field | _Required_ | String | A field |
811| value | _Required_ | Mixed | A value to test for. Should be a string or number |
812| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
813
814Returns an _Array_ of documents.
815
816<a name="db-list-find"></a>
817**db.list.find(collection, query, cleanFields)**
818
819List all documents in a collection matching the provided query.
820| Argument | Required | Type | Description |
821|-------------------------|------------|-----------|---------------------------------|
822| collection | _Required_ | String | The collection name |
823| query | _Required_ | Object | A lokiJS query object |
824| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
825
826Returns an _Array_ of documents.
827
828<a name="db-list-where"></a>
829**db.list.where(collection, searchFn, cleanFields)**
830
831List all documents in a collection for which the searchFn callback returns a truthy value
832| Argument | Required | Type | Description |
833|-------------------------|------------|-----------|---------------------------------|
834| collection | _Required_ | String | The collection name |
835| searchFn | _Required_ | Function | A function that will be called for each document and take the document in argument. |
836| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
837
838Returns an _Array_ of documents.
839
840<a name="db-get-byId"></a>
841**db.get.byId(collection, id, cleanFields)**
842
843Find first document in a collection that have the provided identifier.
844| Argument | Required | Type | Description |
845|-------------------------|------------|-----------|---------------------------------|
846| collection | _Required_ | String | The collection name |
847| id | _Required_ | Mixed | A [document identifier](#db-identify) |
848| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
849
850Returns a document.
851
852<a name="db-get-byFields"></a>
853**db.get.byFields(collection, fields, value, cleanFields)**
854
855Find first document in a collection having at least one of the provided fields that contains the provided value.
856| Argument | Required | Type | Description |
857|-------------------------|------------|-----------|---------------------------------|
858| collection | _Required_ | String | The collection name |
859| fields | _Required_ | String[] | A list of fields |
860| value | _Required_ | Mixed | A value to test for. Should be a string or number |
861| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
862
863Returns a document.
864
865<a name="db-get-byField"></a>
866**db.get.byField(collection, field, value, cleanFields)**
867
868Find first document in a collection having the provided field that contains the provided value.
869| Argument | Required | Type | Description |
870|-------------------------|------------|-----------|---------------------------------|
871| collection | _Required_ | String | The collection name |
872| field | _Required_ | String | A field |
873| value | _Required_ | Mixed | A value to test for. Should be a string or number |
874| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
875
876Returns a document.
877
878<a name="db-get-find"></a>
879**db.get.find(collection, query, cleanFields)**
880
881Find first document in a collection matching the provided query.
882| Argument | Required | Type | Description |
883|-------------------------|------------|-----------|---------------------------------|
884| collection | _Required_ | String | The collection name |
885| query | _Required_ | Object | A lokiJS query object |
886| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
887
888Returns a document.
889
890<a name="db-get-where"></a>
891**db.get.where(collection, searchFn, cleanFields)**
892
893Find first document in a collection for which the searchFn callback returns a truthy value
894| Argument | Required | Type | Description |
895|-------------------------|------------|-----------|---------------------------------|
896| collection | _Required_ | String | The collection name |
897| searchFn | _Required_ | Function | A function that will be called for each document and take the document in argument. |
898| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
899
900Returns a document.
901
902<a name="db-get-byRef"></a>
903**db.get.byRef(refObj, dynamicId, cleanFields)**
904
905Find first document in a collection matching the provided query.
906| Argument | Required | Type | Description |
907|-------------------------|------------|-----------|---------------------------------|
908| refObj | _Required_ | Object | An object that contains a `collection` property and an `id` property. See [Ref documents](#db-ref) documentation. |
909| dynamicId | _Optional_ | Mixed | A [document identifier](#db-identify) |
910| cleanFields | _Optional_ | Array | A list of properties you want to exclude from each returned document |
911
912Returns a document.
913
914```js
915const getDetailedProject = projectId => {
916 const myProject = db.get.byId('projects', projectId)
917 myProject.customer = db.get.byRef(myProject.customer)
918 return myProject
919}
920
921const detailedProject = getDetailedProject(1980)
922```
923
924<a name="db-query-getMapId"></a>
925**db.query.getMapId(collection, fieldname, firstOnly)**
926
927Generate a hash to link a specific document field value to the document ids (or first id)
928| Argument | Required | Type | Description |
929|-------------------------|------------|-----------|---------------------------------|
930| collection | _Required_ | String | The collection name |
931| fieldname | _Required_ | String | A field |
932| firstOnly | _Optional_ | Boolean | If `true`, will consider only the first `id` found in the `DROSSE.ids` array. |
933
934Returns a correspondance hash with the chosen field value as key and the corresponding id as value.
935
936:warning: Be aware that if the chosen `fieldname` hasn't unique values for each document in collection, the later documents will overwrite the formers.
937
938<a name="db-query-chain"></a>
939**db.query.chain(collection)**
940
941Exposes the LokiJS chain method ([see LokiJS documentation](https://techfort.github.io/LokiJS/Collection.html) for more details)
942| Argument | Required | Type | Description |
943|-------------------------|------------|-----------|---------------------------------|
944| collection | _Required_ | String | The collection name |
945
946Returns a [LokiJS ResultSet](https://techfort.github.io/LokiJS/Resultset.html)
947
948<a name="db-query-clean"></a>
949**db.query.clean([...fields])**
950
951Creates a cleaning function that will remove all listed fields from the passed object
952| Argument | Required | Type | Description |
953|-------------------------|------------|-----------|---------------------------------|
954| ...fields | _Optional_ | ...String | Any number of fieldnames |
955
956Returns a function that takes a javascript Object as unique argument
957
958: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.
959
960:fire::fire: This function is used in all other methods to clean up the results and merges the optional `cleanFields` with the `reserved words`.
961
962<a name="db-insert"></a>
963**db.insert(collection, ids, payload)**
964
965Inserts a document in a collection
966| Argument | Required | Type | Description |
967|-------------------------|------------|-----------|---------------------------------|
968| collection | _Required_ | String | The collection name |
969| ids | _Required_ | Array | An array of identifiers for your new document (will be stored in `DROSSE.ids`) |
970| payload | _Required_ | Object | The document |
971
972Returns the inserted document
973
974<a name="db-update-byId"></a>
975**db.update.byId(collection, id, newValue)**
976
977Updates a document in a collection
978| Argument | Required | Type | Description |
979|-------------------------|------------|-----------|---------------------------------|
980| collection | _Required_ | String | The collection name |
981| id | _Required_ | Mixed | One of the document identifiers (from `DROSSE.ids`) |
982| 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) |
983
984Returns _nothing_
985
986<a name="db-update-subItem-append"></a>
987**db.update.subItem.append(collection, id, subPath, payload)**
988
989Insert (append) a new item in some of the identified document subItems list
990| Argument | Required | Type | Description |
991|-------------------------|------------|-----------|---------------------------------|
992| collection | _Required_ | String | The collection name |
993| id | _Required_ | Mixed | One of the document identifiers (from `DROSSE.ids`) |
994| subPath | _Required_ | String | A string of type `field.subfield.subsubfield` pointing to the field to alter |
995| payload | _Required_ | Object | The sub item to insert |
996
997Returns _nothing_
998
999
1000<a name="db-update-subItem-prepend"></a>
1001**db.update.subItem.prepend(collection, id, subPath, payload)**
1002
1003Insert (prepend) a new item in some of the identified document subItems list
1004| Argument | Required | Type | Description |
1005|-------------------------|------------|-----------|---------------------------------|
1006| collection | _Required_ | String | The collection name |
1007| id | _Required_ | Mixed | One of the document identifiers (from `DROSSE.ids`) |
1008| subPath | _Required_ | String | A string of type `field.subfield.subsubfield` pointing to the field to alter |
1009| payload | _Required_ | Object | The sub item to insert |
1010
1011Returns _nothing_
1012
1013<a name="db-update-byId"></a>
1014**db.remove.byId(collection, id)**
1015
1016Removes (delete) a document from a collection
1017| Argument | Required | Type | Description |
1018|-------------------------|------------|-----------|---------------------------------|
1019| collection | _Required_ | String | The collection name |
1020| id | _Required_ | Mixed | One of the document identifiers (from `DROSSE.ids`) |
1021
1022Returns _nothing_ or `false` if the document was not found.
1023
1024<a name="drosserc"></a>
1025## The .drosserc.js file (configure your Drosse)
1026This file holds your mock server general configuration. It's optional as all its keys have default values. It must simply export a configuration object.
1027
1028Here is a typical example of what it could contain.
1029```js
1030module.exports = {
1031 name: 'My mocks app',
1032 port: 8000
1033}
1034```
1035
1036### All configuration keys
1037| Key | Default value | Description |
1038|----------------------|---------------|-------------|
1039| `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). |
1040| `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.) |
1041| `baseUrl` | **(empty)** | The base URL (ex. http://my.domain.com) for the routes |
1042| `basePath` | **(empty)** | A prefix (ex. /api/v2) that will be added to each route path |
1043| `routesFile` | **routes** | Name of the routes definition file. |
1044| `collectionsPath` | **collections** | Relative path to the loki collections directory from your mocks directory. |
1045| `shallowCollections` | **[]** | List of collections that should be recreated/overriden on each server restart. |
1046| `assetsPath` | **assets** | Relative path to the assets directory from your mocks directory. |
1047| `servicesPath` | **services** | Relative path to the services directory from your mocks directory. |
1048| `staticPath` | **static** | Relative path to the static files directory from your mocks directory. |
1049| `scraperServicesPath`| **scrapers** | Relative path to the scraper services files directory from your mocks directory. |
1050| `scrapedPath` | **scraped** | Relative path to the scraped files directory from your mocks directory. |
1051| `database` | **mocks.db** | Name of your loki database dump file. |
1052| `dbAdapter` | **LokiFsAdapter** | IO adapter to use for database persistence. |
1053| `middlewares` | **['body-parser-json', 'morgan']** | List of global middlewares. Drosse provides 3 built-in middlewares, 2 being added by default. The third is 'open-cors'. |
1054| `templates` | **{}** | Templates to be used in `routes.json`. See [templates](#templates) documentation. |
1055| `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)) |
1056| `configureExpress` | **(empty)** | Used to set custom instructions to the express app. Must be a function with the following signature: function (app) {}. `app` being the express instance. |
1057| `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. |
1058
1059### Custom middlewares
1060
1061You can create your own middlewares. Simply create a JS file that exports a classic express middleware function. Something like this:
1062
1063```js
1064module.exports = function (req, res, next) {
1065 // put your middleware code here
1066 next()
1067}
1068```
1069
1070You can also define the middleware with a supplementary argument, to place at the first position. It will
1071expose the Drosse API inside your middleware, letting you access the `db` instance for example.
1072
1073```js
1074module.exports = function (api, req, res, next) {
1075 // (very) naive role checking :)
1076 const { db } = api
1077 const user = db.get.byId('users', req.params.id)
1078 if (user.role !== 'admin') {
1079 return next({ some: 'error'})
1080 }
1081 next()
1082}
1083```
1084
1085<a name="endpoints-scraping"></a>
1086## Endpoints scraping
1087Do 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.
1088
1089:warning: Endpoint scraping come along with the `proxy` feature. You won't scrape your own defined mocks, right?
1090
1091There are 2 ways to scrape your proxied endpoints responses. The `static` and the `dynamic`.
1092
1093### Static scraping
1094The easiest one. Just indicate in your `routes.json` file, which endpoint you want to scrape.
1095
1096```json
1097 "countries": {
1098 "DROSSE": {
1099 "proxy": "https://restcountries.eu/rest/v2"
1100 },
1101 "name": {
1102 "DROSSE": {
1103 "scraper": {
1104 "static": true
1105 }
1106 }
1107 }
1108 }
1109```
1110In the snippet above, we've told Drosse to scrape any call to the `.../countries/name/....` endpoint.
1111
1112Concretely, 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`.
1113
1114:fire: As usual, you can redefine this `scraped` directory name in your `.drosserc.js` file ([see above](#drosserc)).
1115
1116This 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.
1117
1118Ideally 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.
1119
1120### Dynamic scraping
1121The dynamic scraping will let you rework the scraped content and save it exactly how and where you want to.
1122
1123```json
1124 "countries": {
1125 "DROSSE": {
1126 "proxy": "https://restcountries.eu/rest/v2"
1127 },
1128 "name": {
1129 "DROSSE": {
1130 "scraper": {
1131 "service": true
1132 }
1133 }
1134 }
1135 }
1136```
1137
1138In contrast with the Static scraping, you simply have to replace the `static` by `service`; see above.
1139
1140When 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.
1141
1142If 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.
1143
1144
1145:fire: As always, the scrapers directory can be renamed in the `.drosserc.js` file, with the `scraperServicesPath` property ([see above](#drosserc)).
1146
1147Your 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.
1148
1149This gives you then access to the `db` object, the whole drosse `config` object, the `req`, etc...
1150
1151```js
1152module.exports = function (json, { db, config, req }) {
1153 // rework your json
1154 // save it in the database
1155 // 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.
1156}
1157```
1158
1159<a name="cli"></a>
1160## The CLI
1161
1162Drosse, once started, is a REPL console where you can type commands. It uses [Vorpal](https://github.com/dthree/vorpal).
1163
1164This feature is still under active development, but you can already create your own commands and register them in the `.drosserc.js`.
1165
1166### Built-in commands
1167`rs`: restarts the server
1168
1169`db drop`: Resets the database to the initial state (json files) and restarts the server.
1170
1171## Features
1172- Cascading Proxies
1173- Fallback to static JSON mocks
1174- In-memory db
1175- Endpoints scraping
1176- Seamless integration to your project OR
1177- Standalone global mode
1178
1179## In progress
1180- CLI + UI
1181- Persist state (in-memory db dump to JSON file)
1182
1183## Future features
1184- Anonymize data
1185- Journey
1186- Sync with OpenAPI (Swagger) ?
1187- GraphQL support ?