From d2daf382fc38e16abbb2087354ce75b5b8efe215 Mon Sep 17 00:00:00 2001 From: Taranveer Virk Date: Fri, 23 Mar 2018 19:09:42 -0400 Subject: [PATCH] style: format markdown files for consistency --- README.md | 71 ++- docs/CONTRIBUTING.md | 59 +- docs/site/Application-generator.md | 69 +-- docs/site/Application.md | 104 ++-- docs/site/Best-practices-with-Loopback-4.md | 40 +- docs/site/Booting-an-Application.md | 147 ++--- .../Calling-other-APIs-and-Web-Services.md | 5 +- docs/site/Command-line-interface.md | 10 +- docs/site/Context.md | 74 +-- docs/site/Controller-generator.md | 73 +-- docs/site/Controllers.md | 1 - docs/site/Crafting-LoopBack-4.md | 460 +++++++++----- docs/site/Creating-components.md | 219 ++++--- docs/site/Creating-servers.md | 11 +- docs/site/DEVELOPING.md | 270 +++++---- docs/site/Decorators.md | 559 +++++++++--------- ...ining-the-API-using-code-first-approach.md | 109 ++-- ...ing-the-API-using-design-first-approach.md | 326 ++++++---- docs/site/Defining-your-testing-strategy.md | 113 +++- docs/site/Dependency-injection.md | 118 ++-- docs/site/Download-examples.md | 7 +- docs/site/Examples-and-tutorials.md | 9 +- docs/site/Extending-LoopBack-4.md | 61 +- docs/site/Extension-generator.md | 40 +- docs/site/FAQ.md | 66 ++- docs/site/Getting-started.md | 30 +- docs/site/Glossary.md | 32 +- docs/site/Implementing-features.md | 386 ++++++++---- ...troduction-to-LoopBack-Next-development.md | 4 +- docs/site/Language-related-concepts.md | 13 + docs/site/LoopBack-3.x.md | 117 ++-- docs/site/MONOREPO.md | 48 +- docs/site/Mixin.md | 86 +-- docs/site/Model.md | 95 +-- .../site/Preparing-the-API-for-consumption.md | 78 ++- docs/site/Repositories.md | 102 ++-- docs/site/Reserved-binding-keys.md | 143 +++-- docs/site/Sequence.md | 106 ++-- docs/site/Server.md | 29 +- docs/site/Team.md | 6 +- docs/site/Testing-Your-Extensions.md | 122 ++-- docs/site/Testing-the-API.md | 157 ++--- docs/site/Testing-your-application.md | 318 +++++----- docs/site/Using-decorators.md | 4 +- docs/site/VSCODE.md | 126 ++-- docs/site/index.md | 37 +- packages/authentication/README.md | 18 +- packages/boot/README.md | 104 ++-- packages/build/README.md | 72 ++- packages/cli/README.md | 18 +- .../generators/app/templates/test/README.md | 1 - .../templates/src/controllers/README.md | 5 +- .../templates/src/decorators/README.md | 14 +- .../extension/templates/src/mixins/README.md | 93 ++- .../templates/src/providers/README.md | 54 +- .../templates/src/repositories/README.md | 2 +- packages/context/README.md | 24 +- .../acceptance/{_feature.md => 1-feature.md} | 6 +- .../class-level-bindings.feature.md | 92 ++- .../acceptance/finding-bindings.feature.md | 21 +- .../acceptance/locking-bindings.feature.md | 2 +- .../method-level-bindings.feature.md | 89 ++- .../acceptance/tagged-bindings.feature.md | 7 +- packages/core/README.md | 47 +- packages/example-getting-started/README.md | 39 +- .../docs/controller.md | 57 +- .../docs/datasource.md | 26 +- .../example-getting-started/docs/juggler.md | 9 +- .../example-getting-started/docs/model.md | 56 +- .../docs/putting-it-together.md | 39 +- .../docs/repository.md | 16 +- .../docs/scaffolding.md | 62 +- packages/example-hello-world/README.md | 17 +- packages/example-log-extension/README.md | 73 ++- packages/example-rpc-server/README.md | 24 +- packages/example-rpc-server/test/README.md | 1 - packages/metadata/README.md | 267 +++++---- packages/openapi-spec-builder/README.md | 46 +- packages/openapi-v3-types/README.md | 18 +- packages/openapi-v3/README.md | 30 +- packages/repository-json-schema/README.md | 9 +- packages/repository/README.md | 24 +- packages/rest/README.md | 54 +- .../acceptance/module-exporting/feature.md | 3 +- .../rest/test/acceptance/routing/feature.md | 9 +- packages/testlab/README.md | 26 +- 86 files changed, 3912 insertions(+), 2722 deletions(-) create mode 100644 docs/site/Language-related-concepts.md rename packages/context/test/acceptance/{_feature.md => 1-feature.md} (94%) diff --git a/README.md b/README.md index e844376ded6e..948fe5aebc0f 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,30 @@ [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/strongloop/loopback) [![Travis Build Status](https://travis-ci.org/strongloop/loopback-next.svg?branch=master)](https://travis-ci.org/strongloop/loopback-next) [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/3v1qmusv168a0kb0/branch/master?svg=true)](https://ci.appveyor.com/project/bajtos/loopback-next/branch/master) [![Coverage Status](https://coveralls.io/repos/github/strongloop/loopback-next/badge.svg?branch=master)](https://coveralls.io/github/strongloop/loopback-next?branch=master) -LoopBack makes it easy to build modern applications that require complex integrations. +LoopBack makes it easy to build modern applications that require complex +integrations. - Fast, small, powerful, extensible core - Generate real APIs with a single command - Define your data and endpoints with OpenAPI - No maintenance of generated code -# Status: Developer Preview #1 +## Status: Developer Preview #1 LoopBack 4 is a work in progress, the public API is frequently changed in -backward incompatible ways. See [Upcoming-Releases on wiki](https://github.com/strongloop/loopback-next/wiki/Upcoming-Releases) +backward incompatible ways. See +[Upcoming-Releases on wiki](https://github.com/strongloop/loopback-next/wiki/Upcoming-Releases) for more details. -Learn about the latest and greatest [features and technologies in LoopBack 4](https://loopback.io/doc/en/lb4/Crafting-LoopBack-4.html) by using it for your next project. Start by having a look at [Getting Started](https://loopback.io/doc/en/lb4/Getting-started.html). +Learn about the latest and greatest +[features and technologies in LoopBack 4](https://loopback.io/doc/en/lb4/Crafting-LoopBack-4.html) +by using it for your next project. Start by having a look at +[Getting Started](https://loopback.io/doc/en/lb4/Getting-started.html). -**NOTE:** _It is not recommended for production use yet._ For production, use [LoopBack 3](https://loopback.io/doc/en/lb3/index.html). +**NOTE:** _It is not recommended for production use yet._ For production, use +[LoopBack 3](https://loopback.io/doc/en/lb3/index.html). -# Installation +## Installation Make sure you have the following installed: @@ -31,9 +37,10 @@ Install LoopBack 4 CLI to help create new projects as follows: npm i -g @loopback/cli ``` -To create your first LoopBack 4 application, see [Getting Started](http://loopback.io/doc/en/lb4/Getting-started.html). +To create your first LoopBack 4 application, see +[Getting Started](http://loopback.io/doc/en/lb4/Getting-started.html). -# Documentation +## Documentation - [Official documentation](http://loopback.io/doc/en/lb4/) - [API documentation](http://apidocs.loopback.io/#LoopBack4) @@ -41,45 +48,55 @@ To create your first LoopBack 4 application, see [Getting Started](http://loopba - [LoopBack 3 vs LoopBack 4](http://loopback.io/doc/en/lb4/LoopBack-3.x.html) - [Tutorials and examples](http://loopback.io/doc/en/lb4/Examples-and-tutorials.html) -# Contributing +## Contributing See the following resources to get your started: - - [Contributing Guidelines](./docs/CONTRIBUTING.md) - - [Monorepo overview](./docs/site/MONOREPO.md) - - [Developing LoopBack](./docs/site/DEVELOPING.md) +- [Contributing Guidelines](./docs/CONTRIBUTING.md) +- [Monorepo overview](./docs/site/MONOREPO.md) +- [Developing LoopBack](./docs/site/DEVELOPING.md) -You can join the team by posting a comment to [issue #110](https://github.com/strongloop/loopback-next/issues/110). +You can join the team by posting a comment to +[issue #110](https://github.com/strongloop/loopback-next/issues/110). -# Team +## Team -## Project Architects: +### Project Architects -Raymond Feng|Miroslav Bajtos|Ritchie Martori -:-:|:-:|:-: -[![raymondfeng]](http://github.com/raymondfeng)|[![bajtos]](http://github.com/bajtos)|[![ritch]](http://github.com/ritch) +| Raymond Feng | Miroslav Bajtos | Ritchie Martori | +| :---------------------------------------------: | :-----------------------------------: | :---------------------------------: | +| [![raymondfeng]](http://github.com/raymondfeng) | [![bajtos]](http://github.com/bajtos) | [![ritch]](http://github.com/ritch) | -## Project Maintainers: +### Project Maintainers -||||| -|:-:|:-:|:-:|:-:| -|Taranveer Virk|Biniam Admikew|Kyu Shim|Diana Lau| -|[![virkt25]](http://github.com/virkt25)|[![b-admike]](http://github.com/b-admike)|[](http://github.com/shimks)|[![dhmlau]](http://github.com/dhmlau)| -|Janny Hou|Simon Ho|Kevin Delisle|Rand McKinney| -|[![jannyhou]](http://github.com/jannyHou)|[![superkhau]](http://github.com/superkhau)| +| | | | | +| :---------------------------------------: | :-----------------------------------------: | :----------------------------------------------------------------------------------------------------------: | :-----------------------------------: | +| Taranveer Virk | Biniam Admikew | Kyu Shim | Diana Lau | +| [![virkt25]](http://github.com/virkt25) | [![b-admike]](http://github.com/b-admike) | [](http://github.com/shimks) | [![dhmlau]](http://github.com/dhmlau) | +| Janny Hou | Simon Ho | | | +| [![jannyhou]](http://github.com/jannyHou) | [![superkhau]](http://github.com/superkhau) | | | -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). -# License +## License [MIT](LICENSE) [raymondfeng]: https://avatars0.githubusercontent.com/u/540892?v=3&s=60 + [bajtos]: https://avatars2.githubusercontent.com/u/1140553?v=3&s=60 + [ritch]: https://avatars2.githubusercontent.com/u/462228?v=3&s=60 + [b-admike]: https://avatars0.githubusercontent.com/u/13950637?v=3&s=60 + [dhmlau]: https://avatars2.githubusercontent.com/u/25489897?v=3&s=60 + [jannyhou]: https://avatars2.githubusercontent.com/u/12554153?v=3&s=60 + [superkhau]: https://avatars1.githubusercontent.com/u/1617364?v=3&s=60 + [loay]: https://avatars3.githubusercontent.com/u/1986928?v=3&s=60 + [virkt25]: https://avatars1.githubusercontent.com/u/3311536?v=3&s=60 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 8b2a6e3db9b4..3e4c004025eb 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,41 +1,54 @@ # Contributing to LoopBack -Contributions to LoopBack include code, documentation, answering user questions, and advocating for all types of LoopBack users. See our official documentation on loopback.io for more information common to all of our GitHub repositories: +Contributions to LoopBack include code, documentation, answering user questions, +and advocating for all types of LoopBack users. See our official documentation +on loopback.io for more information common to all of our GitHub repositories: - - http://loopback.io/doc/en/contrib/index.html +- ## [Principles](http://loopback.io/doc/en/contrib/Governance.html#principles) -LoopBack is an open, inclusive, and tolerant community of people working together to build a world-class Node framework and tools. We value diversity of individuals and opinions, and seek to operate on consensus whenever possible. We strive to maintain a welcoming, inclusive, and harassment-free environment, regardless of the form of communication. When consensus is not achievable, we defer to the owners of each individual module; the powers of the individual owner are kept in check by the ability of the community to fork and replace dependencies on the individual module and maintainer. +LoopBack is an open, inclusive, and tolerant community of people working +together to build a world-class Node framework and tools. We value diversity of +individuals and opinions, and seek to operate on consensus whenever possible. We +strive to maintain a welcoming, inclusive, and harassment-free environment, +regardless of the form of communication. When consensus is not achievable, we +defer to the owners of each individual module; the powers of the individual +owner are kept in check by the ability of the community to fork and replace +dependencies on the individual module and maintainer. ## [Reporting issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) -Issues in [strongloop/loopback-next](https://github.com/strongloop/loopback-next/issues) are the primary means by which bug reports and general discussions are made. +Issues in +[strongloop/loopback-next](https://github.com/strongloop/loopback-next/issues) +are the primary means by which bug reports and general discussions are made. - - [How to report an issue](http://loopback.io/doc/en/contrib/Reporting-issues.html#how-to-report-an-issue) - - [Disclosing security vulnerabilities](http://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues) +- [How to report an issue](http://loopback.io/doc/en/contrib/Reporting-issues.html#how-to-report-an-issue) +- [Disclosing security vulnerabilities](http://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues) ## [Contributing code](http://loopback.io/doc/en/contrib/code-contrib.html) -Pull Requests are the way concrete changes are made to the code, documentation and tools contained in LoopBack repositories. +Pull Requests are the way concrete changes are made to the code, documentation +and tools contained in LoopBack repositories. - - [Monorepo overview](./site/MONOREPO.md) - - [Setting up development environment](./site/DEVELOPING.md#setting-up-development-environment) - - [How to contribute the code](http://loopback.io/doc/en/contrib/code-contrib.html#how-to-contribute-to-the-code) - - [Building the project](./site/DEVELOPING.md#building-the-project) - - [Running tests](./site/DEVELOPING.md#running-tests) - - [Coding rules](./site/DEVELOPING.md#coding-rules) - - [API documentation](./site/DEVELOPING.md#api-documentation) - - [Git commit messages](./site/DEVELOPING.md#commit-message-guidelines) - - [Reviewing pull requests](http://loopback.io/doc/en/contrib/triaging-pull-requests.html) - - [Contributor License Agreement (CLA)](http://loopback.io/doc/en/contrib/code-contrib.html#agreeing-to-the-cla) +- [Monorepo overview](./site/MONOREPO.md) +- [Setting up development environment](./site/DEVELOPING.md#setting-up-development-environment) +- [How to contribute the code](http://loopback.io/doc/en/contrib/code-contrib.html#how-to-contribute-to-the-code) +- [Building the project](./site/DEVELOPING.md#building-the-project) +- [Running tests](./site/DEVELOPING.md#running-tests) +- [Coding rules](./site/DEVELOPING.md#coding-rules) +- [API documentation](./site/DEVELOPING.md#api-documentation) +- [Git commit messages](./site/DEVELOPING.md#commit-message-guidelines) +- [Reviewing pull requests](http://loopback.io/doc/en/contrib/triaging-pull-requests.html) +- [Contributor License Agreement (CLA)](http://loopback.io/doc/en/contrib/code-contrib.html#agreeing-to-the-cla) ## [Documentation](http://loopback.io/doc/en/contrib/doc-contrib.html) -LoopBack documentation is sourced in the [strongloop/loopback.io](https://github.com/strongloop/loopback.io) GitHub repository. - - - [How LoopBack documentation works](http://loopback.io/doc/en/contrib/doc-contrib.html#how-loopback-documentation-works) - - [Using Jekyll](http://loopback.io/doc/en/contrib/jekyll_getting_started.html) - - [Authoring pages](http://loopback.io/doc/en/contrib/pages.html) - - [Translations](http://loopback.io/doc/en/contrib/translation.html) +LoopBack documentation is sourced in the +[strongloop/loopback.io](https://github.com/strongloop/loopback.io) GitHub +repository. +- [How LoopBack documentation works](http://loopback.io/doc/en/contrib/doc-contrib.html#how-loopback-documentation-works) +- [Using Jekyll](http://loopback.io/doc/en/contrib/jekyll_getting_started.html) +- [Authoring pages](http://loopback.io/doc/en/contrib/pages.html) +- [Translations](http://loopback.io/doc/en/contrib/translation.html) diff --git a/docs/site/Application-generator.md b/docs/site/Application-generator.md index 312586feba01..9531c9e64a10 100644 --- a/docs/site/Application-generator.md +++ b/docs/site/Application-generator.md @@ -12,59 +12,55 @@ summary: Creates a new LoopBack4 application using REST API. -``` +```sh lb4 [app] [options] [] ``` ### Options -`--applicationName` -: Application name. +`--applicationName` : Application name. -`--description` -: Description of the application. +`--description` : Description of the application. -`--outDir` -: Project root directory for the application. +`--outDir` : Project root directory for the application. -`--tslint` -: Add TSLint to LoopBack4 application project. +`--tslint` : Add TSLint to LoopBack4 application project. -`--prettier` -: Add Prettier to LoopBack4 application project. +`--prettier` : Add Prettier to LoopBack4 application project. -`--mocha` -: Add Mocha to LoopBack4 application project. +`--mocha` : Add Mocha to LoopBack4 application project. -`--loopbackBuild` -: Add @loopback/build module's script set to LoopBack4 application project. +`--loopbackBuild` : Add @loopback/build module's script set to LoopBack4 +application project. {% include_relative includes/CLI-std-options.md %} ### Arguments -`` - Optional name of the application given as an argument to the command.  -If provided, the tool will use that as the default when prompting for the name. +`` - Optional name of the application given as an argument to the +command.  If provided, the tool will use that as the default when prompting for +the name. ### Interactive Prompts The tool will prompt you for: -- Name of the application as will be shown in `package.json`. -If the name had been supplied from the command-line, the prompt is skipped and the application is built with the name from the command-line argument. -Must follow npm naming conventions. +- Name of the application as will be shown in `package.json`. If the name had + been supplied from the command-line, the prompt is skipped and the application + is built with the name from the command-line argument. Must follow npm naming + conventions. - Description of the application as will be shown in `package.json`. -- Name of the directory in which to create your application. -Defaults to the name of the application previously entered. +- Name of the directory in which to create your application. Defaults to the + name of the application previously entered. -- Name of the Application class in `application.ts`. -Defaults to nameApplication. +- Name of the Application class in `application.ts`. Defaults to + nameApplication. -- Optional modules to add to the application. These modules are helpful tools to help format, test, and build a LoopBack4 application. -Defaults to `true` for all of the modules. -The prompted modules are: +- Optional modules to add to the application. These modules are helpful tools to + help format, test, and build a LoopBack4 application. Defaults to `true` for + all of the modules. The prompted modules are: - [`tslint`](https://www.npmjs.com/package/tslint) - [`prettier`](https://www.npmjs.com/package/prettier) @@ -73,9 +69,10 @@ The prompted modules are: ### Output -The core scaffold of a LoopBack4 application generated by the CLI consists of the following files and directories: +The core scaffold of a LoopBack4 application generated by the CLI consists of +the following files and directories: -``` +```text . ├── src/ | ├── controllers/ @@ -90,10 +87,14 @@ The core scaffold of a LoopBack4 application generated by the CLI consists of th └── package.json ``` -`ping.controller.ts` is a file used to provide the application with a responsive endpoint. -It contains logic to respond with a greeting message when the appliaction receives a `GET` request from endpoint `/ping`. +`ping.controller.ts` is a file used to provide the application with a responsive +endpoint. It contains logic to respond with a greeting message when the +appliaction receives a `GET` request from endpoint `/ping`. -`cd` to the application's newly created directory and run `npm start` to see the application running at `localhost:3000`. -Go to `localhost:3000/ping` to be greeted with a message. +`cd` to the application's newly created directory and run `npm start` to see the +application running at `localhost:3000`. Go to `localhost:3000/ping` to be +greeted with a message. -Once the application has been created, additional generators such as [controller generator](Controller-generator.md) can be run from the application's root directory to further scaffold the application. +Once the application has been created, additional generators such as +[controller generator](Controller-generator.md) can be run from the +application's root directory to further scaffold the application. diff --git a/docs/site/Application.md b/docs/site/Application.md index db378a9c6e94..7fc9c4bb11ff 100644 --- a/docs/site/Application.md +++ b/docs/site/Application.md @@ -10,24 +10,27 @@ summary: ## What is an Application? -In LoopBack 4, the [`Application`](http://apidocs.strongloop.com/@loopback%2fcore/#Application) +In LoopBack 4, the +[`Application`](http://apidocs.strongloop.com/@loopback%2fcore/#Application) class is the central class for setting up all of your module's components, controllers, servers and bindings. The `Application` class extends [Context](Context.md), and provides the controls for starting and stopping itself and its associated servers. -When using LoopBack 4, we strongly encourage you to create your own subclass -of `Application` to better organize your configuration and setup. +When using LoopBack 4, we strongly encourage you to create your own subclass of +`Application` to better organize your configuration and setup. ## Making your own application class -By making your own application class, you can perform several additional -tasks as a part of your setup: +By making your own application class, you can perform several additional tasks +as a part of your setup: + - Pass configuration into the base class constructor - Perform some asynchronous wireup before application start - Perform some graceful cleanup on application stop {% include code-caption.html content="src/widget-application.ts" %} + ```ts import {Application} from '@loopback/core'; import {RestComponent, RestServer} from '@loopback/rest'; @@ -39,8 +42,8 @@ export class WidgetApplication extends Application { // (as well as handle your own!) super({ rest: { - port: 8080 - } + port: 8080, + }, }); const app = this; // For clarity. @@ -54,26 +57,27 @@ export class WidgetApplication extends Application { async stop() { // This is where you would do whatever is necessary before stopping your // app (graceful closing of connections, flushing buffers, etc) - console.log('Widget application is shutting down...') + console.log('Widget application is shutting down...'); // The superclass stop method will call stop on all servers that are // bound to the application. await super.stop(); } } - ``` ## Configuring your application -Your application can be configured with constructor arguments, bindings, or -a combination of both. + +Your application can be configured with constructor arguments, bindings, or a +combination of both. ### Binding configuration + Binding is the most commonly-demonstrated form of application configuration throughout our examples, and is the recommended method for setting up your application. -In addition to the binding functions provided by [Context](Context.md), -the `Application` class also provides some sugar functions for commonly used +In addition to the binding functions provided by [Context](Context.md), the +`Application` class also provides some sugar functions for commonly used bindings, like `component`, `server` and `controller`: ```ts @@ -111,45 +115,53 @@ export class MyApplication extends Application { } } ``` + In the above example: + - injection calls for `fooCorp.logger` will be handled by the `LogProvider` class. - injection calls for `repositories.widget` will be handled by a singleton -instance of the `WidgetRepository` class. + instance of the `WidgetRepository` class. #### Components + ```ts app.component(MyComponent); app.component(RestComponent); ``` -The `component` function allows binding of component constructors within -your `Application` instance's context. -For more information on how to make use of components, -see [Using Components](Using-components.md). +The `component` function allows binding of component constructors within your +`Application` instance's context. + +For more information on how to make use of components, see +[Using Components](Using-components.md). #### Controllers + ```ts app.controller(FooController); app.controller(BarController); ``` -Much like the component function, the `controller` function allows -binding of [Controllers](Controllers.md) to the `Application` context. + +Much like the component function, the `controller` function allows binding of +[Controllers](Controllers.md) to the `Application` context. #### Servers + ```ts app.server(RestServer); app.servers([MyServer, GrpcServer]); ``` -The `server` function is much like the previous functions, but -with [Servers](server.md) bulk bindings are possible through the function -`servers`. + +The `server` function is much like the previous functions, but with +[Servers](server.md) bulk bindings are possible through the function `servers`. ```ts const app = new Application(); app.server(RestServer, 'public'); // {'public': RestServer} app.server(RestServer, 'private'); // {'private': RestServer} ``` + In the above example, the two server instances would be bound to the Application context under the keys `servers.public`, and `servers.private` respectively. @@ -159,14 +171,13 @@ The `Application` class constructor also accepts an [`ApplicationConfig`](http://apidocs.strongloop.com/@loopback%2fcore/#ApplicationConfig) object which contains component-level configurations such as [`RestServerConfig`](http://apidocs.strongloop.com/@loopback%2frest/#RestServerConfig). -It will automatically create bindings for these configurations and later be injected -through dependency injections. Visit [Dependency Injection](Dependency-injection.md) -for more details. +It will automatically create bindings for these configurations and later be +injected through dependency injections. Visit +[Dependency Injection](Dependency-injection.md) for more details. -{% include note.html content=" - Binding configuration such as component binding, provider binding, or binding scopes - are not possible with the constructor-based configuration approach. -" %} +{% include note.html content=" Binding configuration such as component binding, +provider binding, or binding scopes are not possible with the constructor-based +configuration approach. " %} ```ts export class MyApplication extends RestApplication { @@ -174,29 +185,33 @@ export class MyApplication extends RestApplication { super({ rest: { port: 4000, - host: 'my-host' - } - }) + host: 'my-host', + }, + }); } } ``` ## Tips for application setup + Here are some tips to help avoid common pitfalls and mistakes. ### Extend from `RestApplication` when using `RestServer` -If you want to use `RestServer` from our `@loopback/rest` package, we recommend you extend -`RestApplication` in your app instead of manually binding `RestServer` or -`RestComponent`. `RestApplication` already uses `RestComponent` and makes -useful functions in `RestServer` like `handler()` available at the app level. -This means you can call these `RestServer` functions to do all of your + +If you want to use `RestServer` from our `@loopback/rest` package, we recommend +you extend `RestApplication` in your app instead of manually binding +`RestServer` or `RestComponent`. `RestApplication` already uses `RestComponent` +and makes useful functions in `RestServer` like `handler()` available at the app +level. This means you can call these `RestServer` functions to do all of your server-level setups in the app constructor without having to explicitly retrieve an instance of your server. ### Use unique bindings + Use binding names that are prefixed with a unique string that does not overlap -with loopback's bindings. As an example, if your application is built for -your employer FooCorp, you can prefix your bindings with `fooCorp`. +with loopback's bindings. As an example, if your application is built for your +employer FooCorp, you can prefix your bindings with `fooCorp`. + ```ts // This is unlikely to conflict with keys used by other component developers // or within loopback itself! @@ -204,9 +219,11 @@ app.bind('fooCorp.widgetServer.config').to(widgetServerConfig); ``` ### Avoid use of `getSync` -We provide the [`getSync`](http://apidocs.loopback.io/@loopback%2fcontext/#getSync) -function for scenarios where you cannot asynchronously retrieve your bindings, -such as in constructor bodies. + +We provide the +[`getSync`](http://apidocs.loopback.io/@loopback%2fcontext/#getSync) function +for scenarios where you cannot asynchronously retrieve your bindings, such as in +constructor bodies. However, the number of scenarios in which you must do this are limited, and you should avoid potential race conditions and retrieve your bindings asynchronously @@ -214,6 +231,7 @@ using the [`get`](http://apidocs.loopback.io/@loopback%2fcontext/#get) function whenever possible. ### Use caution with singleton binding scopes + By default, bindings for controllers will instantiate a new instance whenever they are injected or retrieved from their binding. Your application should only set singleton binding scopes on controllers when it makes sense to do so. diff --git a/docs/site/Best-practices-with-Loopback-4.md b/docs/site/Best-practices-with-Loopback-4.md index f56666f2e792..796f43e51aca 100644 --- a/docs/site/Best-practices-with-Loopback-4.md +++ b/docs/site/Best-practices-with-Loopback-4.md @@ -8,21 +8,37 @@ permalink: /doc/en/lb4/Best-practices-with-Loopback-4.html summary: --- -{% include important.html content=" -The API-first approach for building LoopBack +{% include important.html content=" The API-first approach for building LoopBack applications is not yet fully supported. Therefore, some of the sections in this page are outdated and may not work out of the box. They will be revisited after -our MVP release. -" %} +our MVP release. " %} -LoopBack 4 is more than just a framework: It’s an ecosystem that encourages developers to follow best practices through predefined standards. This section will walk through some important guidelines by building an example API for a catalog of products. +LoopBack 4 is more than just a framework: It’s an ecosystem that encourages +developers to follow best practices through predefined standards. This section +will walk through some important guidelines by building an example API for a +catalog of products. Our best practice follows an "API first" and test-driven development approach: -1. **Defining the API**: There are two possible approaches to take in this section - - [**Defining the API using code-first approach**](./Defining-the-API-using-code-first-approach.md): This section guides you through setting up a skeleton of your application so that its full OpenAPI specification can be automatically generated. - - [**Defining the API using design-first approach**](./Defining-the-API-using-design-first-approach.md): This section guides you through constructing your API first before any internal logic is added. __*Not fully supported*__ - - [**Testing the API**](./Testing-the-API.md): This section describes the process of writing smoke test for your API and its spec. __*Not fully supported*__ -2. [**Defining your testing strategy**](./Defining-your-testing-strategy.md): This section discusses the advantages and the process of building a strong testing suite. -3. [**Implementing features**](./Implementing-features.md): This section demonstrates how the tests for each feature of your application should be written, and how to write the logic to make these tests pass. In the example, the tests for the controller, model, repository, data source, and sequence are written and then implemented. -4. [**Preparing the API for consumption**](./Preparing-the-API-for-consumption.md): This section shows how the endpoints can be physically tested using the Swagger UI. +1. **Defining the API**: There are two possible approaches to take in this + section + - [**Defining the API using code-first approach**](./Defining-the-API-using-code-first-approach.md): + This section guides you through setting up a skeleton of your application + so that its full OpenAPI specification can be automatically generated. + - [**Defining the API using design-first approach**](./Defining-the-API-using-design-first-approach.md): + This section guides you through constructing your API first before any + internal logic is added. **_Not fully supported_** + - [**Testing the API**](./Testing-the-API.md): This section describes the + process of writing smoke test for your API and its spec. **_Not fully + supported_** +2. [**Defining your testing strategy**](./Defining-your-testing-strategy.md): + This section discusses the advantages and the process of building a strong + testing suite. +3. [**Implementing features**](./Implementing-features.md): This section + demonstrates how the tests for each feature of your application should be + written, and how to write the logic to make these tests pass. In the + example, the tests for the controller, model, repository, data source, and + sequence are written and then implemented. +4. [**Preparing the API for consumption**](./Preparing-the-API-for-consumption.md): + This section shows how the endpoints can be physically tested using the + Swagger UI. diff --git a/docs/site/Booting-an-Application.md b/docs/site/Booting-an-Application.md index 9340171a0f9f..549d5eee8e97 100644 --- a/docs/site/Booting-an-Application.md +++ b/docs/site/Booting-an-Application.md @@ -13,17 +13,18 @@ summary: A typical LoopBack application is made up of many artifacts in different files, organized in different folders. **Booting an Application** means: -* Discovering artifacts automatically based on a convention (a specific folder +- Discovering artifacts automatically based on a convention (a specific folder containing files with a given suffix) -* Processing those artifacts (this usually means automatically binding them to the Application's Context) +- Processing those artifacts (this usually means automatically binding them to + the Application's Context) `@loopback/boot` provides a Bootstrapper that uses Booters to automatically discover and bind artifacts, all packaged in an easy-to-use Mixin. ### What is an artifact? -An artifact is any LoopBack construct usually defined in code as a Class. LoopBack -constructs include Controllers, Repositories, Models, etc. +An artifact is any LoopBack construct usually defined in code as a Class. +LoopBack constructs include Controllers, Repositories, Models, etc. ## Usage @@ -35,34 +36,37 @@ followed by the CLI. ### Adding to existing project -See [Using the BootMixin](#using-the-bootmixin) to add Boot to your Project manually. +See [Using the BootMixin](#using-the-bootmixin) to add Boot to your Project +manually. --- -The rest of this page describes the inner workings of `@loopback/boot` for advanced use -cases, manual usage or using `@loopback/boot` as a standalone package (with custom -booters). +The rest of this page describes the inner workings of `@loopback/boot` for +advanced use cases, manual usage or using `@loopback/boot` as a standalone +package (with custom booters). ## BootMixin -Boot functionality can be added to a LoopBack 4 Application by mixing it with the -`BootMixin`. The Mixin adds the `BootComponent` to your Application as well as -convenience methods such as `app.boot()` and `app.booters()`. The Mixin also allows -Components to set the property `booters` as an Array of `Booters`. They will be bound -to the Application and called by the `Bootstrapper`. +Boot functionality can be added to a LoopBack 4 Application by mixing it with +the `BootMixin`. The Mixin adds the `BootComponent` to your Application as well +as convenience methods such as `app.boot()` and `app.booters()`. The Mixin also +allows Components to set the property `booters` as an Array of `Booters`. They +will be bound to the Application and called by the `Bootstrapper`. -Since this is a convention-based Bootstrapper, it is important to set a `projectRoot`, -as all other artifact paths will be resolved relative to this path. +Since this is a convention-based Bootstrapper, it is important to set a +`projectRoot`, as all other artifact paths will be resolved relative to this +path. -_Tip_: `application.ts` will likely be at the root of your project, so its path can be -used to set the `projectRoot` by using the `__dirname` variable. _(See example below)_ +_Tip_: `application.ts` will likely be at the root of your project, so its path +can be used to set the `projectRoot` by using the `__dirname` variable. _(See +example below)_ ### Using the BootMixin -`Booter` and `Binding` types must be imported alongside `BootMixin` to allow TypeScript -to infer types and avoid errors. _If using `tslint` with the `no-unused-variable` rule, -you can disable it for the import line by adding `// tslint:disable-next-line:no-unused-variable` -above the import statement_. +`Booter` and `Binding` types must be imported alongside `BootMixin` to allow +TypeScript to infer types and avoid errors. _If using `tslint` with the +`no-unused-variable` rule, you can disable it for the import line by adding +`// tslint:disable-next-line:no-unused-variable` above the import statement_. ```ts import {BootMixin, Booter, Binding} from "@loopback/boot"; @@ -84,14 +88,15 @@ class MyApplication extends BootMixin(Application) { } ``` -Now just call `app.boot()` from `index.ts` before starting your Application using `app.start()`. +Now just call `app.boot()` from `index.ts` before starting your Application +using `app.start()`. #### app.boot() A convenience method to retrieve the `Bootstrapper` instance bound to the Application and calls its `boot` function. This should be called before an -Application's `start()` method is called. _This is an `async` function and should -be called with `await`._ +Application's `start()` method is called. _This is an `async` function and +should be called with `await`._ ```ts class MyApp extends BootMixin(Application) {} @@ -106,9 +111,9 @@ async main() { #### app.booters() -A convenience method to manually bind `Booters`. You can pass any number of `Booter` -classes to this method and they will all be bound to the Application using the -prefix (`booters.`) and tag (`booter`) used by the `Bootstrapper`. +A convenience method to manually bind `Booters`. You can pass any number of +`Booter` classes to this method and they will all be bound to the Application +using the prefix (`booters.`) and tag (`booter`) used by the `Bootstrapper`. ```ts // Binds MyCustomBooter to `booters.MyCustomBooter` @@ -119,13 +124,14 @@ app.booters(MyCustomBooter, AnotherCustomBooter); ## BootComponent -This component is added to an Application by `BootMixin` if used. This Component: +This component is added to an Application by `BootMixin` if used. This +Component: -* Provides a list of default `booters` as a property of the component -* Binds the conventional Bootstrapper to the Application +- Provides a list of default `booters` as a property of the component +- Binds the conventional Bootstrapper to the Application -_If using this as a standalone component without the `BootMixin`, you will need to -bind the `booters` of a component manually._ +_If using this as a standalone component without the `BootMixin`, you will need +to bind the `booters` of a component manually._ ```ts app.component(BootComponent); @@ -133,22 +139,24 @@ app.component(BootComponent); ## Bootstrapper -A Class that acts as the "manager" for Booters. The Bootstrapper is designed to be -bound to an Application as a `SINGLETON`. The Bootstrapper class provides a `boot()` -method. This method is responsible for getting all bound `Booters` and running -their `phases`. A `phase` is a method on a `Booter` class. +A Class that acts as the "manager" for Booters. The Bootstrapper is designed to +be bound to an Application as a `SINGLETON`. The Bootstrapper class provides a +`boot()` method. This method is responsible for getting all bound `Booters` and +running their `phases`. A `phase` is a method on a `Booter` class. -Each `boot()` method call creates a new `Context` that sets the `app` context -as its parent. This is done so each `Context` for `boot` gets a new instance of -`booters` but the same context can be passed into `boot` so selective `phases` can be -run in different calls of `boot`. +Each `boot()` method call creates a new `Context` that sets the `app` context as +its parent. This is done so each `Context` for `boot` gets a new instance of +`booters` but the same context can be passed into `boot` so selective `phases` +can be run in different calls of `boot`. -The Bootstrapper can be configured to run specific booters or boot phases -by passing in `BootExecOptions`. **This is experimental and subject to change. Hence, -this functionality is not exposed when calling `boot()` via `BootMixin`**. +The Bootstrapper can be configured to run specific booters or boot phases by +passing in `BootExecOptions`. **This is experimental and subject to change. +Hence, this functionality is not exposed when calling `boot()` via +`BootMixin`**. -To use `BootExecOptions`, you must directly call `bootstrapper.boot()` instead of `app.boot()`. -You can pass in the `BootExecOptions` object with the following properties: +To use `BootExecOptions`, you must directly call `bootstrapper.boot()` instead +of `app.boot()`. You can pass in the `BootExecOptions` object with the following +properties: | Property | Type | Description | | ---------------- | ----------------------- | ------------------------------------------------ | @@ -159,38 +167,39 @@ You can pass in the `BootExecOptions` object with the following properties: ### Example ```ts -import { BootMixin, Booter, Binding, Bootstrapper } from "@loopback/boot"; +import {BootMixin, Booter, Binding, Bootstrapper} from '@loopback/boot'; class MyApp extends BootMixin(Application) {} const app = new MyApp(); app.projectRoot = __dirname; const bootstrapper: Bootstrapper = await this.get( - BootBindings.BOOTSTRAPPER_KEY + BootBindings.BOOTSTRAPPER_KEY, ); bootstrapper.boot({ booters: [MyCustomBooter], filter: { - booters: ["MyCustomBooter"], - phases: ["configure", "discover"] // Skip the `load` phase. - } + booters: ['MyCustomBooter'], + phases: ['configure', 'discover'], // Skip the `load` phase. + }, }); ``` ## Booters -A Booter is a class that is responsible for booting an artifact. A Booter does its -work in `phases` which are called by the Bootstrapper. The following Booters are -a part of the `@loopback/boot` package and loaded automatically via `BootMixin`. +A Booter is a class that is responsible for booting an artifact. A Booter does +its work in `phases` which are called by the Bootstrapper. The following Booters +are a part of the `@loopback/boot` package and loaded automatically via +`BootMixin`. ### Controller Booter -This Booter's purpose is to discover [Controller](Controllers.md) type Artifacts and to bind -them to the Application's Context. +This Booter's purpose is to discover [Controller](Controllers.md) type Artifacts +and to bind them to the Application's Context. -You can configure the conventions used in your -project for a Controller by passing a `controllers` object on `BootOptions` property -of your Application. The `controllers` object supports the following options: +You can configure the conventions used in your project for a Controller by +passing a `controllers` object on `BootOptions` property of your Application. +The `controllers` object supports the following options: | Options | Type | Default | Description | | ------------ | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------- | @@ -201,13 +210,14 @@ of your Application. The `controllers` object supports the following options: ### Repository Booter -This Booter's purpose is to discover [Repository](Repositories.md) type Artifacts and to bind -them to the Application's Context. The use of this Booter requires `RepositoryMixin` -from `@loopback/repository` to be mixed into your Application class. +This Booter's purpose is to discover [Repository](Repositories.md) type +Artifacts and to bind them to the Application's Context. The use of this Booter +requires `RepositoryMixin` from `@loopback/repository` to be mixed into your +Application class. -You can configure the conventions used in your -project for a Repository by passing a `repositories` object on `BootOptions` property -of your Application. The `repositories` object supports the following options: +You can configure the conventions used in your project for a Repository by +passing a `repositories` object on `BootOptions` property of your Application. +The `repositories` object supports the following options: | Options | Type | Default | Description | | ------------ | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------- | @@ -218,10 +228,11 @@ of your Application. The `repositories` object supports the following options: ### Custom Booters -A custom Booter can be written as a Class that implements the `Booter` interface. The Class -must implement methods that corresponds to a `phase` name. The `phases` are called -by the Bootstrapper in a pre-determined order (unless overridden by `BootExecOptions`). -The next phase is only called once the previous phase has been completed for all Booters. +A custom Booter can be written as a Class that implements the `Booter` +interface. The Class must implement methods that corresponds to a `phase` name. +The `phases` are called by the Bootstrapper in a pre-determined order (unless +overridden by `BootExecOptions`). The next phase is only called once the +previous phase has been completed for all Booters. #### Phases diff --git a/docs/site/Calling-other-APIs-and-Web-Services.md b/docs/site/Calling-other-APIs-and-Web-Services.md index 1d3f7a506eb4..7526617f03fe 100644 --- a/docs/site/Calling-other-APIs-and-Web-Services.md +++ b/docs/site/Calling-other-APIs-and-Web-Services.md @@ -7,9 +7,12 @@ sidebar: lb4_sidebar permalink: /doc/en/lb4/Calling-other-APIs-and-web-services.html summary: --- -Your API implementation often needs to interact with REST APIs, SOAP Web Services or other forms of APIs. + +Your API implementation often needs to interact with REST APIs, SOAP Web +Services or other forms of APIs. How to: + - Set up a Service - Use services with controllers - Reuse models between services and controllers diff --git a/docs/site/Command-line-interface.md b/docs/site/Command-line-interface.md index 0d2bae9e5226..e51f2b97fe06 100644 --- a/docs/site/Command-line-interface.md +++ b/docs/site/Command-line-interface.md @@ -8,13 +8,15 @@ permalink: /doc/en/lb4/Command-line-interface.html summary: --- -LoopBack 4 provides command-line tools to help you get started quickly. The command line tools generate application and extension projects and install their dependencies for you. -The CLI can also help you generate artifacts, such as controllers, for your projects. -Once generated, the scaffold can be expanded with users' own code as needed. +LoopBack 4 provides command-line tools to help you get started quickly. The +command line tools generate application and extension projects and install their +dependencies for you. The CLI can also help you generate artifacts, such as +controllers, for your projects. Once generated, the scaffold can be expanded +with users' own code as needed. To use LoopBack 4's CLI, run this command: -``` +```sh npm install -g @loopback/cli ``` diff --git a/docs/site/Context.md b/docs/site/Context.md index 636963ba5e31..b6cdff5af65e 100644 --- a/docs/site/Context.md +++ b/docs/site/Context.md @@ -13,18 +13,20 @@ summary: - An abstraction of all state and dependencies in your application. - Context is what LoopBack uses to "manage" everything. - A global registry for anything/everything in your app (all configs, state, -dependencies, classes, etc). -- An [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) container used to inject dependencies into your code. container used to inject dependencies into your code. + dependencies, classes, etc). +- An [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) + container used to inject dependencies into your code. container used to inject + dependencies into your code. ### Why is it important? - You can use the context as a way to give loopback more "info" so that other -dependencies in your app may retrieve it (ie. a centralized place/global -builtin/in-memory storage mechanism). + dependencies in your app may retrieve it (ie. a centralized place/global + builtin/in-memory storage mechanism). - LoopBack can help "manage" your resources automatically (through -[Dependency Injection](Dependency-injection.md) and decorators). + [Dependency Injection](Dependency-injection.md) and decorators). - You have full access to updated/real-time application+request state at all -times. + times. ## How to create a context? @@ -40,16 +42,16 @@ const serverCtx = new Context(rootCtx, 'server-ctx'); // rootCtx as the parent const reqCtx = new Context(serverCtx); // No explicit name, a UUID will be generated ``` -LoopBack's context system allows an unlimited amount of Context instances, -each of which may have a parent Context. However, an application typically -has three "levels" of context: application-level, server-level and request-level. +LoopBack's context system allows an unlimited amount of Context instances, each +of which may have a parent Context. However, an application typically has three +"levels" of context: application-level, server-level and request-level. ## Application-level context (global) - stores all the initial and modified app state throughout the entire life of -the app (while the process is alive) + the app (while the process is alive) - Generally configured when the application is created (though other things may -modify things in the context while alive/running) + modify things in the context while alive/running) Here is a simple example: @@ -68,15 +70,17 @@ into the Application Context (`app` is a Context). ## Server-level context Server-level context: + - Is a child of application-level context - Holds configuration specific to a particular server instance Your application will typically contain one or more server instances, each of -which will have the application-level context as its parent. This means that -any bindings that are defined on the application will also be available to the +which will have the application-level context as its parent. This means that any +bindings that are defined on the application will also be available to the server(s), unless you replace these bindings on the server instance(s) directly. -For example, [`@loopback/rest`](https://github.com/strongloop/loopback-next/blob/master/packages/rest) +For example, +[`@loopback/rest`](https://github.com/strongloop/loopback-next/blob/master/packages/rest) has the `RestServer` class, which sets up a running HTTP/S server on a port, as well as defining routes on that server for a REST API. To set the port binding for the `RestServer`, you would bind the `RestBindings.PORT` key to a number. @@ -98,10 +102,13 @@ async start() { ## Request-level context (request) -Using [`@loopback/rest`](https://github.com/strongloop/loopback-next/blob/master/packages/rest) as an -example, we can create custom sequences that: +Using +[`@loopback/rest`](https://github.com/strongloop/loopback-next/blob/master/packages/rest) +as an example, we can create custom sequences that: + - are dynamically created for each incoming server request -- extend the application level context (to give you access to application level dependencies during the request/response lifecycle) +- extend the application level context (to give you access to application level + dependencies during the request/response lifecycle) - are garbage collected once the response is sent (memory management) Let's see this in action: @@ -118,21 +125,21 @@ class MySequence extends DefaultSequence { - `this.ctx` is available to your sequence - allows you to craft your response using resources from the app in addition to -the resources available to the request in real-time (right when you need it) + the resources available to the request in real-time (right when you need it) - `getSync` is one way to get stuff out of the context, there are many others, -see below + see below ## Storing and retrieving items from a Context -Items in the Context are indexed via a key and bound to a `ContextValue`. -A `ContextKey` is simply a string value and is used to look up whatever you -store along with the key. For example: +Items in the Context are indexed via a key and bound to a `ContextValue`. A +`ContextKey` is simply a string value and is used to look up whatever you store +along with the key. For example: ```js // app level const app = new Application(); app.bind('hello').to('world'); // ContextKey='hello', ContextValue='world' -console.log(app.getSync('hello')); // => 'world' +console.log(app.getSync < string > 'hello'); // => 'world' ``` In this case, we bind the 'world' string ContextValue to the 'hello' ContextKey. @@ -143,14 +150,15 @@ too -- ie. instantiate your classes, etc) The process of registering a ContextValue into the Context is known as _binding_. Sequence-level bindings work the same way (shown 2 examples before). -For a list of the available functions you can use for binding, visit -the [Context API Docs](http://apidocs.loopback.io/@loopback%2fcontext). +For a list of the available functions you can use for binding, visit the +[Context API Docs](http://apidocs.loopback.io/@loopback%2fcontext). ## Dependency injection -- Many configs are adding to the Context during app instantiation/boot time by you/developer. +- Many configs are adding to the Context during app instantiation/boot time by + you/developer. - When things are registered, the Context provides a way to use your -dependencies during runtime. + dependencies during runtime. How you access these things is via low level helpers like `app.getSync` or the `sequence` class that is provided to you as shown in the example in the previous @@ -177,8 +185,8 @@ class HelloController { Notice we just use the default name as though it were available to the constructor. Context allows LoopBack to give you the necessary information at -runtime even if you do not know the value when writing up the Controller. -The above will print `Hello John` at run time. +runtime even if you do not know the value when writing up the Controller. The +above will print `Hello John` at run time. Please refer to [Dependency injection](Dependency-injection.md) for further details. @@ -200,13 +208,13 @@ class HelloController { // tell LoopBack you want to accept // the name parameter as a string from // the query string - @param.query.string('name') - name: string) { + @param.query.string('name') name: string, + ) { return `Hello ${name}`; } } ``` These "sugar" decorators allow you to quickly build up your application without -having to code up all the additional logic by simply giving LoopBack hints -(in the form of metadata) to your intent. +having to code up all the additional logic by simply giving LoopBack hints (in +the form of metadata) to your intent. diff --git a/docs/site/Controller-generator.md b/docs/site/Controller-generator.md index 42dfddf299c5..11a29a7b3bb9 100644 --- a/docs/site/Controller-generator.md +++ b/docs/site/Controller-generator.md @@ -14,39 +14,39 @@ summary: Adds a new empty controller to a LoopBack application. -``` +```sh lb4 controller [options] [] ``` ### Options -`--controllerType` -: Type of the controller. +`--controllerType` : Type of the controller. -Valid types are `BASIC` and `REST`. -`BASIC` corresponds to an empty controller, whereas `REST` corresponds to -REST controller with CRUD methods. +Valid types are `BASIC` and `REST`. `BASIC` corresponds to an empty controller, +whereas `REST` corresponds to REST controller with CRUD methods. {% include_relative includes/CLI-std-options.md %} ### Arguments -`` - Optional name of the controller to create as an argument to the command.  -If provided, the tool will use that as the default when it prompts for the name. +`` - Optional name of the controller to create as an argument to the +command.  If provided, the tool will use that as the default when it prompts for +the name. ### Interactive Prompts The tool will prompt you for: - Name of the controller. If the name had been supplied from the command line, -the prompt is skipped and the controller is built with the name from the -command-line argument. + the prompt is skipped and the controller is built with the name from the + command-line argument. - Type of the controller. You can select from the following types: - * **Empty Controller** - An empty controller definition - * **REST Controller with CRUD Methods** - A controller wired up to a model - and repository definition, with pre-defined CRUD methods. + - **Empty Controller** - An empty controller definition + - **REST Controller with CRUD Methods** - A controller wired up to a model and + repository definition, with pre-defined CRUD methods. #### Empty Controller + If you select the Empty Controller, it will generate a nearly-empty template based on the given name: @@ -61,19 +61,19 @@ export class FooController { ``` #### REST Controller with CRUD Methods + If you select the REST Controller with CRUD Methods type, you will then be asked to select: + - The model to use for the CRUD function definitions - The repository for this model that provides datasource connectivity -{% include warning.html content= -" -If you do not have a model and repository to select, then you will -receive an error! -" lang=page.lang %} +{% include warning.html content= " If you do not have a model and repository to +select, then you will receive an error! " lang=page.lang %} + +Here's an example of what the template will produce given a `Todo` model and a +`TodoRepository`: -Here's an example of what the template will produce given a `Todo` model and -a `TodoRepository`: ```ts import {Filter, Where} from '@loopback/repository'; import {post, param, get, put, patch, del} from '@loopback/openapi-v2'; @@ -82,53 +82,54 @@ import {Todo} from '../models'; import {TodoRepository} from '../repositories'; export class TodoController { - constructor( @inject('repositories.TodoRepository') - public todoRepository : TodoRepository, + public todoRepository: TodoRepository, ) {} @post('/todo') - async create(@param.body('obj') obj: Todo) - : Promise { + async create(@param.body('obj') obj: Todo): Promise { return await this.todoRepository.create(obj); } @get('/todo/count') - async count(@param.query.string('where') where: Where) : Promise { + async count(@param.query.string('where') where: Where): Promise { return await this.todoRepository.count(where); } @get('/todo') - async find(@param.query.string('filter') filter: Filter) - : Promise { - return await this.todoRepository.find(filter); + async find(@param.query.string('filter') filter: Filter): Promise { + return await this.todoRepository.find(filter); } @patch('/todo') - async updateAll(@param.query.string('where') where: Where, - @param.body('obj') obj: Todo) : Promise { - return await this.todoRepository.updateAll(where, obj); + async updateAll( + @param.query.string('where') where: Where, + @param.body('obj') obj: Todo, + ): Promise { + return await this.todoRepository.updateAll(where, obj); } @del('/todo') - async deleteAll(@param.query.string('where') where: Where) : Promise { + async deleteAll(@param.query.string('where') where: Where): Promise { return await this.todoRepository.deleteAll(where); } @get('/todo/{id}') - async findById(@param.path.number('id') id: number) : Promise { + async findById(@param.path.number('id') id: number): Promise { return await this.todoRepository.findById(id); } @patch('/todo/{id}') - async updateById(@param.path.number('id') id: number, @param.body('obj') - obj: Todo) : Promise { + async updateById( + @param.path.number('id') id: number, + @param.body('obj') obj: Todo, + ): Promise { return await this.todoRepository.updateById(id, obj); } @del('/todo/{id}') - async deleteById(@param.path.number('id') id: number) : Promise { + async deleteById(@param.path.number('id') id: number): Promise { return await this.todoRepository.deleteById(id); } } diff --git a/docs/site/Controllers.md b/docs/site/Controllers.md index 305cb54abc2f..ab0af2083ddd 100644 --- a/docs/site/Controllers.md +++ b/docs/site/Controllers.md @@ -262,7 +262,6 @@ describe('Hello Controller', () => { }); }); }); - ``` ```ts diff --git a/docs/site/Crafting-LoopBack-4.md b/docs/site/Crafting-LoopBack-4.md index 20dca57193c2..5a2c40d72e17 100644 --- a/docs/site/Crafting-LoopBack-4.md +++ b/docs/site/Crafting-LoopBack-4.md @@ -7,215 +7,351 @@ sidebar: lb4_sidebar permalink: /doc/en/lb4/Crafting-LoopBack-4.html summary: --- + ## Background -[LoopBack](http://loopback.io) is an open-source [Node.js](https://nodejs.org) framework built for API developers. Its primary goal is to help create APIs as microservices from existing services/databases and expose them as endpoints for client applications, such as web, mobile, and IoT. LoopBack connects the dots between accepting API requests and interacting with backend resources. By facilitating developers to implement API logic with out of box integration capabilities, LoopBack establishes itself as the API composition layer to [differentiate](http://loopback.io/resources/#compare) from other frameworks, such as [Express](https://expressjs.com), [Hapi](https://hapijs.com), and [Sails](http://sailsjs.com). +[LoopBack](http://loopback.io) is an open-source [Node.js](https://nodejs.org) +framework built for API developers. Its primary goal is to help create APIs as +microservices from existing services/databases and expose them as endpoints for +client applications, such as web, mobile, and IoT. LoopBack connects the dots +between accepting API requests and interacting with backend resources. By +facilitating developers to implement API logic with out of box integration +capabilities, LoopBack establishes itself as the API composition layer to +[differentiate](http://loopback.io/resources/#compare) from other frameworks, +such as [Express](https://expressjs.com), [Hapi](https://hapijs.com), and +[Sails](http://sailsjs.com). ![loopback-composition](./imgs/loopback-composition.png) -Up to version 3.x, LoopBack built on the popular [Express framework](https://expressjs.com). In retrospect, basing LoopBack on Express was the right decision: Standing on the shoulders of Express enabled LoopBack to focus on adding value for API creation experience without reinventing the wheel. LoopBack also has benefitted from the Express ecosystem, especially ready-to-use middleware modules from npm as well as valuable knowledge and support by the community. - -With LoopBack, developers can create and expose APIs just like following a recipe. LoopBack introduces a set of [core concepts](https://loopback.io/doc/en/lb3/LoopBack-core-concepts) that represent the key aspects of API implementation. To create APIs out of existing databases or services, developers can simply scaffold a LoopBack application, then add necessary JSON declarations and Node.js code to get their APIs up and running in a few minutes. - -LoopBack uses Express routing and middleware as the plumbing to a request/response pipeline for API use cases, such as authentication, authorization, and routing. Beyond inbound HTTP processing, LoopBack provides integration facilities such as models, datasources, and connectors to allow API logic to interact with various backend systems, including but not limited to, databases, REST APIs, SOAP web services and gRPC microservices. The ability to glue inbound communication and outbound integration makes LoopBack a very powerful framework for API developers. The diagram below illustrates how LoopBack fits into a typical end-to-end API processing flow. +Up to version 3.x, LoopBack built on the popular +[Express framework](https://expressjs.com). In retrospect, basing LoopBack on +Express was the right decision: Standing on the shoulders of Express enabled +LoopBack to focus on adding value for API creation experience without +reinventing the wheel. LoopBack also has benefitted from the Express ecosystem, +especially ready-to-use middleware modules from npm as well as valuable +knowledge and support by the community. + +With LoopBack, developers can create and expose APIs just like following a +recipe. LoopBack introduces a set of +[core concepts](https://loopback.io/doc/en/lb3/LoopBack-core-concepts) that +represent the key aspects of API implementation. To create APIs out of existing +databases or services, developers can simply scaffold a LoopBack application, +then add necessary JSON declarations and Node.js code to get their APIs up and +running in a few minutes. + +LoopBack uses Express routing and middleware as the plumbing to a +request/response pipeline for API use cases, such as authentication, +authorization, and routing. Beyond inbound HTTP processing, LoopBack provides +integration facilities such as models, datasources, and connectors to allow API +logic to interact with various backend systems, including but not limited to, +databases, REST APIs, SOAP web services and gRPC microservices. The ability to +glue inbound communication and outbound integration makes LoopBack a very +powerful framework for API developers. The diagram below illustrates how +LoopBack fits into a typical end-to-end API processing flow. ![loopback-overview](./imgs/loopback-overview.png) -LoopBack has grown significantly in features and users with many years of development and multiple releases. LoopBack has been well-recieved by the developer community. As an indication, the community has developed [many extensions](https://github.com/pasindud/awesome-loopback). The core team has also learned a lot from what we have done as well as great feedback from the community. +LoopBack has grown significantly in features and users with many years of +development and multiple releases. LoopBack has been well-recieved by the +developer community. As an indication, the community has developed +[many extensions](https://github.com/pasindud/awesome-loopback). The core team +has also learned a lot from what we have done as well as great feedback from the +community. ## Why LoopBack 4? -Like many projects, LoopBack has started to experience growing pains, especially as: - -1. The code base becomes more complicated over time with more modules and more functionality. We would like to have more maintainers and contributors to help out. But the learning curve is getting steep. One of the things to blame is JavaScript itself, which is weakly-typed and lack of constructs such as interfaces to explicitly define contracts between code. There is quite a bit hidden knowledge that is not explicit or obvious for new comers. - -2. Technical debt is accumulating, for example inconsistent designs across modules and feature flags for different behaviors. Here are a few examples: - - Various modules use different registries to manage different types of artifacts, such as remoting metadata, models, datasources, and middleware. - - Different flavors are used to allow custom logic to intercept requests/responses at various layers, such as middleware, remote hooks, CRUD operation hooks, and connector hooks. - - More feature flags were added over time to keep backward compatibility while enabling users to opt-in to new behaviors. - -3. It is becoming more difficult to add new features or fix bugs as some areas start to reach the limit of the current design. - - The `loopback-datasource-juggler` module is a kitchen sink for many things, such as typing, data modeling, validation, aggregation, persistence, and service integration. - - Models are overloaded with multiple responsibilities, such as data representation, persistence, and mapping to REST. Models are tied to datasources and it's not easy to reuse the same model definition against different datasources. - -4. It's not easy to extend the framework without requesting the core team to make code changes in LoopBack modules. The current version of LoopBack has ad-hoc extensibility at various layers. Extension points are not consistently defined. For example, - - Use Express to register middleware. - - Use remoting hooks to intercept remote method invocations. - - Use CRUD hooks to add logic around CRUD operations. - -5. More projects start to use LoopBack as the underlying platform. Such use cases require more knowledge of LoopBack internals and more flexibility and dynamicity to leverage LoopBack to manage and compose artifacts using a metadata driven approach. Some good examples are: - - Multi-tenancy which requires artifact isolation between tenants. - - Metadata APIs to manage/activate model definitions and datasources. - - New interaction patterns for connectors, such as eventing or messaging. - - Extra metadata for model definitions. - -Since the release of 3.x, the team has been brainstorming about how to sustain and advance LoopBack. We did a lot of homework, triaged existing GitHub issues, reached out to community members and downstream products, and evaluated relevant frameworks and technologies to answer to the following questions: - -- Who is the target audience of LoopBack? Why are they interested in LoopBack? What do they use LoopBack for and how do they use it? -- What are the critical pain points? Can we address them incrementally without rebuilding a new foundation? -- What are the most requested features? Is it possible to add such features with the current design? -- What are latest and greatest technologies in our space? What value will they bring in if we start to adopt them? -- How to scale the development and maintenance of LoopBack? How do we allow larger development teams to collaborate on creating APIs using LoopBack? -- How to further grow the community and expand its ecosystem? What can we do to bring more users and contributors to LoopBack? - -LoopBack has gained traction among a spectrum of users beyond Node.js application developers, including: +Like many projects, LoopBack has started to experience growing pains, especially +as: + +1. The code base becomes more complicated over time with more modules and more + functionality. We would like to have more maintainers and contributors to + help out. But the learning curve is getting steep. One of the things to + blame is JavaScript itself, which is weakly-typed and lack of constructs + such as interfaces to explicitly define contracts between code. There is + quite a bit hidden knowledge that is not explicit or obvious for new comers. + +2. Technical debt is accumulating, for example inconsistent designs across + modules and feature flags for different behaviors. Here are a few examples: + + - Various modules use different registries to manage different types of + artifacts, such as remoting metadata, models, datasources, and middleware. + - Different flavors are used to allow custom logic to intercept + requests/responses at various layers, such as middleware, remote hooks, + CRUD operation hooks, and connector hooks. + - More feature flags were added over time to keep backward compatibility + while enabling users to opt-in to new behaviors. + +3. It is becoming more difficult to add new features or fix bugs as some areas + start to reach the limit of the current design. + + - The `loopback-datasource-juggler` module is a kitchen sink for many + things, such as typing, data modeling, validation, aggregation, + persistence, and service integration. + - Models are overloaded with multiple responsibilities, such as data + representation, persistence, and mapping to REST. Models are tied to + datasources and it's not easy to reuse the same model definition against + different datasources. + +4. It's not easy to extend the framework without requesting the core team to + make code changes in LoopBack modules. The current version of LoopBack has + ad-hoc extensibility at various layers. Extension points are not + consistently defined. For example, + + - Use Express to register middleware. + - Use remoting hooks to intercept remote method invocations. + - Use CRUD hooks to add logic around CRUD operations. + +5. More projects start to use LoopBack as the underlying platform. Such use + cases require more knowledge of LoopBack internals and more flexibility and + dynamicity to leverage LoopBack to manage and compose artifacts using a + metadata driven approach. Some good examples are: + - Multi-tenancy which requires artifact isolation between tenants. + - Metadata APIs to manage/activate model definitions and datasources. + - New interaction patterns for connectors, such as eventing or messaging. + - Extra metadata for model definitions. + +Since the release of 3.x, the team has been brainstorming about how to sustain +and advance LoopBack. We did a lot of homework, triaged existing GitHub issues, +reached out to community members and downstream products, and evaluated relevant +frameworks and technologies to answer to the following questions: + +- Who is the target audience of LoopBack? Why are they interested in LoopBack? + What do they use LoopBack for and how do they use it? +- What are the critical pain points? Can we address them incrementally without + rebuilding a new foundation? +- What are the most requested features? Is it possible to add such features with + the current design? +- What are latest and greatest technologies in our space? What value will they + bring in if we start to adopt them? +- How to scale the development and maintenance of LoopBack? How do we allow + larger development teams to collaborate on creating APIs using LoopBack? +- How to further grow the community and expand its ecosystem? What can we do to + bring more users and contributors to LoopBack? + +LoopBack has gained traction among a spectrum of users beyond Node.js +application developers, including: - **API developers** - Use LoopBack to create APIs in Node.js. -- **LoopBack maintainers and contributors** - Build and maintain modules by the LoopBack project . -- **Extension developers** - Contribute extensions to LoopBack to augment the framework. -- **Platform developers** - Leverage LoopBack as the base to build their value-added offerings. +- **LoopBack maintainers and contributors** - Build and maintain modules by the + LoopBack project . +- **Extension developers** - Contribute extensions to LoopBack to augment the + framework. +- **Platform developers** - Leverage LoopBack as the base to build their + value-added offerings. ![loopback-ecosystem](./imgs/loopback-ecosystem.png) -The core team decided to make a bold move and rebuild LoopBack to meet the needs of all the above groups. -The decision led to the inception of LoopBack 4, a new generation of API creation platform. -For more information, read the blog post [Announcing LoopBack.next, the Next Step to Make LoopBack Effortlessly Extensible](https://strongloop.com/strongblog/announcing-loopback-next). +The core team decided to make a bold move and rebuild LoopBack to meet the needs +of all the above groups. The decision led to the inception of LoopBack 4, a new +generation of API creation platform. For more information, read the blog post +[Announcing LoopBack.next, the Next Step to Make LoopBack Effortlessly Extensible](https://strongloop.com/strongblog/announcing-loopback-next). ## Objectives LoopBack 4's goals are: 1. Catch up with latest and greatest technology advances. - - Adopt [ES2016/2017](http://exploringjs.com/es2016-es2017/index.html) and [TypeScript](https://www.typescriptlang.org/) for ease of maintenance and productivity. - - Embrace new standards such as [OpenAPI Spec](https://www.openapis.org/) and [GraphQL](http://graphql.org/). + + - Adopt [ES2016/2017](http://exploringjs.com/es2016-es2017/index.html) and + [TypeScript](https://www.typescriptlang.org/) for ease of maintenance and + productivity. + - Embrace new standards such as [OpenAPI Spec](https://www.openapis.org/) + and [GraphQL](http://graphql.org/). 2. Promote extensibility to grow the ecosystem. - - Build a minimal core and enable everything else to be implemented via extensions. - - Open the door for more [extension points and extensions](https://github.com/strongloop/loopback-next/issues/512). + + - Build a minimal core and enable everything else to be implemented via + extensions. + - Open the door for more + [extension points and extensions](https://github.com/strongloop/loopback-next/issues/512). 3. Align with cloud native experience for microservices. - - Adopt cloud native microservices by adopting initiatives such as [Cloud Native Computing Foundation](https://www.cncf.io/). + + - Adopt cloud native microservices by adopting initiatives such as + [Cloud Native Computing Foundation](https://www.cncf.io/). - Make LoopBack a first-class citizen of the microservices ecosystem. 4. Remove the complexity and inconsistency across modules. - - Use a consistent registry and APIs to manage artifacts and their dependencies. + + - Use a consistent registry and APIs to manage artifacts and their + dependencies. - Pay down technical debts by refactoring complex modules. 5. Separate concerns for better composability. - - Introduce new concepts such as controllers and repositories to represent different responsibilities. - - Break down the runtime as a set of services and utilize the extension points/extensions pattern to manage the registration, resolution, and composition. + - Introduce new concepts such as controllers and repositories to represent + different responsibilities. + - Break down the runtime as a set of services and utilize the extension + points/extensions pattern to manage the registration, resolution, and + composition. ## Design principles -We decided not to take a "big-bang" approach to build LoopBack 4. Instead, we are doing it incrementally in multiple stages with smaller steps. This approach allows us to better engage the community from the beginning. We are following the principles below to pursue architectural simplicity and extensibility: +We decided not to take a "big-bang" approach to build LoopBack 4. Instead, we +are doing it incrementally in multiple stages with smaller steps. This approach +allows us to better engage the community from the beginning. We are following +the principles below to pursue architectural simplicity and extensibility: 1. **Imperative first, declarative later** - Everything can be done by code via `APIs`. The LoopBack team or community contributors can then create varieties of user experiences with such APIs. For example, with APIs to define models, we allow applications to declare models in JSON or YAML files so that they can be discovered and loaded. An extension can parse other forms of model definition, such as JSON schemas, ES6 classes with decorators, schemas in OpenAPI spec, or even XML schemas into LoopBack model definitions. + Everything can be done by code via `APIs`. The LoopBack team or community + contributors can then create varieties of user experiences with such APIs. + For example, with APIs to define models, we allow applications to declare + models in JSON or YAML files so that they can be discovered and loaded. An + extension can parse other forms of model definition, such as JSON schemas, + ES6 classes with decorators, schemas in OpenAPI spec, or even XML schemas + into LoopBack model definitions. - We can also leverage programming constructs such as [decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) allow developers to supply metadata in code. Furthermore, LoopBack artifacts can be declared in JSON or YAML files, which will be handy for users to generate and manipulate them by hand or tooling. + We can also leverage programming constructs such as + [decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) + allow developers to supply metadata in code. Furthermore, LoopBack artifacts + can be declared in JSON or YAML files, which will be handy for users to + generate and manipulate them by hand or tooling. 2. **Build minimum features and add more later if necessary** - Apply YAGNI (You Aint’t Gonna Need It). Design and build for what is needed now, not for what you think you may need in the future. There are many different perspectives in API creation and people ask for a lot of features. Starting with MVP allow us to reach the root of the issues without being derailed by noises and build the absolutely necessary features as the core building blocks. + Apply YAGNI (You Aint’t Gonna Need It). Design and build for what is needed + now, not for what you think you may need in the future. There are many + different perspectives in API creation and people ask for a lot of features. + Starting with MVP allow us to reach the root of the issues without being + derailed by noises and build the absolutely necessary features as the core + building blocks. 3. **Developer experience first** - Always keep in mind that LoopBack is built for developers by developers. Our first priority is to make API developers' life easier. When we design APIs and user interfaces such as a CLI or GUI, we want to make sure they are intuitive to and natural to their thought process. + Always keep in mind that LoopBack is built for developers by developers. Our + first priority is to make API developers' life easier. When we design APIs + and user interfaces such as a CLI or GUI, we want to make sure they are + intuitive to and natural to their thought process. ## Implementation stages -Here are the stages we are marching through toward the final version of LoopBack 4 as illustrated below. +Here are the stages we are marching through toward the final version of LoopBack +4 as illustrated below. 1. **Rebase and rewrite the core** - - Leverage TypeScript for better code quality and productivity. - * Provide optional type system for JavaScript. - * Provide planned features from future JavaScript editions to current JavaScript engines. + - Leverage TypeScript for better code quality and productivity. + + - Provide optional type system for JavaScript. + - Provide planned features from future JavaScript editions to current + JavaScript engines. - - Unify the asynchronous programming model/style. - * 100% promise-based APIs. - * Async/Await as first-class async programming style. + - Unify the asynchronous programming model/style. - - Implement an IoC Container for better visibility and extensibility - * Universal registry across different modules - * Dependency injection as a pattern to manage dependencies + - 100% promise-based APIs. + - Async/Await as first-class async programming style. - - Introduce Component as packaging model for extensions - * Component can be a npm module or a local directory - * Component encapsulates a list of extensions as a whole + - Implement an IoC Container for better visibility and extensibility + + - Universal registry across different modules + - Dependency injection as a pattern to manage dependencies + + - Introduce Component as packaging model for extensions + - Component can be a npm module or a local directory + - Component encapsulates a list of extensions as a whole 2. **Validate the core design by implementing an REST/HTTP invocation chain** - - Add top-down REST API creation which starts with OpenAPI specs. + - Add top-down REST API creation which starts with OpenAPI specs. + + - Build sequence of actions for inbound http processing - - Build sequence of actions for inbound http processing - - Introduce sequence as the composition of actions - - Implement the most critical actions to fulfill the REST API routing and invocation + - Introduce sequence as the composition of actions + - Implement the most critical actions to fulfill the REST API routing and + invocation - - Introduce controllers as entry points for API-related business logic. + - Introduce controllers as entry points for API-related business logic. - Models are the centerpieces of the current LoopBack applications. . They take multiple responsibilities: + Models are the centerpieces of the current LoopBack applications. . They + take multiple responsibilities: - - Data modeling - - Anchor for API related business logic - - Persistence or service invocation - - Mapping to REST HTTP/JSON endpoints + - Data modeling + - Anchor for API related business logic + - Persistence or service invocation + - Mapping to REST HTTP/JSON endpoints - - Authentication as a component + - Authentication as a component - Implement the core functionality of authentication as a component, which includes: - - Decorators to denote authentication requirement - - `authenticate` action to handle authentication - - Extension points for various authentication strategies + Implement the core functionality of authentication as a component, which + includes: + + - Decorators to denote authentication requirement + - `authenticate` action to handle authentication + - Extension points for various authentication strategies 3. **Rebuild our integration and composition capabilities** - - Introduce repositories to represent data access patterns such as CRUD or Key/Value stores - - Provide a reference implementation of CRUD and KV flavors of repository interfaces using the legacy juggler and connectors - - Refactor/rewrite the juggler into separate modules - - Typing system - - Model and relation definition - - Validation - - Query and mutation language - - DataSource - - Repository interfaces and implementations for data access - - Service interfaces and implementations for service invocations - - Define interfaces and metadata for connectors - - Rewrite connectors + - Introduce repositories to represent data access patterns such as CRUD or + Key/Value stores + - Provide a reference implementation of CRUD and KV flavors of repository + interfaces using the legacy juggler and connectors + - Refactor/rewrite the juggler into separate modules + - Typing system + - Model and relation definition + - Validation + - Query and mutation language + - DataSource + - Repository interfaces and implementations for data access + - Service interfaces and implementations for service invocations + - Define interfaces and metadata for connectors + - Rewrite connectors 4. **Declarative metadata and bootstrapping** - LoopBack manages a set of artifacts, such as models, relations, datasources, connectors, ACLs, controllers, repositories, actions, sequences, components, utility functions, and OpenAPI specs. In addition to the programmatic approach to describe these artifacts by code (apis and decorators), we would like to add declarative support so that they can be declared in JSON/YAML files. - - - Define a new domain-specific language (DSL) in JSON/YAML format and corresponding templates. - - Define the project layout to organize project artifacts. - - Leverage the IoC Context to manage metadata/instances of such artifacts following the extension point/extension pattern. - - Define the lifecycle and serialization/de-serialization requirements for each type of artifact. - - Add a boot component to discover/load/resolve/activate the artifacts. The boot process can be tailored for both tooling and runtime. + LoopBack manages a set of artifacts, such as models, relations, datasources, + connectors, ACLs, controllers, repositories, actions, sequences, components, + utility functions, and OpenAPI specs. In addition to the programmatic + approach to describe these artifacts by code (apis and decorators), we would + like to add declarative support so that they can be declared in JSON/YAML + files. + + - Define a new domain-specific language (DSL) in JSON/YAML format and + corresponding templates. + - Define the project layout to organize project artifacts. + - Leverage the IoC Context to manage metadata/instances of such artifacts + following the extension point/extension pattern. + - Define the lifecycle and serialization/de-serialization requirements for + each type of artifact. + - Add a boot component to discover/load/resolve/activate the artifacts. The + boot process can be tailored for both tooling and runtime. 5. **Tooling (CLI & UI)** - - Add CLI and UI tools to: - - Scaffold LoopBack 4 applications - - Manage artifacts such as sequences, actions, controllers, repositories, services, datasources and models + - Add CLI and UI tools to: + - Scaffold LoopBack 4 applications + - Manage artifacts such as sequences, actions, controllers, repositories, + services, datasources and models 6. **Enable cloud native experience** - - Allow controllers to be exposed as gRPC services - - Allow interaction with other gRPC services - - Integration with microservices deployment infrastructure such as Docker and Kubernetes - - Integration with service mesh + - Allow controllers to be exposed as gRPC services + - Allow interaction with other gRPC services + - Integration with microservices deployment infrastructure such as Docker + and Kubernetes + - Integration with service mesh The following diagram illustrates the high-level building blocks of LoopBack 4: ![loopback-stack](./imgs/loopback-stack.png) -Please note there is a common layer below the different functional areas in the stack. Let's examine the need to build a new core foundation for LoopBack 4. +Please note there is a common layer below the different functional areas in the +stack. Let's examine the need to build a new core foundation for LoopBack 4. ## A new core foundation ### The core responsibility -LoopBack itself is already modular. For example, a typical LoopBack 3.x application's dependency graph will have the following npm modules: +LoopBack itself is already modular. For example, a typical LoopBack 3.x +application's dependency graph will have the following npm modules: - loopback - strong-remoting - loopback-datasource-juggler -- loopback-connector-* +- loopback-connector-\* - loopback-component-explorer -LoopBack manages various artifacts across different modules. The following are a list of built-in types of artifacts that LoopBack 3.x supports out of box: +LoopBack manages various artifacts across different modules. The following are a +list of built-in types of artifacts that LoopBack 3.x supports out of box: - Model definitions/relations: describes data models and their relations - Validation: validates model instances and properties @@ -225,7 +361,8 @@ LoopBack manages various artifacts across different modules. The following are a - Components: wraps a module that be bootstrapped with LoopBack - Remoting: maps JavaScript methods to REST API operations - ACLs: controls access to protected resources -- Built-in models: provides set of prebuilt models such as User, AccessToken, and Role +- Built-in models: provides set of prebuilt models such as User, AccessToken, + and Role - Hooks/interceptors - Express middleware - remote hooks @@ -251,47 +388,79 @@ LoopBack manages various artifacts across different modules. The following are a - gRPC - GraphQL -Metadata for these artifacts form the knowledge base for LoopBack to glue all the pieces together and build capabilities to handle common API use cases. +Metadata for these artifacts form the knowledge base for LoopBack to glue all +the pieces together and build capabilities to handle common API use cases. -How to represent the metadata and their relations is the key responsibility of the LoopBack core foundation. It needs to provide a consistent way to contribute and consume such building blocks. +How to represent the metadata and their relations is the key responsibility of +the LoopBack core foundation. It needs to provide a consistent way to contribute +and consume such building blocks. ### Key ingredients for the core -The core foundation for LoopBack 4 is responsible for managing various artifacts independent of the nature of such artifacts. +The core foundation for LoopBack 4 is responsible for managing various artifacts +independent of the nature of such artifacts. -- A consistent registry to provide visibility and addressability for all artifacts. +- A consistent registry to provide visibility and addressability for all + artifacts. - - Visibility: Each artifact has a unique address and can be accessed via a URI or key. Artifacts can also be visible at different scopes. + - Visibility: Each artifact has a unique address and can be accessed via a URI + or key. Artifacts can also be visible at different scopes. - - Extensibility: LoopBack artifacts can be managed by types. New artifact types can be introduced. Instances for a given type can be added, removed, or replaced. Organizing artifacts in a hierarchy of extension points/extensions decouples providers and consumers. + - Extensibility: LoopBack artifacts can be managed by types. New artifact + types can be introduced. Instances for a given type can be added, removed, + or replaced. Organizing artifacts in a hierarchy of extension + points/extensions decouples providers and consumers. - Ability to compose with dependency resolution. - - Composability: It's common that one artifact to have dependencies on other artifacts. With dependency injection or service locator patterns, the core will greatly simplify how multiple artifacts work together. + - Composability: It's common that one artifact to have dependencies on other + artifacts. With dependency injection or service locator patterns, the core + will greatly simplify how multiple artifacts work together. - A packaging model for extensions. - - Pluggability: Extensions can be organized and contributed as a whole. We need to have a packaging model so that extension developers can create their own modules as bundles and plug into a LoopBack application. + - Pluggability: Extensions can be organized and contributed as a whole. We + need to have a packaging model so that extension developers can create their + own modules as bundles and plug into a LoopBack application. ### Why not Express? -Do we need to build our own core foundation? Can we continue to use Express? Our conclusion is no. Here are the gaps between what Express and LoopBack's needs. +Do we need to build our own core foundation? Can we continue to use Express? Our +conclusion is no. Here are the gaps between what Express and LoopBack's needs. - **Lack of extensibility**. - Express is a routing and middleware web framework with minimal functionality of its own: An Express application is essentially a series of middleware function calls. For details, see [Using middleware](http://expressjs.com/en/guide/using-middleware.html). + Express is a routing and middleware web framework with minimal functionality + of its own: An Express application is essentially a series of middleware + function calls. For details, see + [Using middleware](http://expressjs.com/en/guide/using-middleware.html). - Express is only extensibile via middleware. It neither exposes a registry nor provides APIs to manage artifacts such as middleware or routers. For its purpose, Express only deals with middleware-like extensions that intercept http requests/responses. LoopBack needs much more extension points and extensions. + Express is only extensibile via middleware. It neither exposes a registry nor + provides APIs to manage artifacts such as middleware or routers. For its + purpose, Express only deals with middleware-like extensions that intercept + http requests/responses. LoopBack needs much more extension points and + extensions. - **Lack of composability**. - Express is not composable. For example, `app.use()` is the only way to register a middleware. The order of middleware is determined by the order of `app.use`. This simplistic approach works for a single monolithic application where all middleware are known and arranged ahead of time. But it does not support the case when middleware from other components need to be added between existing ones. LoopBack had to introduce a phase-based extension and hack Express to provide this capability. + Express is not composable. For example, `app.use()` is the only way to + register a middleware. The order of middleware is determined by the order of + `app.use`. This simplistic approach works for a single monolithic application + where all middleware are known and arranged ahead of time. But it does not + support the case when middleware from other components need to be added + between existing ones. LoopBack had to introduce a phase-based extension and + hack Express to provide this capability. - Express doesn't provide any way to manage dependencies between artifact instances either. + Express doesn't provide any way to manage dependencies between artifact + instances either. - **Lack of declarative support**. - In Express, everything is done by JavaScript code as it works exactly as the web site claims: `Fast, unopinionated, minimalist web framework for Node.js`. In contrast, LoopBack is designed to facilitate API creation and composition by conventions and patterns as best practices. More types of constructs are introduced. + In Express, everything is done by JavaScript code as it works exactly as the + web site claims: `Fast, unopinionated, minimalist web framework for Node.js`. + In contrast, LoopBack is designed to facilitate API creation and composition + by conventions and patterns as best practices. More types of constructs are + introduced. ## Deep dive into LoopBack 4 extensibility @@ -306,12 +475,16 @@ Please check out [Extending LoopBack 4](Extending-LoopBack-4.md). ## Rebuilding LoopBack experience on top of the new core -With the extensible foundation in place, we start to rebuild the LoopBack REST API experience by "eating your own dog food" with the following artifacts: +With the extensible foundation in place, we start to rebuild the LoopBack REST +API experience by "eating your own dog food" with the following artifacts: -- [Sequence and actions](Sequence.md): A sequence of actions to handle HTTP requests/responses. -- [Controllers](Controllers.md): A class with methods to implement API operations behind REST endpoints. +- [Sequence and actions](Sequence.md): A sequence of actions to handle HTTP + requests/responses. +- [Controllers](Controllers.md): A class with methods to implement API + operations behind REST endpoints. - [Model](Model.md): Definition of data models. -- [Repositories](Repositories.md): Interfaces of access patterns for data sources. +- [Repositories](Repositories.md): Interfaces of access patterns for data + sources. The features are provided by the following modules: @@ -320,7 +493,8 @@ The features are provided by the following modules: ## Example for application developers -Before we go further, let's try to build a 'hello world' application with LoopBack 4. +Before we go further, let's try to build a 'hello world' application with +LoopBack 4. ### Basic Hello-World @@ -339,8 +513,8 @@ Before we go further, let's try to build a 'hello world' application with LoopBa ## References -- https://strongloop.com/strongblog/announcing-loopback-next/ -- https://www.infoq.com/articles/driving-architectural-simplicity -- https://strongloop.com/strongblog/creating-a-multi-tenant-connector-microservice-using-loopback/ -- https://strongloop.com/strongblog/loopback-as-an-event-publisher/ -- https://strongloop.com/strongblog/loopback-as-a-service-using-openwhisk/ +- +- +- +- +- diff --git a/docs/site/Creating-components.md b/docs/site/Creating-components.md index bae81c7ec062..149564c8b907 100644 --- a/docs/site/Creating-components.md +++ b/docs/site/Creating-components.md @@ -19,17 +19,20 @@ export class MyComponent implements Component { constructor() { this.controllers = [MyController]; this.providers = { - 'my.value': MyValueProvider + 'my.value': MyValueProvider, }; } } ``` -When a component is mounted to an application, a new instance of the component class is created and then: - - Each Controller class is registered via `app.controller()`, - - Each Provider is bound to its key in `providers` object. +When a component is mounted to an application, a new instance of the component +class is created and then: -The example `MyComponent` above will add `MyController` to application's API and create a new binding `my.value` that will be resolved using `MyValueProvider`. +- Each Controller class is registered via `app.controller()`, +- Each Provider is bound to its key in `providers` object. + +The example `MyComponent` above will add `MyController` to application's API and +create a new binding `my.value` that will be resolved using `MyValueProvider`. ## Providers @@ -47,7 +50,8 @@ export class MyValueProvider implements Provider { ### Specifying binding key -Notice that the provider class itself does not specify any binding key, the key is assigned by the component class. +Notice that the provider class itself does not specify any binding key, the key +is assigned by the component class. ```ts import {MyValueProvider} from './providers/my-value-provider'; @@ -55,7 +59,7 @@ import {MyValueProvider} from './providers/my-value-provider'; export class MyComponent implements Component { constructor() { this.providers = { - 'my-component.my-value': MyValueProvider + 'my-component.my-value': MyValueProvider, }; } } @@ -63,8 +67,9 @@ export class MyComponent implements Component { ### Accessing values from Providers -Applications can use `@inject` decorators to access the value of an exported Provider. -If you’re not familiar with decorators in TypeScript, see [Key Concepts: Decorators](Decorators.md) +Applications can use `@inject` decorators to access the value of an exported +Provider. If you’re not familiar with decorators in TypeScript, see +[Key Concepts: Decorators](Decorators.md) ```ts const app = new Application(); @@ -80,8 +85,10 @@ class MyController { } ``` -{% include note.html title="A note on binding names" content="To avoid name conflicts, add a unique prefix to your binding key (for example, `my-component.` in the example above). See [Reserved binding keys](Reserved-binding-keys.md) for the list of keys reserved for the framework use. -" %} +{% include note.html title="A note on binding names" content="To avoid name +conflicts, add a unique prefix to your binding key (for example, `my-component.` +in the example above). See [Reserved binding keys](Reserved-binding-keys.md) for +the list of keys reserved for the framework use. " %} ### Asynchronous providers @@ -91,21 +98,25 @@ Provider's `value()` method can be asynchronous too: import {Provider} from '@loopback/context'; const request = require('request-promise-native'); const weatherUrl = - 'http://samples.openweathermap.org/data/2.5/weather?appid=b1b15e88fa797225412429c1c50c122a1' + 'http://samples.openweathermap.org/data/2.5/weather?appid=b1b15e88fa797225412429c1c50c122a1'; export class CurrentTemperatureProvider implements Provider { async value() { - const data = await request(`${weatherUrl}&q=Prague,CZ`, {json:true}); + const data = await request(`${weatherUrl}&q=Prague,CZ`, {json: true}); return data.main.temp; } } ``` -In this case, LoopBack will wait until the promise returned by `value()` is resolved, and use the resolved value for dependency injection. +In this case, LoopBack will wait until the promise returned by `value()` is +resolved, and use the resolved value for dependency injection. ### Working with HTTP request/response -In some cases, the Provider may depend on other parts of LoopBack; for example the current `request` object. The Provider's constructor should list such dependencies annotated with `@inject` keyword, so that LoopBack runtime can resolve them automatically. +In some cases, the Provider may depend on other parts of LoopBack; for example +the current `request` object. The Provider's constructor should list such +dependencies annotated with `@inject` keyword, so that LoopBack runtime can +resolve them automatically. ```ts import {Provider} from '@loopback/context'; @@ -113,8 +124,10 @@ import {RestBindings} from '@loopback/rest'; import {ServerRequest} from 'http'; const uuid = require('uuid/v4'); -class CorrelationIdProvider implements Provider{ - constructor(@inject(RestBindings.Http.REQUEST) private request: ServerRequest) {} +class CorrelationIdProvider implements Provider { + constructor( + @inject(RestBindings.Http.REQUEST) private request: ServerRequest, + ) {} value() { return this.request.headers['X-Correlation-Id'] || uuid(); @@ -124,80 +137,95 @@ class CorrelationIdProvider implements Provider{ ## Modifying request handling logic -A frequent use case for components is to modify the way requests are handled. For example, the authentication component needs to verify user credentials before the actual handler can be invoked; or a logger component needs to record start time and write a log entry when the request has been handled. +A frequent use case for components is to modify the way requests are handled. +For example, the authentication component needs to verify user credentials +before the actual handler can be invoked; or a logger component needs to record +start time and write a log entry when the request has been handled. The idiomatic solution has two parts: - 1. The component should define and bind a new [Sequence action](Sequence.md#actions), for example `authentication.actions.authenticate`: +1. The component should define and bind a new [Sequence action](Sequence.md#actions), for example `authentication.actions.authenticate`: - ```ts - import {Component} from '@loopback/core'; + ```ts + import {Component} from '@loopback/core'; - class AuthenticationComponent implements Component{ - constructor() { - this.providers = { - 'authentication.actions.authenticate': AuthenticateActionProvider - }; - } - } - ``` + class AuthenticationComponent implements Component { + constructor() { + this.providers = { + 'authentication.actions.authenticate': AuthenticateActionProvider, + }; + } + } + ``` - A sequence action is typically implemented as an `action()` method in the provider. + A sequence action is typically implemented as an `action()` method in the provider. - ```ts - class AuthenticateActionProvider implements Provider{ - // Provider interface - value() { - return request => this.action(request); - } + ```ts + class AuthenticateActionProvider implements Provider { + // Provider interface + value() { + return request => this.action(request); + } - // The sequence action - action(request): UserProfile | undefined { - // authenticate the user - } - } - ``` - - It may be tempting to put action implementation directly inside the anonymous arrow function returned by provider's `value()` method. We consider that as a bad practice though, because when an error occurs, the stack trace will contain only an anonymous function that makes it more difficult to link the entry with the sequence action. - - - 2. The application should use a custom `Sequence` class which calls this new sequence action in an appropriate place. - - ```ts - class AppSequence implements SequenceHandler { - constructor( - @inject(RestBindings.Http.CONTEXT) protected ctx: Context, - @inject(RestBindings.SequenceActions.FIND_ROUTE) protected findRoute: FindRoute, - @inject(RestBindings.SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams, - @inject(RestBindings.SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, - @inject(RestBindings.SequenceActions.SEND) public send: Send, - @inject(RestBindings.SequenceActions.REJECT) public reject: Reject, - // Inject the new action here: - @inject('authentication.actions.authenticate') protected authenticate: AuthenticateFn - ) {} - - async handle(req: ParsedRequest, res: ServerResponse) { - try { - const route = this.findRoute(req); - - // Invoke the new action: - const user = await this.authenticate(req); - - const args = await parseOperationArgs(req, route); - const result = await this.invoke(route, args); - this.send(res, result); - } catch (err) { - this.reject(res, req, err); - } - } - } - ``` + // The sequence action + action(request): UserProfile | undefined { + // authenticate the user + } + } + ``` + + It may be tempting to put action implementation directly inside the anonymous arrow function returned by provider's `value()` method. We consider that as a bad practice though, because when an error occurs, the stack trace will contain only an anonymous function that makes it more difficult to link the entry with the sequence action. + +2) The application should use a custom `Sequence` class which calls this new sequence action in an appropriate place. + +```` +```ts +class AppSequence implements SequenceHandler { + constructor( + @inject(RestBindings.Http.CONTEXT) protected ctx: Context, + @inject(RestBindings.SequenceActions.FIND_ROUTE) protected findRoute: FindRoute, + @inject(RestBindings.SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams, + @inject(RestBindings.SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, + @inject(RestBindings.SequenceActions.SEND) public send: Send, + @inject(RestBindings.SequenceActions.REJECT) public reject: Reject, + // Inject the new action here: + @inject('authentication.actions.authenticate') protected authenticate: AuthenticateFn + ) {} +``` +```` + +async handle(req: ParsedRequest, res: ServerResponse) { +try { +const route = this.findRoute(req); + +``` + // Invoke the new action: + const user = await this.authenticate(req); + + const args = await parseOperationArgs(req, route); + const result = await this.invoke(route, args); + this.send(res, result); +} catch (err) { + this.reject(res, req, err); +} +``` + +} +} +```` ### Accessing Elements contributed by other Sequence Actions -When writing a custom sequence action, you need to access Elements contributed by other actions run in the sequence. For example, `authenticate()` action needs information about the invoked route to decide whether and how to authenticate the request. +When writing a custom sequence action, you need to access Elements contributed +by other actions run in the sequence. For example, `authenticate()` action needs +information about the invoked route to decide whether and how to authenticate +the request. -Because all Actions are resolved before the Sequence `handle` function is run, Elements contributed by Actions are not available for injection yet. To solve this problem, use `@inject.getter` decorator to obtain a getter function instead of the actual value. This allows you to defer resolution of your dependency only until the sequence action contributing this value has already finished. +Because all Actions are resolved before the Sequence `handle` function is run, +Elements contributed by Actions are not available for injection yet. To solve +this problem, use `@inject.getter` decorator to obtain a getter function instead +of the actual value. This allows you to defer resolution of your dependency only +until the sequence action contributing this value has already finished. ```ts export class AuthenticationProvider implements Provider { @@ -215,17 +243,17 @@ export class AuthenticationProvider implements Provider { // ... } } -``` +```` ### Contributing Elements from Sequence Actions -Use `@inject.setter` decorator to obtain a setter function that can be used to contribute new Elements to the request context. +Use `@inject.setter` decorator to obtain a setter function that can be used to +contribute new Elements to the request context. ```ts export class AuthenticationProvider implements Provider { constructor( - @inject.getter(BindingKeys.Authentication.STRATEGY) - readonly getStrategy, + @inject.getter(BindingKeys.Authentication.STRATEGY) readonly getStrategy, @inject.setter(BindingKeys.Authentication.CURRENT_USER) readonly setCurrentUser, ) {} @@ -236,8 +264,7 @@ export class AuthenticationProvider implements Provider { async action(request): UserProfile | undefined { const strategy = await this.getStrategy(); - const user = // ... authenticate - this.setCurrentUser(user); + const user = this.setCurrentUser(user); // ... authenticate return user; } } @@ -245,8 +272,8 @@ export class AuthenticationProvider implements Provider { ## Extending Application with Mixins -When binding a component to an app, you may want to extend the app with the component's -properties and methods by using mixins. +When binding a component to an app, you may want to extend the app with the +component's properties and methods by using mixins. An example of how a mixin leverages a component is `RepositoryMixin`. Suppose an app has multiple components with repositories bound to each of them. @@ -256,6 +283,7 @@ The following snippet is an abbreviated function [`RepositoryMixin`](https://github.com/strongloop/loopback-next/blob/master/packages/repository/src/repository-mixin.ts): {% include code-caption.html content="mixins/src/repository-mixin.ts" %} + ```ts export function RepositoryMixin>(superClass: T) { return class extends superClass { @@ -296,7 +324,7 @@ import {RepositoryMixin} from 'mixins/src/repository-mixin'; import {Application} from '@loopback/core'; import {FooComponent} from 'components/src/Foo'; -class AppWithRepoMixin extends RepositoryMixin(Application) {}; +class AppWithRepoMixin extends RepositoryMixin(Application) {} let app = new AppWithRepoMixin(); app.component(FooComponent); @@ -306,17 +334,22 @@ app.find('repositories.*'); ## Configuring components -More often than not, the component may want to offer different value providers depending on the configuration. For example, a component providing an email API may offer different transports (stub, SMTP, and so on). +More often than not, the component may want to offer different value providers +depending on the configuration. For example, a component providing an email API +may offer different transports (stub, SMTP, and so on). -Components should use constructor-level [Dependency Injection](Context.md#dependency-injection) to receive the configuration from the application. +Components should use constructor-level +[Dependency Injection](Context.md#dependency-injection) to receive the +configuration from the application. ```ts class EmailComponent { constructor(@inject('config#components.email') config) { this.providers = { - 'sendEmail': this.config.transport == 'stub' ? - StubTransportProvider : - SmtpTransportProvider, + sendEmail: + this.config.transport == 'stub' + ? StubTransportProvider + : SmtpTransportProvider, }; } } diff --git a/docs/site/Creating-servers.md b/docs/site/Creating-servers.md index 5e9b01a73e67..75eecd21693a 100644 --- a/docs/site/Creating-servers.md +++ b/docs/site/Creating-servers.md @@ -19,6 +19,7 @@ ports (this is why they're called "servers", after all). This leads into a key concept to leverage for creating your custom servers. ### Controllers and routing + LoopBack 4 developers are strongly encouraged to use controllers for their modules, and this naturally leads to the concept of routing. @@ -33,12 +34,13 @@ The toy protocol will require a JSON payload with three properties: `controller` `method`, and `input`. An example request would look something like this: + ```json { "controller": "GreetController", "method": "basicHello", "input": { - "name": "world", + "name": "world" } } ``` @@ -47,13 +49,16 @@ You can find the code for our sample RPC server implementation [over here](https://github.com/strongloop/loopback4-example-rpc-server). ### Trying it out + First, install your dependencies and then start the application: + ``` npm i && npm start ``` Now, try it out: start the server and run a few REST requests. Feel free to use whatever REST client you'd prefer (this example will use `curl`). + ```sh # Basic Greeting Calls $ curl -X POST -d '{ "controller": "GreetController", "method": "basicHello" }' -H "Content-Type: application/json" http://localhost:3000/ @@ -72,9 +77,11 @@ implementation of both its router and server, the general concept remains the same, and you can use these tools to make whatever server you'd like. ### Other considerations + Some additional concepts to add to your server could include: + - Pre-processing of requests (changing content types, checking the request body, -etc) + etc) - Post-processing of responses (removing sensitive/useless information) - Caching - Logging diff --git a/docs/site/DEVELOPING.md b/docs/site/DEVELOPING.md index dc997d0d1c80..a9b7e4b7a936 100644 --- a/docs/site/DEVELOPING.md +++ b/docs/site/DEVELOPING.md @@ -1,56 +1,68 @@ # Developing LoopBack -This document describes how to develop modules living in loopback-next monorepo. See [Monorepo overview](./MONOREPO.md) for a list of all packages. - - - [Setting up development environment](#setting-up-development-environment) - - [Building the project](#building-the-project) - - [Running tests](#running-tests) - - [Coding rules](#coding-rules) - - [API documentation](#api-documentation) - - [Commit message guidelines](#commit-message-guidelines) - - [Releasing new versions](#releasing-new-versions) - - [Adding a new package](#adding-a-new-package) - - [How to test infrastructure changes](#how-to-test-infrastructure-changes) +This document describes how to develop modules living in loopback-next monorepo. +See [Monorepo overview](./MONOREPO.md) for a list of all packages. + +- [Setting up development environment](#setting-up-development-environment) +- [Building the project](#building-the-project) +- [Running tests](#running-tests) +- [Coding rules](#coding-rules) +- [API documentation](#api-documentation) +- [Commit message guidelines](#commit-message-guidelines) +- [Releasing new versions](#releasing-new-versions) +- [Adding a new package](#adding-a-new-package) +- [How to test infrastructure changes](#how-to-test-infrastructure-changes) ## Setting up development environment Before you can start developing LoopBack, you need to install and configure few dependencies. - - [git](https://git-scm.com/): Github's [Set Up Git](https://help.github.com/articles/set-up-git/) guide is a good source of information. - - [Node.js 8.x (LTS)](https://nodejs.org/en/download/) +- [git](https://git-scm.com/): Github's + [Set Up Git](https://help.github.com/articles/set-up-git/) guide is a good + source of information. +- [Node.js 8.x (LTS)](https://nodejs.org/en/download/) You may want to configure your IDE or editor to get better support for TypeScript too. - - [VisualStudio Code](./VSCODE.md) - - _Missing your favorite IDE/editor here? We would love to have documentation for more IDEs/editors! Please send a pull request to add recommended setup for your tool._ +- [VisualStudio Code](./VSCODE.md) +- _Missing your favorite IDE/editor here? We would love to have documentation + for more IDEs/editors! Please send a pull request to add recommended setup for + your tool._ -Before getting started, it is recommended to configure `git` so that it knows who you are: +Before getting started, it is recommended to configure `git` so that it knows +who you are: ```sh -$ git config --global user.name "J. Random User" -$ git config --global user.email "j.random.user@example.com" +git config --global user.name "J. Random User" +git config --global user.email "j.random.user@example.com" ``` -Please make sure this local email is also added to your [GitHub email list](https://github.com/settings/emails) so that your commits will be properly associated with your account and you will be promoted to Contributor once your first commit is landed. +Please make sure this local email is also added to your +[GitHub email list](https://github.com/settings/emails) so that your commits +will be properly associated with your account and you will be promoted to +Contributor once your first commit is landed. ## Building the project -Whenever you pull updates from GitHub or switch between feature branches, make sure to updated installed dependencies in all monorepo packages. The following command will install npm dependencies for all packages and create symbolic links for intra-dependencies: +Whenever you pull updates from GitHub or switch between feature branches, make +sure to updated installed dependencies in all monorepo packages. The following +command will install npm dependencies for all packages and create symbolic links +for intra-dependencies: ```sh -$ npm run bootstrap +npm run bootstrap ``` The next step is to compile all packages from TypeScript to JavaScript: ```sh -$ npm run build +npm run build ``` -Please note that we are automatically running the build from `pretest` -script, therefore you should not need to run this command as part of your +Please note that we are automatically running the build from `pretest` script, +therefore you should not need to run this command as part of your [red-green-refactor cycle](http://www.jamesshore.com/Blog/Red-Green-Refactor.html). ## Running tests @@ -58,62 +70,84 @@ script, therefore you should not need to run this command as part of your This is the only command you should need while developing LoopBack: ```sh -$ npm test +npm test ``` It does all you need: - - Compile TypeScript - - Run all tests - - Check code formatting using [Prettier](https://prettier.io/) - - Lint the code using [TSLint](https://palantir.github.io/tslint/) +- Compile TypeScript +- Run all tests +- Check code formatting using [Prettier](https://prettier.io/) +- Lint the code using [TSLint](https://palantir.github.io/tslint/) ## Coding rules - All features and bug fixes must be covered by one or more automated tests. -- All public methods must be documented with typedoc comments (see [API Documentation](#api-documentation) below). +- All public methods must be documented with typedoc comments (see + [API Documentation](#api-documentation) below). -- Follow our style guide as documented on loopback.io: [Code style guide](http://loopback.io/doc/en/contrib/style-guide.html). +- Follow our style guide as documented on loopback.io: + [Code style guide](http://loopback.io/doc/en/contrib/style-guide.html). ### Linting and formatting We use two tools to keep our codebase healthy: - - [TSLint](https://palantir.github.io/tslint/) to statically analyse our source code and detect common problems. - - [Prettier](https://prettier.io/) to keep our code always formatted the same way, avoid style discussions in code reviews, and save everybody's time an energy. +- [TSLint](https://palantir.github.io/tslint/) to statically analyse our source + code and detect common problems. +- [Prettier](https://prettier.io/) to keep our code always formatted the same + way, avoid style discussions in code reviews, and save everybody's time an + energy. -You can run both linters via the following npm script, just keep in mind that `npm test` is already running them for you. +You can run both linters via the following npm script, just keep in mind that +`npm test` is already running them for you. ```sh -$ npm run lint +npm run lint ``` -Many problems (especially formatting) can be automatically fixed by running the npm script `lint:fix`. +Many problems (especially formatting) can be automatically fixed by running the +npm script `lint:fix`. ```sh -$ npm run lint:fix +npm run lint:fix ``` ## API Documentation -We use [strong-docs](https://github.com/strongloop/strong-docs) to generate API documentation for all our packages. This documentation is generated when publishing new releases to npmjs.org and it's picked up by http://apidocs.loopback.io/. +We use [strong-docs](https://github.com/strongloop/strong-docs) to generate API +documentation for all our packages. This documentation is generated when +publishing new releases to npmjs.org and it's picked up by +. -You can preview API docs locally by opening the file `docs/apidocs.html` in your browser. +You can preview API docs locally by opening the file `docs/apidocs.html` in your +browser. ## Commit message guidelines -_Note: we have recently changed our commit message conventions. Most of other LoopBack repositories (e.g. [strongloop/loopback.io](https://github.com/strongloop/loopback.io)) use the older convention as described on [loopback.io](https://loopback.io/doc/en/contrib/git-commit-messages.html)._ +_Note: we have recently changed our commit message conventions. Most of other +LoopBack repositories (e.g. +[strongloop/loopback.io](https://github.com/strongloop/loopback.io)) use the +older convention as described on +[loopback.io](https://loopback.io/doc/en/contrib/git-commit-messages.html)._ A good commit message should describe what changed and why. -Our commit messages are formatted according to [Conventional Commits](https://conventionalcommits.org/), we use [commitlint](https://github.com/marionebl/commitlint) to verify and enforce this convention. These rules lead to more readable messages that are easy to follow when looking through the project history. But also, we use the git commit messages to generate change logs when publishing new versions. +Our commit messages are formatted according to +[Conventional Commits](https://conventionalcommits.org/), we use +[commitlint](https://github.com/marionebl/commitlint) to verify and enforce this +convention. These rules lead to more readable messages that are easy to follow +when looking through the project history. But also, we use the git commit +messages to generate change logs when publishing new versions. ### Commit Message Format -Each commit message consists of a **header**, a **body** and a **footer**. The header has a special format that includes a **type**, an optional **scope** and a **subject**: +Each commit message consists of a **header**, a **body** and a **footer**. The +header has a special format that includes a **type**, an optional **scope** and +a **subject**: -``` +```text (): @@ -125,65 +159,83 @@ Each commit message consists of a **header**, a **body** and a **footer**. The h The **type** must be one of the following: - - **feat**: A new feature - - **fix**: A bug fix - - **docs**: Documentation only changes - - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) - - **refactor**: A code change that neither fixes a bug nor adds a feature - - **perf**: A code change that improves performance - - **test**: Adding missing or correcting existing tests - - **build**: Changes that affect the build system or external dependencies - - **ci**: Changes to our CI configuration files and scripts - - **chore**: Changes to the auxiliary tools and libraries such as documentation generation - - **revert**: Reverts a previous commit +- **feat**: A new feature +- **fix**: A bug fix +- **docs**: Documentation only changes +- **style**: Changes that do not affect the meaning of the code (white-space, + formatting, missing semi-colons, etc) +- **refactor**: A code change that neither fixes a bug nor adds a feature +- **perf**: A code change that improves performance +- **test**: Adding missing or correcting existing tests +- **build**: Changes that affect the build system or external dependencies +- **ci**: Changes to our CI configuration files and scripts +- **chore**: Changes to the auxiliary tools and libraries such as documentation + generation +- **revert**: Reverts a previous commit #### scope -The **scope** must be a list of one or more packages contained in this monorepo. Each scope name must match a directory name in [packages/](../packages), e.g. `core` or `context`. +The **scope** must be a list of one or more packages contained in this monorepo. +Each scope name must match a directory name in [packages/](../packages), e.g. +`core` or `context`. -_Note: If multiple packages are affected by a pull request, don't list the scopes as the commit linter currently only supports only one scope being listed at most. The `CHANGELOG` for each affected package will still show the commit. Commit linter will be updated to allow listing of multiple affected scopes, see [issue #581](https://github.com/strongloop/loopback-next/issues/581)_ +_Note: If multiple packages are affected by a pull request, don't list the +scopes as the commit linter currently only supports only one scope being listed +at most. The `CHANGELOG` for each affected package will still show the commit. +Commit linter will be updated to allow listing of multiple affected scopes, see +[issue #581](https://github.com/strongloop/loopback-next/issues/581)_ #### subject The **subject** contains succinct description of the change: - - use the imperative, present tense: "change" not "changed" nor "changes" - - don't capitalize first letter - - no dot (.) at the end +- use the imperative, present tense: "change" not "changed" nor "changes" +- don't capitalize first letter +- no dot (.) at the end #### body -The **body** provides more details, it should include the motivation for the change and contrast this with previous behavior. +The **body** provides more details, it should include the motivation for the +change and contrast this with previous behavior. -Just as in the subject, use the imperative, present tense: "change" not "changed" nor "changes"a +Just as in the subject, use the imperative, present tense: "change" not +"changed" nor "changes"a -Paragraphs or bullet points are ok (must not exceed 100 characters per line). Typically a hyphen or asterisk is used for the bullet, followed by a single space, with blank lines in between. +Paragraphs or bullet points are ok (must not exceed 100 characters per line). +Typically a hyphen or asterisk is used for the bullet, followed by a single +space, with blank lines in between. #### footer (optional) -The **footer** should contain any information about Breaking Changes introduced by this commit. +The **footer** should contain any information about Breaking Changes introduced +by this commit. -This section must start with the upper case text `BREAKING CHANGE` followed by a colon (`:`) and a space (` `). A description must be provided, describing what has changed and how to migrate from older versions. +This section must start with the upper case text `BREAKING CHANGE` followed by a +colon (`:`) and a space (``). A description must be provided, describing what +has changed and how to migrate from older versions. ### Tools to help generate a commit message -This repository has [commitizen](https://github.com/commitizen/cz-cli) support enabled. Commitizen can help you generate your commit messages automatically. You must install it globally as follows: +This repository has [commitizen](https://github.com/commitizen/cz-cli) support +enabled. Commitizen can help you generate your commit messages automatically. +You must install it globally as follows: ```sh -$ npm i -g commitizen +npm i -g commitizen ``` -And to use it, simply call `git cz` instead of `git commit`. The tool will help you generate a commit message that follows the above guidelines. +And to use it, simply call `git cz` instead of `git commit`. The tool will help +you generate a commit message that follows the above guidelines. ## Releasing new versions When we are ready to tag and publish a release, run the following commands: ```sh -$ cd loopback-next -$ git checkout master -$ git pull -$ npm run release +cd loopback-next +git checkout master +git pull +npm run release ``` The `release` script will automatically perform the tasks for all packages: @@ -194,21 +246,27 @@ The `release` script will automatically perform the tasks for all packages: - Run mocha tests - Check lint (tslint and prettier) issues -If all steps are successful, it prompts you to publish packages into npm repository. +If all steps are successful, it prompts you to publish packages into npm +repository. ## Adding a new package ### Create a new package -To add a new package, create a folder in [`packages`](packages) as the root directory of your module. For example, +To add a new package, create a folder in [`packages`](packages) as the root +directory of your module. For example, + ```sh -$ cd loopback-next/packages -$ mkdir +cd loopback-next/packages +mkdir ``` -The package follows the node/npm module layout. You can use `npm init` or `lb4 extension` command to scaffold the module, copy/paste from an existing package, or manually add files including `package.json`. +The package follows the node/npm module layout. You can use `npm init` or +`lb4 extension` command to scaffold the module, copy/paste from an existing +package, or manually add files including `package.json`. -Make sure you add LICENSE file properly and all source code files have the correct copyright header. +Make sure you add LICENSE file properly and all source code files have the +correct copyright header. ### Keep shared configuration in root @@ -218,13 +276,16 @@ We have some configuration files at the top level (**loopback-next/**): - `.prettierignore` - `.nycrc.yml` -For consistency across all packages, do not add them at package level unless specific customization is needed. +For consistency across all packages, do not add them at package level unless +specific customization is needed. ### Make a scoped package public -By default, npm publishes scoped packages with private access. There are two options to make a new scoped package with public access. +By default, npm publishes scoped packages with private access. There are two +options to make a new scoped package with public access. Either add the following section to `package.json`: + ```json "publishConfig": { "access": "public" @@ -234,18 +295,24 @@ Either add the following section to `package.json`: Or explicitly publish the package with `--access=public`: ```sh -$ cd packages/ -$ npm publish --access=public +cd packages/ +npm publish --access=public ``` ### Register the new package Please register the new package in the following files: - - Update [MONOREPO.md](./MONOREPO.md) - insert a new table row to describe the new package, please keep the rows sorted by package name. - - Update [docs/apidocs.html](../apidocs.html) - add a link to API docs for this new package. - - Update [CODEOWNERS](../../CODEOWNERS) - add a new entry listing the primary maintainers (owners) of the new package - - Ask somebody from the IBM team (e.g. [@bajtos](https://github.com/bajtos), [@raymondfeng](https://github.com/raymondfeng) or [@kjdelisle](https://github.com/kjdelisle)) to enlist the new package on http://apidocs.loopback.io/ +- Update [MONOREPO.md](./MONOREPO.md) - insert a new table row to describe the + new package, please keep the rows sorted by package name. +- Update [docs/apidocs.html](../apidocs.html) - add a link to API docs for this + new package. +- Update [CODEOWNERS](../../CODEOWNERS) - add a new entry listing the primary + maintainers (owners) of the new package +- Ask somebody from the IBM team (e.g. [@bajtos](https://github.com/bajtos), + [@raymondfeng](https://github.com/raymondfeng) or + [@kjdelisle](https://github.com/kjdelisle)) to enlist the new package on + ## How to test infrastructure changes @@ -258,40 +325,43 @@ configuration, it's important to verify that all usage scenarios keep working. 2. Add a small bit of code to break TypeScript's type checks, for example: - ```ts - const foo: number = 'bar'; - ``` + ```ts + const foo: number = 'bar'; + ``` 3. Run `npm test` 4. Verify that the build failed and the compiler error message shows a path relative to monorepo root, e.g. `packages/src/index.ts`. - _(This is does not work now, `tsc` is reporting paths relative to individual package directories. See https://github.com/strongloop/loopback-next/issues/1010)_ + _(This is does not work now, `tsc` is reporting paths relative to individual + package directories. See + )_ 5. Test integration with supported IDEs: - - [VS Code](./VSCode.md#how-to-verify-typescript-setup) + - [VS Code](./VSCode.md#how-to-verify-typescript-setup) ### Verify TSLint setup 1. Open any existing TypeScript file, e.g. `packages/src/index.ts` -2. Introduce two kinds linting problems - one that does and another that does not require type information to be detected. For example, you can add the following line at the end of the opened `index.ts`: +2. Introduce two kinds linting problems - one that does and another that does + not require type information to be detected. For example, you can add the + following line at the end of the opened `index.ts`: - ```ts - const foo: any = 'bar'; - ``` + ```ts + const foo: any = 'bar'; + ``` 3. Run `npm test` 4. Verify that the build failed and both linting problems are reported: - ```text - ERROR: /Users/(...)/packages/core/src/index.ts[16, 7]: 'foo' is declared but its value is never read. - ERROR: /Users/(...)/packages/core/src/index.ts[16, 12]: Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type. - ``` + ```text + ERROR: /Users/(...)/packages/core/src/index.ts[16, 7]: 'foo' is declared but its value is never read. + ERROR: /Users/(...)/packages/core/src/index.ts[16, 12]: Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type. + ``` 5. Test integration with supported IDEs: - - [VS Code](./VSCode.md#how-to-verify-tslint-setup) - + - [VS Code](./VSCode.md#how-to-verify-tslint-setup) diff --git a/docs/site/Decorators.md b/docs/site/Decorators.md index 7dc518da5cf3..f84e5b0c97e8 100644 --- a/docs/site/Decorators.md +++ b/docs/site/Decorators.md @@ -8,16 +8,19 @@ permalink: /doc/en/lb4/Decorators.html summary: --- -A decorator allows you to annotate or modify your class declarations and members with metadata. +A decorator allows you to annotate or modify your class declarations and members +with metadata. ## Introduction -*If you're new to Decorators in TypeScript, see [here](https://www.typescriptlang.org/docs/handbook/decorators.html) for more info.* +_If you're new to Decorators in TypeScript, see +[here](https://www.typescriptlang.org/docs/handbook/decorators.html) for more +info._ Decorators give LoopBack the flexibility to modify your plain TypeScript classes and properties in a way that allows the framework to better understand how to -make use of them, without the need to inherit base classes or add functions -that tie into an API. +make use of them, without the need to inherit base classes or add functions that +tie into an API. As a default, LoopBack comes with some pre-defined decorators: @@ -29,8 +32,9 @@ As a default, LoopBack comes with some pre-defined decorators: ## Route Decorators Route decorators are used to expose controller methods as REST API operations. -If you are not familiar with the concept Route or Controller, please see [LoopBack Route](routes.md) -and [LoopBack Controller](controllers.md) to learn more about them. +If you are not familiar with the concept Route or Controller, please see +[LoopBack Route](routes.md) and [LoopBack Controller](controllers.md) to learn +more about them. By calling a route decorator, you provide OpenAPI specification to describe the endpoint which the decorated method maps to. You can choose different decorators @@ -38,164 +42,171 @@ accordingly or do a composition of them: ### API Decorator - Syntax: [`@api(spec: ControllerSpec)`](http://apidocs.loopback.io/@loopback%2fcore/#783) - - `@api` is a decorator for controller constructor, it's called before a controller - class. `@api` is used when you have a base path and a Paths Object, which - contains all path definitions of your controller. Please note the api specs defined - with `@api` will override other api specs defined inside the controller. For example: - - ```ts - @api({ - basePath: '/', - paths: { - '/greet': { - get: { - 'x-operation-name': 'greet', - 'x-controller-name': 'MyController', - parameters: [{name: 'name', type: 'string', in: 'query'}], - responses: { - '200': { - description: 'greeting text', - schema: {type: 'string'}, - } - } - } - } - } - }) - class MyController { - // The operation endpoint defined here will be overriden! - @get('/foo') - @param.query.number('limit') - greet(name) { - } - } - app.controller(MyController); - ``` +Syntax: +[`@api(spec: ControllerSpec)`](http://apidocs.loopback.io/@loopback%2fcore/#783) + +`@api` is a decorator for controller constructor, it's called before a +controller class. `@api` is used when you have a base path and a Paths Object, +which contains all path definitions of your controller. Please note the api +specs defined with `@api` will override other api specs defined inside the +controller. For example: + +```ts +@api({ + basePath: '/', + paths: { + '/greet': { + get: { + 'x-operation-name': 'greet', + 'x-controller-name': 'MyController', + parameters: [{name: 'name', type: 'string', in: 'query'}], + responses: { + '200': { + description: 'greeting text', + schema: {type: 'string'}, + }, + }, + }, + }, + }, +}) +class MyController { + // The operation endpoint defined here will be overriden! + @get('/foo') + @param.query.number('limit') + greet(name) {} +} +app.controller(MyController); +``` - A more detailed explanation can be found in [Specifying Controller APIs](controllers.htm#specifying-controller-apis) +A more detailed explanation can be found in +[Specifying Controller APIs](controllers.htm#specifying-controller-apis) ### Operation Decorator - Syntax: [`@operation(verb: string, path: string, spec?: OperationObject)`](http://apidocs.loopback.io/@loopback%2fcore/#818) +Syntax: +[`@operation(verb: string, path: string, spec?: OperationObject)`](http://apidocs.loopback.io/@loopback%2fcore/#818) - `@operation` is a controller method decorator. It exposes a Controller method as - a REST API operation. You can specify the verb, path, parameters and response - as specification of your endpoint, for example: +`@operation` is a controller method decorator. It exposes a Controller method as +a REST API operation. You can specify the verb, path, parameters and response as +specification of your endpoint, for example: - ```ts - const spec = { - parameters: [{name: 'name', type: 'string', in: 'query'}], - responses: { - '200': { - description: 'greeting text', - schema: {type: 'boolean'}, - } - } - }; - class MyController { - @operation('HEAD', '/checkExist', spec) - checkExist(name) { - } - } - ``` +```ts +const spec = { + parameters: [{name: 'name', type: 'string', in: 'query'}], + responses: { + '200': { + description: 'greeting text', + schema: {type: 'boolean'}, + }, + }, +}; +class MyController { + @operation('HEAD', '/checkExist', spec) + checkExist(name) {} +} +``` ### Commonly-used Operation Decorators - Syntax: [`@get(path: string, spec?: OperationObject)`](http://apidocs.loopback.io/@loopback%2fcore/#798) +Syntax: +[`@get(path: string, spec?: OperationObject)`](http://apidocs.loopback.io/@loopback%2fcore/#798) - Same Syntax for decorators [`@post`](http://apidocs.loopback.io/@loopback%2fcore/#802) - , [`@put`](http://apidocs.loopback.io/@loopback%2fcore/#806) - , [`@patch`](http://apidocs.loopback.io/@loopback%2fcore/#810) - , [`@del`](http://apidocs.loopback.io/@loopback%2fcore/#814) +Same Syntax for decorators +[`@post`](http://apidocs.loopback.io/@loopback%2fcore/#802) , +[`@put`](http://apidocs.loopback.io/@loopback%2fcore/#806) , +[`@patch`](http://apidocs.loopback.io/@loopback%2fcore/#810) , +[`@del`](http://apidocs.loopback.io/@loopback%2fcore/#814) - You can call these sugar operation decorators as a shortcut of `@operation`, for example: - ```ts - class MyController { - @get('/greet', spec) - greet(name) { - } - } - ``` +You can call these sugar operation decorators as a shortcut of `@operation`, for +example: + +```ts +class MyController { + @get('/greet', spec) + greet(name) {} +} +``` - is equivalent to +is equivalent to - ```ts - class MyController { - @operation('GET', '/greet', spec) - greet(name) { - } - } - ``` +```ts +class MyController { + @operation('GET', '/greet', spec) + greet(name) {} +} +``` - For more usage, refer to [Routing to Controllers](controllers.htm#routing-to-controllers) +For more usage, refer to +[Routing to Controllers](controllers.htm#routing-to-controllers) ### Parameter Decorator - Syntax: [`@param(paramSpec: ParameterObject)`]() +Syntax: [`@param(paramSpec: ParameterObject)`](<>) - Optional Pattern: `@param.${in}.${type}(${name})` +Optional Pattern: `@param.${in}.${type}(${name})` - `@param` can be applied to method itself or specific parameters, it describes - an input parameter of a Controller method. +`@param` can be applied to method itself or specific parameters, it describes an +input parameter of a Controller method. - For example: +For example: - ```ts - // example 1: decorator `@param ` applied on method level - class MyController { - @get('/') - @param(offsetSpec) - @param(pageSizeSpec) - list(offset?: number, pageSize?: number) {} - } - ``` - - or - - ```ts - // example 2: decorator `@param` applied on parameter level - class MyController { - @get('/') - list( - @param(offsetSpec) offset?: number, - @param(pageSizeSpec) pageSize?: number, - ) {} - } - ``` +```ts +// example 1: decorator `@param ` applied on method level +class MyController { + @get('/') + @param(offsetSpec) + @param(pageSizeSpec) + list(offset?: number, pageSize?: number) {} +} +``` - In the first example, we apply multiple decorators to a single declaration. - The order in which the decorators are called is important because a `@param` - decorator must be applied after an operation decorator. - To learn more about TypeScript decorator composition, - refer to [TypeScript Decorator Documentation](https://www.typescriptlang.org/docs/handbook/decorators.html) +or - Please note method level `@param` and parameter level `@param` are mutually exclusive, - you can not mix and apply them to the same parameter. +```ts +// example 2: decorator `@param` applied on parameter level +class MyController { + @get('/') + list( + @param(offsetSpec) offset?: number, + @param(pageSizeSpec) pageSize?: number, + ) {} +} +``` + +In the first example, we apply multiple decorators to a single declaration. The +order in which the decorators are called is important because a `@param` +decorator must be applied after an operation decorator. To learn more about +TypeScript decorator composition, refer to +[TypeScript Decorator Documentation](https://www.typescriptlang.org/docs/handbook/decorators.html) + +Please note method level `@param` and parameter level `@param` are mutually +exclusive, you can not mix and apply them to the same parameter. - You can also use this pattern to make it quicker to define params: `@param.${in}.${type}(${name})` +You can also use this pattern to make it quicker to define params: +`@param.${in}.${type}(${name})` - - in: one of the following values: `query`, `header`, `path`, `formData`, `body` - - type: one of the following values: `string`, `number`, `boolean`, `integer` - - name: a `string`, name of the parameter +- in: one of the following values: `query`, `header`, `path`, `formData`, `body` +- type: one of the following values: `string`, `number`, `boolean`, `integer` +- name: a `string`, name of the parameter - So an example would be `@param.query.number('offset')`. - You can find the specific usage in [Writing Controller methods](controller.md#writing-controller-methods) +So an example would be `@param.query.number('offset')`. You can find the +specific usage in +[Writing Controller methods](controller.md#writing-controller-methods) ## Dependency Injection `@inject` is a decorator to annotate method arguments for automatic injection by LoopBack's IoC container. -The injected values are applied to a constructed instance, so it can only be used on -non-static properties or constructor parameters of a Class. +The injected values are applied to a constructed instance, so it can only be +used on non-static properties or constructor parameters of a Class. -The `@inject` decorator allows you to inject dependencies bound to any implementation -of the [Context](#context) object, such as an Application instance or a request context instance. -You can bind values, class definitions and provider functions to those contexts and -then resolve values (or the results of functions that return those values!) in other -areas of your code. +The `@inject` decorator allows you to inject dependencies bound to any +implementation of the [Context](#context) object, such as an Application +instance or a request context instance. You can bind values, class definitions +and provider functions to those contexts and then resolve values (or the results +of functions that return those values!) in other areas of your code. ```ts // application.ts @@ -215,9 +226,9 @@ class MyApp extends Application { } ``` -Now that we've bound the 'config.widget' key to our configuration object, -and 'logger.widget' key to the function `logInfo()`, -we can inject them in our WidgetController: +Now that we've bound the 'config.widget' key to our configuration object, and +'logger.widget' key to the function `logInfo()`, we can inject them in our +WidgetController: ```ts // widget-controller.ts @@ -225,21 +236,21 @@ import {widgetSpec} from '../apispec'; @api(widgetSpec) class WidgetController { // injection for property - @inject('logger.widget') - private logger: Function + @inject('logger.widget') private logger: Function; // injection for constructor parameter constructor( - @inject('config.widget') protected widget: any - // This will be resolved at runtime! + @inject('config.widget') protected widget: any, // This will be resolved at runtime! ) {} // etc... } ``` -A few variants of `@inject` are provided to declare special forms of dependencies: +A few variants of `@inject` are provided to declare special forms of +dependencies: -- `@inject.getter`: inject a getter function that returns a promise of the bound value of the key +- `@inject.getter`: inject a getter function that returns a promise of the bound + value of the key Syntax: `@inject.getter(bindingKey: string)`. @@ -247,7 +258,8 @@ Syntax: `@inject.getter(bindingKey: string)`. class HelloController { constructor( @inject.getter('authentication.currentUser') - private userGetter: Getter) {} + private userGetter: Getter, + ) {} async greet() { const user = await this.userGetter(); @@ -263,8 +275,8 @@ Syntax: `@inject.setter(bindingKey: string)`. ```ts class HelloController { constructor( - @inject.setter('greeting') - private greetingSetter: Setter) {} + @inject.setter('greeting') private greetingSetter: Setter, + ) {} greet() { greetingSetter('my-greeting-message'); @@ -272,26 +284,27 @@ class HelloController { } ``` -- `@inject.tag`: inject an array of values by a pattern or regexp to match bindng tags +- `@inject.tag`: inject an array of values by a pattern or regexp to match + bindng tags Syntax: `@inject.tag(tag: string | RegExp)`. ```ts - class Store { - constructor(@inject.tag('store:location') public locations: string[]) {} - } +class Store { + constructor(@inject.tag('store:location') public locations: string[]) {} +} - ctx.bind('store').toClass(Store); - ctx - .bind('store.locations.sf') - .to('San Francisco') - .tag('store:location'); - ctx - .bind('store.locations.sj') - .to('San Jose') - .tag('store:location'); - const store = ctx.getSync('store'); - // `store.locations` is now `['San Francisco', 'San Jose']` +ctx.bind('store').toClass(Store); +ctx + .bind('store.locations.sf') + .to('San Francisco') + .tag('store:location'); +ctx + .bind('store.locations.sj') + .to('San Jose') + .tag('store:location'); +const store = ctx.getSync('store'); +// `store.locations` is now `['San Francisco', 'San Jose']` ``` - `@inject.context`: inject the current context @@ -299,173 +312,185 @@ Syntax: `@inject.tag(tag: string | RegExp)`. Syntax: `@inject.context()`. ```ts - class MyComponent { - constructor(@inject.context() public ctx: Context) {} - } +class MyComponent { + constructor(@inject.context() public ctx: Context) {} +} - const ctx = new Context(); - ctx.bind('my-component').toClass(MyComponent); - const component = ctx.getSync('my-component'); - // `component.ctx` should be the same as `ctx` +const ctx = new Context(); +ctx.bind('my-component').toClass(MyComponent); +const component = ctx.getSync('my-component'); +// `component.ctx` should be the same as `ctx` ``` -**NOTE**: It's recommended to use `@inject` with specific keys for dependency injection if possible. Use `@inject.context` only when the code need to access the current context object for advanced use cases. +**NOTE**: It's recommended to use `@inject` with specific keys for dependency +injection if possible. Use `@inject.context` only when the code need to access +the current context object for advanced use cases. -For more information, see the [Dependency Injection](Dependency-Injection.md) section under [LoopBack Core Concepts](Concepts.md) +For more information, see the [Dependency Injection](Dependency-Injection.md) +section under [LoopBack Core Concepts](Concepts.md) ## Authentication Decorator - Syntax: `@authenticate(strategyName: string, options?: Object)` +Syntax: `@authenticate(strategyName: string, options?: Object)` - Marks a controller method as needing an authenticated user. - This decorator requires a strategy name as a parameter. +Marks a controller method as needing an authenticated user. This decorator +requires a strategy name as a parameter. - Here's an example using 'BasicStrategy': to authenticate user in function `whoAmI`: +Here's an example using 'BasicStrategy': to authenticate user in function +`whoAmI`: - ```ts - // my-controller.ts - import { authenticate } from '@loopback/authentication'; - import { inject } from '@loopback/context'; +```ts +// my-controller.ts +import {authenticate} from '@loopback/authentication'; +import {inject} from '@loopback/context'; - class MyController { - constructor( - @inject(BindingKeys.Authentication.CURRENT_USER) - private user: UserProfile, - ) {} +class MyController { + constructor( + @inject(BindingKeys.Authentication.CURRENT_USER) private user: UserProfile, + ) {} - @authenticate('BasicStrategy') - async whoAmI() : Promise { - return this.user.id; - } + @authenticate('BasicStrategy') + async whoAmI(): Promise { + return this.user.id; } - ``` +} +``` ## Repository Decorators - As a Domain-driven design concept, - the repository is a layer between your domain object and data mapping layers - using a collection-like interface for accessing domain objects. +As a Domain-driven design concept, the repository is a layer between your domain +object and data mapping layers using a collection-like interface for accessing +domain objects. - In LoopBack, a domain object is usually a TypeScript/JavaScript Class instance, - and a typical example of a data mappting layer module could be a database's node.js driver. +In LoopBack, a domain object is usually a TypeScript/JavaScript Class instance, +and a typical example of a data mappting layer module could be a database's +node.js driver. - LoopBack repository encapsulates your TypeScript/JavaScript Class instance, - and its methods that communicate with your database. - It is an interface to implement data persistence. +LoopBack repository encapsulates your TypeScript/JavaScript Class instance, and +its methods that communicate with your database. It is an interface to implement +data persistence. - Repository decorators are used for defining models(domain objects) for use with your chosen datasources, - and the navigation strategies among models. +Repository decorators are used for defining models(domain objects) for use with +your chosen datasources, and the navigation strategies among models. - If you are not familiar with repository related concepts like `Model`, `Entity` and `Datasource`, - please see LoopBack concept [Repositories](#Repositories.md) to learn more. +If you are not familiar with repository related concepts like `Model`, `Entity` +and `Datasource`, please see LoopBack concept [Repositories](#Repositories.md) +to learn more. ### Model Decorators - Model is a Class that LoopBack builds for you to organize the data that - share same configurations and properties. - You can use model decorators to define a model and its properties. +Model is a Class that LoopBack builds for you to organize the data that share +same configurations and properties. You can use model decorators to define a +model and its properties. #### Model Decorator - Syntax: `@model(definition?: ModelDefinitionSyntax)` +Syntax: `@model(definition?: ModelDefinitionSyntax)` - Model decorator is a Class decorator. - In LoopBack 4, we inherit the model definition format from LoopBack 3, - you can find it in [Model definition JSON file](https://loopback.io/doc/en/lb3/Model-definition-JSON-file). - For usage examples, see [Define Models](Repositories.md#define-models) +Model decorator is a Class decorator. In LoopBack 4, we inherit the model +definition format from LoopBack 3, you can find it in +[Model definition JSON file](https://loopback.io/doc/en/lb3/Model-definition-JSON-file). +For usage examples, see [Define Models](Repositories.md#define-models) - *Please note we will elaborate more about model and model definition in #Model.htm,* - *and replace the link above with LoopBack 4 link* +_Please note we will elaborate more about model and model definition in +\#Model.htm,_ _and replace the link above with LoopBack 4 link_ - By using a model decorator, you can define a model as your repository's metadata, - then you have two choices to create the repository instance: +By using a model decorator, you can define a model as your repository's +metadata, then you have two choices to create the repository instance: - One is to inject your repository and resolve it with Legacy Juggler that complete - with CRUD operations for accessing the model's data. - A use case can be found in section [Repository decorator](#repository-decorator) +One is to inject your repository and resolve it with Legacy Juggler that +complete with CRUD operations for accessing the model's data. A use case can be +found in section [Repository decorator](#repository-decorator) - The other one is defining your own repository without using legacy juggler, - and use an ORM/ODM of your choice. +The other one is defining your own repository without using legacy juggler, and +use an ORM/ODM of your choice. - ```ts - // Missing example here - // Will be provided in Model.htm - // refer to [example code](https://github.com/strongloop/loopback-next-example/blob/master/services/account-without-juggler/repositories/account/models/Account.ts) - ``` +```ts +// Missing example here +// Will be provided in Model.htm +// refer to [example code](https://github.com/strongloop/loopback-next-example/blob/master/services/account-without-juggler/repositories/account/models/Account.ts) +``` #### Property Decorator - Syntax: `@property(definition?: PropertyDefinition)` +Syntax: `@property(definition?: PropertyDefinition)` - The property decorator defines metadata for a property on a Model definition. - The format of property definitions can be found in [Property definitions](https://loopback.io/doc/en/lb2/Model-definition-JSON-file.html#properties) +The property decorator defines metadata for a property on a Model definition. +The format of property definitions can be found in +[Property definitions](https://loopback.io/doc/en/lb2/Model-definition-JSON-file.html#properties) - For usage examples, see [Define Models](Repositories.md#define-models) +For usage examples, see [Define Models](Repositories.md#define-models) ### Relation Decorators - The relation decorator defines the nature of a relationship between two models. +The relation decorator defines the nature of a relationship between two models. - *This feature has not yet been released in alpha form. Documentation will be* - *added here as this feature progresses.* +_This feature has not yet been released in alpha form. Documentation will be_ +_added here as this feature progresses._ #### Relation Decorator - Syntax: `@relation` +Syntax: `@relation` - Register a general relation. +Register a general relation. #### Specfic Relation Decorator - Syntax: +Syntax: - - `@belongsTo` - - `@hasOne` - - `@hasMany` - - `@embedsOne` - - `@embedsMany` - - `@referencesOne` - - `@referencesMany` +- `@belongsTo` +- `@hasOne` +- `@hasMany` +- `@embedsOne` +- `@embedsMany` +- `@referencesOne` +- `@referencesMany` - Register a specific relation +Register a specific relation ### Repository Decorator - Syntax: `@repository(model: string | typeof Entity, dataSource?: string | juggler.DataSource)` - - This decorator either injects an existing repository or creates a repository - from a model and a datasource. +Syntax: - The injection example can be found in [Repository#controller-configuration](Repositories.md#controller-configuration) +```ts +@repository(model: string | typeof Entity, dataSource?: string | juggler.DataSource) +``` - To create a repository in a controller, you can define your model and datasource - first, then import them in your controller file: +This decorator either injects an existing repository or creates a repository +from a model and a datasource. - *To learn more about creating model and datasource, please see the example in [Thinking in LoopBack](Thinking-in-LoopBack.htm#define-product-model-repository-and-data-source)* +The injection example can be found in +[Repository#controller-configuration](Repositories.md#controller-configuration) - ```ts - // my-controller.ts - import { Todo } from '{path_of_Todo_model}.ts'; - import { datasource } from '{path_of_datasource}.ts'; +To create a repository in a controller, you can define your model and datasource +first, then import them in your controller file: - export class TodoController { - @repository(Todo, datasource) - repository: EntityCrudRepository; - ... ... - } - ``` +_To learn more about creating model and datasource, please see the example in +[Thinking in LoopBack](Thinking-in-LoopBack.htm#define-product-model-repository-and-data-source)_ - If the model or datasource is already bound to the app, you can create the - repository by providing their names instead of the classes. For example: +```ts +// my-controller.ts +import { Todo } from '{path_of_Todo_model}.ts'; +import { datasource } from '{path_of_datasource}.ts'; + +export class TodoController { + @repository(Todo, datasource) + repository: EntityCrudRepository; + ... ... +} +``` - ```ts - // with `datasource` and `Todo` already defined. - app.bind('datasources.ds').to(datasource); - app.bind('repositories.todo').to(Todo); +If the model or datasource is already bound to the app, you can create the +repository by providing their names instead of the classes. For example: - export class TodoController { - @repository('todo', 'ds') - repository: EntityCrudRepository; - // etc - } - ``` +```ts +// with `datasource` and `Todo` already defined. +app.bind('datasources.ds').to(datasource); +app.bind('repositories.todo').to(Todo); + +export class TodoController { + @repository('todo', 'ds') + repository: EntityCrudRepository; + // etc +} +``` diff --git a/docs/site/Defining-the-API-using-code-first-approach.md b/docs/site/Defining-the-API-using-code-first-approach.md index a75dd1b177b6..7138f856ca60 100644 --- a/docs/site/Defining-the-API-using-code-first-approach.md +++ b/docs/site/Defining-the-API-using-code-first-approach.md @@ -12,12 +12,12 @@ summary: You may want to build your application from the 'bottom up' if you: -* do not have a complete understanding of what your existing tools can offer. -* want to capture already existing domain models so that they can be reflected +- do not have a complete understanding of what your existing tools can offer. +- want to capture already existing domain models so that they can be reflected as APIs for external consumption. -* need to grow and change your API from the initial implementation -* want to set up and run an API from an early stage of the production to - easily envision the big picture of the end product. +- need to grow and change your API from the initial implementation +- want to set up and run an API from an early stage of the production to easily + envision the big picture of the end product. There are various tools available to LoopBack which allows this bottom-up approach of building your application to be simple through the usages of @@ -25,19 +25,19 @@ metadata and decorators. ### Start with LoopBack artfiacts -With TypeScript's [experimental decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) +With TypeScript's +[experimental decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) feature, APIs can be automatically built and exposed as your application -continues development. Some key concepts utilize decorators to gather -_metadata_ about your code and then assemble them into a valid OpenAPI -specification, which provide a description of your API. -These concepts and their decorators include: - -* [Model](Model.md) - * `@model()` - * `@property()` -* [Routes](Routes.md) - * `@operation()` - * `@param()` +continues development. Some key concepts utilize decorators to gather _metadata_ +about your code and then assemble them into a valid OpenAPI specification, which +provide a description of your API. These concepts and their decorators include: + +- [Model](Model.md) + - `@model()` + - `@property()` +- [Routes](Routes.md) + - `@operation()` + - `@param()` ### Define your models @@ -46,13 +46,11 @@ layer and the datasource layer. Since your API is going to be built around the manipulation of models and their properties, they will be the first to be defined. -{% include note.html content=" -`Todo` model from [tutorial](https://github.com/strongloop/loopback-next/blob/master/packages/example-getting-started/docs/model.md#srcmodelstodomodelts) -is used for demonstration here. -" %} +{% include note.html content=" `Todo` model from +[tutorial](https://github.com/strongloop/loopback-next/blob/master/packages/example-getting-started/docs/model.md#srcmodelstodomodelts) +is used for demonstration here. " %} -First, write a simple TypeScript class describing your model and its -properties: +First, write a simple TypeScript class describing your model and its properties: {% include code-caption.html content="src/models/todo.model.ts" %} @@ -66,18 +64,18 @@ export class Todo { ``` To this representation of your model, we can use the `@model` and `@property` -decorators to create the model's _metadata_; a model definition. -LoopBack and LoopBack extensions can use this model definition for -a wide variety of uses, such as: +decorators to create the model's _metadata_; a model definition. LoopBack and +LoopBack extensions can use this model definition for a wide variety of uses, +such as: -* generating OpenAPI schema for your APIs -* validating instances of the models during the request/response lifecycle -* automatically inferring relationships between models during datasource +- generating OpenAPI schema for your APIs +- validating instances of the models during the request/response lifecycle +- automatically inferring relationships between models during datasource operations To apply these decorators to your model, you simply prefix the class definition -with the `@model` decorator, and prefix each property with the -`@property` decorator: +with the `@model` decorator, and prefix each property with the `@property` +decorator: {% include code-caption.html content="src/models/todo.model.ts" %} @@ -98,14 +96,12 @@ export class Todo { ### Define your routes -{% include note.html content=" -`TodoController` from [tutorial](https://github.com/strongloop/loopback-next/blob/master/packages/example-getting-started/docs/controller.md#srccontrollerstodocontrollerts-2) -is used for -demonstration here. -" %} +{% include note.html content=" `TodoController` from +[tutorial](https://github.com/strongloop/loopback-next/blob/master/packages/example-getting-started/docs/controller.md#srccontrollerstodocontrollerts-2) +is used for demonstration here. " %} -Once your models are defined, create a controller to host your routes -for each [paths](https://swagger.io/specification/#pathsObject) of your API: +Once your models are defined, create a controller to host your routes for each +[paths](https://swagger.io/specification/#pathsObject) of your API: {% include code-caption.html content="src/controllers/todo.controller.ts" %} @@ -127,9 +123,9 @@ export class TodoController { } ``` -The controller's routes in their current state has no information on which -API endpoints they belong to. Add them in by appending `@operation` to each -method of your routes and `@param` or `@requestBody` to its parameters: +The controller's routes in their current state has no information on which API +endpoints they belong to. Add them in by appending `@operation` to each method +of your routes and `@param` or `@requestBody` to its parameters: {% include code-caption.html content="src/controllers/todo.controller.ts" %} @@ -157,30 +153,29 @@ export class TodoController { } ``` -Once your routes have been decorated, your application is ready to serve -its API. When an instance of `RestServer` is run, an OpenAPI specification -representing your application's API is built. The spec is generated -entirely from the decorated elements' metadata, which in turn provides -routing logic for your API when your application is running. +Once your routes have been decorated, your application is ready to serve its +API. When an instance of `RestServer` is run, an OpenAPI specification +representing your application's API is built. The spec is generated entirely +from the decorated elements' metadata, which in turn provides routing logic for +your API when your application is running. ### Reviewing your API specification To review your complete API specification, run your application with the decorated controllers registered. Once it is running, visit `/openapi.json` -endpoint to access your API specification in JSON format or `/openapi.yaml` -for YAML. Alternatively, the specification file can also be accessed -in code through the `getApiSpec()` function from your `RestServer` instance. +endpoint to access your API specification in JSON format or `/openapi.yaml` for +YAML. Alternatively, the specification file can also be accessed in code through +the `getApiSpec()` function from your `RestServer` instance. For a complete walkthrough of developing an application with the bottom-up -approach, see our [Todo application](https://github.com/strongloop/loopback-next/blob/master/packages/example-getting-started/README.md#loopbackexample-getting-started) +approach, see our +[Todo application](https://github.com/strongloop/loopback-next/blob/master/packages/example-getting-started/README.md#loopbackexample-getting-started) tutorial. -{% include note.html content=" -If you would like to create your API manually or already have one designed, -refer to [Defining the API using design-first approach](Defining-the-API-using-design-first-approach.md) -page for best practices. -" %} +{% include note.html content=" If you would like to create your API manually or +already have one designed, refer to +[Defining the API using design-first approach](Defining-the-API-using-design-first-approach.md) +page for best practices. " %} {% include next.html content= " -[Defining your testing strategy](./Defining-your-testing-strategy.md) -" %} +[Defining your testing strategy](./Defining-your-testing-strategy.md) " %} diff --git a/docs/site/Defining-the-API-using-design-first-approach.md b/docs/site/Defining-the-API-using-design-first-approach.md index f27ad2f46c3c..385614b502e7 100644 --- a/docs/site/Defining-the-API-using-design-first-approach.md +++ b/docs/site/Defining-the-API-using-design-first-approach.md @@ -11,25 +11,53 @@ summary: {% include important.html content="The top-down approach for building LoopBack applications is not yet fully supported. Therefore, the steps outlined in this page are outdated and may not work out of the box. They will be revisited after -our MVP release. -"%} +our MVP release. "%} ## Define the API from top to bottom (design-first) ### Start with data -When building an API, its usually easiest to start by outlining some example data that consumers of the API will need. This can act as the first rough draft of the API specification for smaller applications / APIs. In this tutorial, you'll start by sketching out some example API response data as simple JavaScript objects: +When building an API, its usually easiest to start by outlining some example +data that consumers of the API will need. This can act as the first rough draft +of the API specification for smaller applications / APIs. In this tutorial, +you'll start by sketching out some example API response data as simple +JavaScript objects: ```js const products = [ - {name: 'Headphones', price: 29.99, category: '/categories/accessories', available: true, deals: ['50% off', 'free shipping']}, - {name: 'Mouse', price: 32.99, category: '/categories/accessories', available: true, deals: ['30% off', 'free shipping']}, - {name: 'yPhone', price: 299.99, category: '/categories/phones', available: true, deals: ['free shipping']}, - {name: 'yBook', price: 5999.99, category: '/categories/computers', available: true} + { + name: 'Headphones', + price: 29.99, + category: '/categories/accessories', + available: true, + deals: ['50% off', 'free shipping'], + }, + { + name: 'Mouse', + price: 32.99, + category: '/categories/accessories', + available: true, + deals: ['30% off', 'free shipping'], + }, + { + name: 'yPhone', + price: 299.99, + category: '/categories/phones', + available: true, + deals: ['free shipping'], + }, + { + name: 'yBook', + price: 5999.99, + category: '/categories/computers', + available: true, + }, ]; ``` -With the example data defined, you can start to get an idea of how to separate the data into individual proper nouns, which will eventually be defined in different ways. Either as resources, schemas, models, or repositories. +With the example data defined, you can start to get an idea of how to separate +the data into individual proper nouns, which will eventually be defined in +different ways. Either as resources, schemas, models, or repositories. - `CatalogItem` - Each object in the array above - `Category` - Has a URL, and more information about the category @@ -38,83 +66,103 @@ With the example data defined, you can start to get an idea of how to separate t - `Deals` - Information about promotions on a group of products ### Outline the API -With the proper nouns of the API defined, you can now start to think about what the API will look like. -This is where you choose how fine or coarse grain the API will be. You have to decide which proper nouns above will be available as _Resources_. The easiest way to figure out which Resources are needed is by sketching out the URLs (without verbs) for the API: +With the proper nouns of the API defined, you can now start to think about what +the API will look like. + +This is where you choose how fine or coarse grain the API will be. You have to +decide which proper nouns above will be available as _Resources_. The easiest +way to figure out which Resources are needed is by sketching out the URLs +(without verbs) for the API: - - `/products?{query}` - Search for products in the catalog - - `/product/{slug}` - Get the details for a particular product - - `/deals?{query}` - Search for deals - - `/deal/{slug}` - Get the details for a particular deal - - `/categories?{query}` - Search for categories - - `/category/{slug}` - Get the details for a particular category - - `/category/{slug}/products?{query}` - Search for products in a particular category +- `/products?{query}` - Search for products in the catalog +- `/product/{slug}` - Get the details for a particular product +- `/deals?{query}` - Search for deals +- `/deal/{slug}` - Get the details for a particular deal +- `/categories?{query}` - Search for categories +- `/category/{slug}` - Get the details for a particular category +- `/category/{slug}/products?{query}` - Search for products in a particular + category ### Break down the data into resources + With the URLs, defined, its easy to determine which resources you'll need. - - `ProductResource` - - `DealResource` - - `CategoryResource` +- `ProductResource` +- `DealResource` +- `CategoryResource` -This is where it's useful to determine similarities between Resources; for example, the `ProductResource`, `DealResource`, and `CategoryResource` all have the same URL structure, with the exception of `/category/{slug}/products?{query}` path on `CategoryResource`: +This is where it's useful to determine similarities between Resources; for +example, the `ProductResource`, `DealResource`, and `CategoryResource` all have +the same URL structure, with the exception of +`/category/{slug}/products?{query}` path on `CategoryResource`: - - `/{pluralName}?{query}` - Search with a query and the resource plural name - - `/{name}/{slug}` - Get details about the resource +- `/{pluralName}?{query}` - Search with a query and the resource plural name +- `/{name}/{slug}` - Get details about the resource ### Using patterns to reduce duplication -It can be tricky to determine the patterns on which to base the API, since you'll likely want to change it in the future. To keep the patterns flexible, you can define these patterns via simple TypeScript functions (you can also do it in JavaScript). Start with a `SearchableResource` pattern, since all of the resources must support the same search and listing operations. +It can be tricky to determine the patterns on which to base the API, since +you'll likely want to change it in the future. To keep the patterns flexible, +you can define these patterns via simple TypeScript functions (you can also do +it in JavaScript). Start with a `SearchableResource` pattern, since all of the +resources must support the same search and listing operations. -The `SearchableResource` pattern will define all of the semantics for an OpenAPI fragment that supports search. +The `SearchableResource` pattern will define all of the semantics for an OpenAPI +fragment that supports search. -{% include code-caption.html content="/apidefs/templates/searchable-resource.ts" %} +{% include code-caption.html content="/apidefs/templates/searchable-resource.ts" +%} ```ts export let searchableResource = (resource: any, type: string) => ({ paths: { - [`/${resource.path}`]: { // pattern + [`/${resource.path}`]: { + // pattern get: { - "parameters": [{ - in: "query", - name: "filter", - type: "string", - }], - "responses": { + parameters: [ + { + in: 'query', + name: 'filter', + type: 'string', + }, + ], + responses: { 200: { - description: resource.description || - `Result set of type ${type} returned.`, + description: + resource.description || `Result set of type ${type} returned.`, schema: { $ref: `#/definitions/${type}`, - type: "array", + type: 'array', }, }, }, - "x-controller-name": resource.controller, - "x-operation-name": "search", + 'x-controller-name': resource.controller, + 'x-operation-name': 'search', }, }, - [`/${resource.path}/{slug}`]: { // pattern + [`/${resource.path}/{slug}`]: { + // pattern get: { - "parameters": [ + parameters: [ { - in: "path", - name: "slug", + in: 'path', + name: 'slug', required: true, - type: "string", + type: 'string', }, ], - "responses": { + responses: { 200: { - description: resource.description || - `Result of type ${type} returned.`, + description: + resource.description || `Result of type ${type} returned.`, schema: { $ref: `#/definitions/${type}`, }, }, }, - "x-controller-name": resource.controller, - "x-operation-name": "getDetails", + 'x-controller-name': resource.controller, + 'x-operation-name': 'getDetails', }, }, }, @@ -123,34 +171,36 @@ export let searchableResource = (resource: any, type: string) => ({ Here's another example for creating a POST template, called `CreatableResource`. -{% include code-caption.html content="/apidefs/templates/creatable-resource.ts" %} +{% include code-caption.html content="/apidefs/templates/creatable-resource.ts" +%} ```ts export let creatableResource = (resource: any, type: string) => ({ paths: { - [`/${resource.path}`]: { // pattern + [`/${resource.path}`]: { + // pattern post: { - "parameters": [ + parameters: [ { - in: "body", - name: "body", + in: 'body', + name: 'body', required: true, schema: { - $ref: `#/definitions/${type}`, + $ref: `#/definitions/${type}`, }, }, ], - "responses": { + responses: { 201: { - description: resource.description - || `The ${type} instance was created.`, + description: + resource.description || `The ${type} instance was created.`, schema: { - $ref: `#/definitions/${type}`, + $ref: `#/definitions/${type}`, }, }, }, - "x-controller-name": resource.controller, - "x-operation-name": "create", + 'x-controller-name': resource.controller, + 'x-operation-name': 'create', }, }, }, @@ -163,20 +213,21 @@ OpenAPI. {% include code-caption.html content="/apidefs/templates/type-definition.ts" %} ```ts -import { DefinitionsObject } from "@loopback/openapi-spec"; +import {DefinitionsObject} from '@loopback/openapi-spec'; export let TypeDefinition = (type: any): DefinitionsObject => ({ definitions: { [`${type.name}`]: { - properties: type.definition, + properties: type.definition, }, }, }); ``` Given the pattern function above, you can now create the OpenAPI fragment that -represents the `ProductController` portion of the full specification. -This example, uses [lodash](https://lodash.com/) to help with merging generated definitions together. Install lodash with this command: +represents the `ProductController` portion of the full specification. This +example, uses [lodash](https://lodash.com/) to help with merging generated +definitions together. Install lodash with this command: ```shell npm install --save lodash @@ -185,44 +236,62 @@ npm install --save lodash {% include code-caption.html content="/apidefs/product.api.ts" %} ```ts -import * as _ from "lodash"; +import * as _ from 'lodash'; // Assuming you have created the "base" schema elsewhere. // If there are no common properties between all of the endpoint objects, // then you can ignore this. -import BaseSchema from "../BaseSchema"; +import BaseSchema from '../BaseSchema'; // Don't forget to export the template functions under a common file! -import { SearchableResource, CreatableResource, TypeDefinition } from "./templates"; +import { + SearchableResource, + CreatableResource, + TypeDefinition, +} from './templates'; let ProductAPI: ControllerSpec = {}; const ProductDefinition = {}; // Build type definition using base schema + additional properties. -_.merge(ProductDefinition, BaseSchema, TypeDefinition({ - price: { - type: "number", - minimum: 0, - exclusiveMinimum: true, - } -})); - -const ProductGetResource = SearchableResource({ - controller: "ProductController", - operation: "search", - path: "products", -}, "Product"); - -const ProductCreateResource = CreatableResource({ - controller: "ProductController", - operation: "create", - path: "products", -}, "Product"); +_.merge( + ProductDefinition, + BaseSchema, + TypeDefinition({ + price: { + type: 'number', + minimum: 0, + exclusiveMinimum: true, + }, + }), +); + +const ProductGetResource = SearchableResource( + { + controller: 'ProductController', + operation: 'search', + path: 'products', + }, + 'Product', +); + +const ProductCreateResource = CreatableResource( + { + controller: 'ProductController', + operation: 'create', + path: 'products', + }, + 'Product', +); // Rinse and repeat for PUT, PATCH, DELETE, etc... // Merge all of the objects together. // This will mix the product definition into the "definitions" property of the // OpenAPI spec, and the resources will be mixed into the "paths" property. -_.merge(ProductAPI, ProductDefinition, ProductGetResource, - ProductCreateResource); +_.merge( + ProductAPI, + ProductDefinition, + ProductGetResource, + ProductCreateResource, +); // And export it! export default ProductAPI; @@ -230,20 +299,19 @@ export default ProductAPI; ### Connect OpenAPI fragments to Controllers -By separating each individual "Model"-level API export, you can link -them to their corresponding controllers throughout the application. +By separating each individual "Model"-level API export, you can link them to +their corresponding controllers throughout the application. {% include code-caption.html content="/controllers/product-controller.ts" %} ```ts -import { api } from "@loopback/core"; -import ProductApi from "../apidefs/product.api"; +import {api} from '@loopback/core'; +import ProductApi from '../apidefs/product.api'; // This decorator binds the Product API to the controller, // which will establish routing to the specified functions below. @api(ProductApi) export class ProductController { - // Note that the function names here match the strings in the "operation" // property you provided to the SearchableResource call in the previous // example. @@ -262,26 +330,25 @@ export class ProductController { ### Putting together the final API specification -Now that you've built the OpenAPI fragments for each of the controllers, -you can put them all together to produce the final OpenAPI spec. +Now that you've built the OpenAPI fragments for each of the controllers, you can +put them all together to produce the final OpenAPI spec. {% include code-caption.html content="/apidefs/swagger.ts" %} ```ts -import { ProductAPI, DealAPI, CategoryAPI } from "../apidefs"; -import * as OpenApiSpec from "@loopback/openapi-spec"; -import * as _ from "lodash"; - +import {ProductAPI, DealAPI, CategoryAPI} from '../apidefs'; +import * as OpenApiSpec from '@loopback/openapi-spec'; +import * as _ from 'lodash'; // Import API fragments here export let spec = OpenApiSpec.createEmptyApiSpec(); spec.info = { - title: "Your API", - version: "1.0", + title: 'Your API', + version: '1.0', }; -spec.swagger = "2.0"; -spec.basePath = "/"; +spec.swagger = '2.0'; +spec.basePath = '/'; _.merge(spec, ProductAPI); _.merge(spec, DealAPI); @@ -290,13 +357,12 @@ _.merge(spec, CategoryAPI); export default spec; ``` -You can then bind the full spec to the application using `app.api()`. -This works well for applications with a single REST server, because -there is only one API definition involved. +You can then bind the full spec to the application using `app.api()`. This works +well for applications with a single REST server, because there is only one API +definition involved. -If you are building an application with multiple REST servers, -where each server provides a different API, then you need -to call `server.api()` instead. +If you are building an application with multiple REST servers, where each server +provides a different API, then you need to call `server.api()` instead. You also need to associate the controllers implementing the spec with the app using `app.controller(GreetController)`. This is not done on the server level @@ -305,16 +371,19 @@ because a controller may be used with multiple server instances, and types! ```ts // application.ts // This should be the export from the previous example. -import spec from "../apidefs/swagger"; -import { RestApplication, RestServer } from "@loopback/rest"; -import { ProductController, DealController, CategoryController } from "./controllers"; +import spec from '../apidefs/swagger'; +import {RestApplication, RestServer} from '@loopback/rest'; +import { + ProductController, + DealController, + CategoryController, +} from './controllers'; export class YourMicroservice extends RestApplication { - constructor() { super({ rest: { - port: 3001 - } + port: 3001, + }, }); const app = this; @@ -330,9 +399,15 @@ export class YourMicroservice extends RestApplication { ## Validate the API specification -[The OpenAPI Swagger editor](https://editor.swagger.io) is a handy tool for editing OpenAPI specifications that comes with a built-in validator. It can be useful to manually validate an OpenAPI specification. +[The OpenAPI Swagger editor](https://editor.swagger.io) is a handy tool for +editing OpenAPI specifications that comes with a built-in validator. It can be +useful to manually validate an OpenAPI specification. -However, manual validation is tedious and error prone. It's better to use an automated solution that's run as part of a CI/CD workflow. LoopBack's `testlab` module provides a helper function for checking whether a specification conforms to OpenAPI Spec. Just add a new Mocha test that calls this helper function to the test suite: +However, manual validation is tedious and error prone. It's better to use an +automated solution that's run as part of a CI/CD workflow. LoopBack's `testlab` +module provides a helper function for checking whether a specification conforms +to OpenAPI Spec. Just add a new Mocha test that calls this helper function to +the test suite: ```ts // test/acceptance/api-spec.acceptance.ts @@ -351,14 +426,13 @@ describe('API specification', () => { }); ``` -See [Validate your OpenAPI specification](Testing-your-application.md#validate-your-openapi-specification) from [Testing your application](Testing-your-application.md) for more details. +See +[Validate your OpenAPI specification](Testing-your-application.md#validate-your-openapi-specification) +from [Testing your application](Testing-your-application.md) for more details. -{% include note.html content=" - If you would like to make tweaks to your API as you develop your application, - refer to [Defining the API using code-first approach](Defining-the-API-using-code-first-approach.md) - page for best practices. -" %} +{% include note.html content=" If you would like to make tweaks to your API as +you develop your application, refer to +[Defining the API using code-first approach](Defining-the-API-using-code-first-approach.md) +page for best practices. " %} -{% include next.html content= " -[Testing the API](./Testing-the-API.md) -" %} +{% include next.html content= " [Testing the API](./Testing-the-API.md) " %} diff --git a/docs/site/Defining-your-testing-strategy.md b/docs/site/Defining-your-testing-strategy.md index 4ab101ca2341..b4e9619eebfa 100644 --- a/docs/site/Defining-your-testing-strategy.md +++ b/docs/site/Defining-your-testing-strategy.md @@ -8,69 +8,128 @@ permalink: /doc/en/lb4/Defining-your-testing-strategy.html summary: --- -{% include previous.html content=" -This article continues from [Testing the API](./Testing-the-API.md) and [Defining the API using code-first approach](./Defining-the-API-using-code-first-approach.md). +{% include previous.html content=" This article continues +from [Testing the API](./Testing-the-API.md) and +[Defining the API using code-first approach](./Defining-the-API-using-code-first-approach.md). " %} ## Define your testing strategy -It may be tempting to overlook the importance of a good testing strategy when starting a new project. Initially, as the project is small and you mostly keep adding new code, even a badly-written test suite seems to work well. However, as the project grows and matures, inefficiencies in the test suite can severely slow down progress. +It may be tempting to overlook the importance of a good testing strategy when +starting a new project. Initially, as the project is small and you mostly keep +adding new code, even a badly-written test suite seems to work well. However, as +the project grows and matures, inefficiencies in the test suite can severely +slow down progress. A good test suite has the following properties: - - **Speed**: The test suite should complete quickly. This encourages short red-green-refactor cycle, which makes it easier to spot problems, because there have been few changes made since the last test run that passed. It also shortens deployment times, making it easy to frequently ship small changes, reducing the risk of major breakages. - - **Reliability**: The test suite should be reliable. No developer enjoys debugging a failing test only to find out it was poorly written and failures are not related to any problem in the tested code. Flaky tests reduce the trust in your tests, up to point where you learn to ignore these failures, which will eventually lead to a situation when a test failed legitimately because of a bug in the application, but you did not notice. - - **Isolation of failures**: The test suite should make it easy to isolate the source of test failures. To fix a failing test, developers need to find the specific place that does not work as expected. When the project contains thousands of lines and the test failure can be caused by any part of the system, then finding the bug is very difficult, time consuming and demotivating. - - **Resilience**: The test implementation should be robust and resilient to changes in the tested code. As the project grows and matures, you may need to change existing behavior. With a brittle test suite, each change may break dozens of tests, for example when you have many end-to-end/UI tests that rely on specific UI layout. This makes change prohibitively expensive, up to a point where you may start questioning the value of such test suite. +- **Speed**: The test suite should complete quickly. This encourages short + red-green-refactor cycle, which makes it easier to spot problems, because + there have been few changes made since the last test run that passed. It also + shortens deployment times, making it easy to frequently ship small changes, + reducing the risk of major breakages. +- **Reliability**: The test suite should be reliable. No developer enjoys + debugging a failing test only to find out it was poorly written and failures + are not related to any problem in the tested code. Flaky tests reduce the + trust in your tests, up to point where you learn to ignore these failures, + which will eventually lead to a situation when a test failed legitimately + because of a bug in the application, but you did not notice. +- **Isolation of failures**: The test suite should make it easy to isolate the + source of test failures. To fix a failing test, developers need to find the + specific place that does not work as expected. When the project contains + thousands of lines and the test failure can be caused by any part of the + system, then finding the bug is very difficult, time consuming and + demotivating. +- **Resilience**: The test implementation should be robust and resilient to + changes in the tested code. As the project grows and matures, you may need to + change existing behavior. With a brittle test suite, each change may break + dozens of tests, for example when you have many end-to-end/UI tests that rely + on specific UI layout. This makes change prohibitively expensive, up to a + point where you may start questioning the value of such test suite. References: -- [Test Pyramid](https://martinfowler.com/bliki/TestPyramid.html) by Martin Fowler -- [The testing pyramid](http://www.agilenutshell.com/episodes/41-testing-pyramid) by Jonathan Rasmusson +- [Test Pyramid](https://martinfowler.com/bliki/TestPyramid.html) by Martin + Fowler +- [The testing pyramid](http://www.agilenutshell.com/episodes/41-testing-pyramid) + by Jonathan Rasmusson - [Just say no to more end-to-end tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html) - [100,000 e2e selenium tests? Sounds like a nightmare!](https://watirmelon.blog/2014/05/14/100000-e2e-selenium-tests-sounds-like-a-nightmare/) - [Growing Object-Oriented Software Guided by Tests](http://www.growing-object-oriented-software.com/) ### How to build a great test suite -To create a great test suite, think smaller and favor fast, focused unit-tests over slow application-wide end-to-end tests. +To create a great test suite, think smaller and favor fast, focused unit-tests +over slow application-wide end-to-end tests. -Say you are implementing the "search" endpoint of the Product resource described earlier. You might write the following tests: +Say you are implementing the "search" endpoint of the Product resource described +earlier. You might write the following tests: - 1. One "acceptance test", where you start the application, make an HTTP request to search for a given product name, and verify that expected products were returned. This verifies that all parts of the application are correctly wired together. +1. One "acceptance test", where you start the application, make an HTTP request + to search for a given product name, and verify that expected products were + returned. This verifies that all parts of the application are correctly + wired together. - 2. Few "integration tests" where you invoke `ProductController` API from JavaScript/TypeScript, talk to a real database, and verify that the queries built by the controller work as expected when executed by the database server. +2. Few "integration tests" where you invoke `ProductController` API from + JavaScript/TypeScript, talk to a real database, and verify that the queries + built by the controller work as expected when executed by the database + server. - 3. Many "unit tests" where you test `ProductController` in isolation and verify that the controller handles all different situations, including error paths and edge cases. +3. Many "unit tests" where you test `ProductController` in isolation and verify + that the controller handles all different situations, including error paths + and edge cases. ### Testing workflow Here is what your testing workflow might look like: - 1. Write an acceptance test demonstrating the new feature you are going to build. Watch the test fail with a helpful error message. Use this new test as a reminder of what is the scope of your current work. When the new tests passes then you are done. +1. Write an acceptance test demonstrating the new feature you are going to + build. Watch the test fail with a helpful error message. Use this new test + as a reminder of what is the scope of your current work. When the new tests + passes then you are done. - 2. Think about the different ways how the new feature can be used and pick one that's most easy to implement. Consider error scenarios and edge cases that you need to handle too. In the example above, where you want to search for products by name, you may start with the case when no product is found. +2. Think about the different ways how the new feature can be used and pick one + that's most easy to implement. Consider error scenarios and edge cases that + you need to handle too. In the example above, where you want to search for + products by name, you may start with the case when no product is found. - 3. Write a unit-test for this case and watch it fail with an expected (and helpful) error message. This is the "red" step in Test Driven Development ([TDD](https://en.wikipedia.org/wiki/Test-driven_development)). +3. Write a unit-test for this case and watch it fail with an expected (and + helpful) error message. This is the "red" step in Test Driven Development + ([TDD](https://en.wikipedia.org/wiki/Test-driven_development)). - 4. Write a minimal implementation need to make your tests pass. Building up on the example above, let your search method return an empty array. This is the "green" step in TDD. +4. Write a minimal implementation need to make your tests pass. Building up on + the example above, let your search method return an empty array. This is the + "green" step in TDD. - 5. Review the code you have written so far, and refactor as needed to clean up the design. Don't forget to keep your test code clean too! This is the "refactor" step in TDD. +5. Review the code you have written so far, and refactor as needed to clean up + the design. Don't forget to keep your test code clean too! This is the + "refactor" step in TDD. - 6. Repeat the steps 2-5 until your acceptance test starts passing. +6. Repeat the steps 2-5 until your acceptance test starts passing. -When writing new unit tests, watch out for situations where your tests are asserting on how the tested objects interacted with the mocked dependencies, while making implicit assumptions about what is the correct usage of the dependencies. This may indicate that you should add an integration test in addition to a unit test. +When writing new unit tests, watch out for situations where your tests are +asserting on how the tested objects interacted with the mocked dependencies, +while making implicit assumptions about what is the correct usage of the +dependencies. This may indicate that you should add an integration test in +addition to a unit test. -For example, when writing a unit test to verify that the search endpoint is building a correct database query, you would usually assert that the controller invoked the model repository method with an expected query. While this gives us confidence about the way the controller is building queries, it does not tell us whether such queries will actually work when they are executed by the database server. An integration test is needed here. +For example, when writing a unit test to verify that the search endpoint is +building a correct database query, you would usually assert that the controller +invoked the model repository method with an expected query. While this gives us +confidence about the way the controller is building queries, it does not tell us +whether such queries will actually work when they are executed by the database +server. An integration test is needed here. To summarize: -- Pay attention to your test code. It's as important as the "real" code you are shipping to production. +- Pay attention to your test code. It's as important as the "real" code you are + shipping to production. - Prefer fast and focused unit tests over slow app-wide end-to-end tests. -- Watch out for integration points that are not covered by unit-tests and add integration tests to verify your units work well together. +- Watch out for integration points that are not covered by unit-tests and add + integration tests to verify your units work well together. -See [Testing Your Application](Testing-Your-application.md) for a reference manual on automated tests. +See [Testing Your Application](Testing-Your-application.md) for a reference +manual on automated tests. {% include next.html content= " -[Implementing features](./Implementing-features.md) -" %} +[Implementing features](./Implementing-features.md) " %} diff --git a/docs/site/Dependency-injection.md b/docs/site/Dependency-injection.md index 1413c76f66fc..ac8bebbb4937 100644 --- a/docs/site/Dependency-injection.md +++ b/docs/site/Dependency-injection.md @@ -7,41 +7,60 @@ sidebar: lb4_sidebar permalink: /doc/en/lb4/Dependency-injection.html summary: --- + ## Introduction -[Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) is a technique where the construction of dependencies of a class or function is separated from its behavior, in order to keep the code [loosely coupled](https://en.wikipedia.org/wiki/Loose_coupling). +[Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) is a +technique where the construction of dependencies of a class or function is +separated from its behavior, in order to keep the code +[loosely coupled](https://en.wikipedia.org/wiki/Loose_coupling). -For example, the Sequence Action `authenticate` supports different authentication strategies (e.g. HTTP Basic Auth, OAuth2, etc.). Instead of hard-coding some sort of a lookup table to find the right strategy instance, `authenticate` uses dependency injection to let the caller specify which strategy to use. +For example, the Sequence Action `authenticate` supports different +authentication strategies (e.g. HTTP Basic Auth, OAuth2, etc.). Instead of +hard-coding some sort of a lookup table to find the right strategy instance, +`authenticate` uses dependency injection to let the caller specify which +strategy to use. ```ts class AuthenticationProvider { - constructor( - @inject('authentication.strategy') - strategy - ) { + constructor(@inject('authentication.strategy') strategy) { this.strategy = strategy; } value() { // this is the function invoked by "authenticate()" sequence action - return async (request) => { + return async request => { const adapter = new StrategyAdapter(this.strategy); const user = await adapter.authenticate(request); return user; - } + }; } } ``` -Dependency Injection makes the code easier to extend and customize, because the dependencies can be easily rewired by the application developer. It makes the code easier to test in isolation (in a pure unit test), because the test can inject a custom version of the dependency (a mock or a stub). This is especially important when testing code interacting with external services like a database or an OAuth2 provider. Instead of making expensive network requests, the test can provide a lightweight implementation returning pre-defined responses. +Dependency Injection makes the code easier to extend and customize, because the +dependencies can be easily rewired by the application developer. It makes the +code easier to test in isolation (in a pure unit test), because the test can +inject a custom version of the dependency (a mock or a stub). This is especially +important when testing code interacting with external services like a database +or an OAuth2 provider. Instead of making expensive network requests, the test +can provide a lightweight implementation returning pre-defined responses. ## Configuring what to inject -Now that we write a class that gets the dependencies injected, you are probably wondering where are these values going to be injected from and how to configure what should be injected. This part is typically handled by an IoC Container, where IoC means [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control). +Now that we write a class that gets the dependencies injected, you are probably +wondering where are these values going to be injected from and how to configure +what should be injected. This part is typically handled by an IoC Container, +where IoC means +[Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control). -In LoopBack, we use [Context](Context.md) to keep track of all injectable dependencies. +In LoopBack, we use [Context](Context.md) to keep track of all injectable +dependencies. -There are several different ways for configuring the values to inject, the simplest options is to call `app.bind(key).to(value)`. Building on top of the example above, one can configure the app to use a Basic HTTP authentication strategy as follows: +There are several different ways for configuring the values to inject, the +simplest options is to call `app.bind(key).to(value)`. Building on top of the +example above, one can configure the app to use a Basic HTTP authentication +strategy as follows: ```ts // TypeScript example @@ -58,7 +77,9 @@ function loginUser(username, password, cb) { } ``` -However, when you want to create a binding that will instantiate a class and automatically inject required dependencies, then you need to use `.toClass()` method: +However, when you want to create a binding that will instantiate a class and +automatically inject required dependencies, then you need to use `.toClass()` +method: ```ts server.bind('authentication.provider').toClass(AuthenticationProvider); @@ -68,11 +89,18 @@ const provider = await server.get('authentication.provider'); // provider.strategy was set to the value returned by server.get('authentication.strategy') ``` -When a binding is created via `.toClass()`, [Context](Context.md) will create a new instance of the class when resolving the value of this binding, injecting constructor arguments and property values as configured via `@inject` decorator. +When a binding is created via `.toClass()`, [Context](Context.md) will create a +new instance of the class when resolving the value of this binding, injecting +constructor arguments and property values as configured via `@inject` decorator. -Note that the dependencies to be injected could be classes themselves, in which case [Context](Context.md) will recursively instantiate these classes first, resolving their dependencies as needed. +Note that the dependencies to be injected could be classes themselves, in which +case [Context](Context.md) will recursively instantiate these classes first, +resolving their dependencies as needed. -In this particular example, the class is a [Provider](Writing-Components#providers). Providers allow you to customize the way how a value is created by the Context, possibly depending on other Context values. A provider is typically bound using `.toProvider(.md)` API: +In this particular example, the class is a +[Provider](Writing-Components#providers). Providers allow you to customize the +way how a value is created by the Context, possibly depending on other Context +values. A provider is typically bound using `.toProvider(.md)` API: ```js app.bind('authentication.provider').toProvider(AuthenticationProvider); @@ -82,14 +110,20 @@ const authenticate = await app.get('authentication.provider'); // authenticate is the function returned by provider's value() method ``` -You can learn more about Providers in [Creating Components](Creating-components.md). +You can learn more about Providers in +[Creating Components](Creating-components.md). + ## Flavors of Dependency Injection LoopBack supports three kinds of dependency injection: - 1. constructor injection: the dependencies are provided as arguments of the class constructor. - 2. property injection: the dependencies are stored in instance properties after the class was constructed. - 3. method injection: the dependencies are provided as arguments of a method invocation. Please note that constructor injection is a special form of method injection to instantiate a class by calling its constructor. +1. constructor injection: the dependencies are provided as arguments of the + class constructor. +2. property injection: the dependencies are stored in instance properties after + the class was constructed. +3. method injection: the dependencies are provided as arguments of a method + invocation. Please note that constructor injection is a special form of + method injection to instantiate a class by calling its constructor. ### Constructor injection @@ -97,10 +131,7 @@ This is the most common flavor that should be your default choice. ```js class ProductController { - constructor( - @inject('repositories.Product') - repo - ) { + constructor(@inject('repositories.Product') repo) { this.repo = repo; } @@ -112,12 +143,13 @@ class ProductController { ### Property injection -Property injection is usually used for optional dependencies which are not required for the class to function or for dependencies that have a reasonable default. +Property injection is usually used for optional dependencies which are not +required for the class to function or for dependencies that have a reasonable +default. ```ts class InfoController { - @inject('logger') - private logger = ConsoleLogger(); + @inject('logger') private logger = ConsoleLogger(); status() { this.logger.info('Status endpoint accessed.'); @@ -128,12 +160,12 @@ class InfoController { ### Method injection -Method injection allows injection of dependencies at method invocation level. The parameters are decorated -with `@inject` or other variants to declare dependencies as method arguments. +Method injection allows injection of dependencies at method invocation level. +The parameters are decorated with `@inject` or other variants to declare +dependencies as method arguments. ```ts class InfoController { - greet(@inject('authentication.currentUser') user: UserProfile) { return `Hello, ${userProfile.name}`; } @@ -142,7 +174,9 @@ class InfoController { ## Optional dependencies -Sometimes the dependencies are optional. For example, the logging level for a Logger provider can have a default value if it is not set (bound to the context). +Sometimes the dependencies are optional. For example, the logging level for a +Logger provider can have a default value if it is not set (bound to the +context). To resolve an optional dependency, set `optional` flag to true: @@ -151,7 +185,8 @@ const ctx = new Context(); await ctx.get('optional-key', {optional: true}); // Return `undefined` instead of throwing an error ``` -Here is another example showing optional dependency injection using properties with default values: +Here is another example showing optional dependency injection using properties +with default values: ```ts // Optional property injection @@ -166,7 +201,8 @@ export class LoggerProvider implements Provider { } ``` -Optional dependencies can also be used with constructor and method injections. For example: +Optional dependencies can also be used with constructor and method injections. +For example: ```ts // Optional constructor injection @@ -175,7 +211,6 @@ export class LoggerProvider implements Provider { // Log writer is an optional dependency and it falls back to `logToConsole` @inject('log.writer', {optional: true}) private logWriter: LogWriterFn = logToConsole, - // Log level is an optional dependency with a default value `WARN` @inject('log.level', {optional: true}) private logLevel: string = 'WARN', @@ -187,7 +222,10 @@ export class LoggerProvider implements Provider { // Optional method injection export class MyController { // prefix is optional - greet(@inject('hello.prefix', {optional: true}) prefix: string = 'Hello') { + greet( + @inject('hello.prefix', {optional: true}) + prefix: string = 'Hello', + ) { return `${prefix}, world!`; } } @@ -195,8 +233,8 @@ export class MyController { ## Circular dependencies -LoopBack can detect circular dependencies and report the path which leads to the problem. -For example, +LoopBack can detect circular dependencies and report the path which leads to the +problem. For example, ```ts import {Context, inject} from '@loopback/context'; @@ -247,5 +285,7 @@ try { ## Additional resources - - [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) on Wikipedia - - [Dependency Inversion Principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle) on Wikipedia +- [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) on + Wikipedia +- [Dependency Inversion Principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle) + on Wikipedia diff --git a/docs/site/Download-examples.md b/docs/site/Download-examples.md index 107be4e9c29e..e1d8c1dabc63 100644 --- a/docs/site/Download-examples.md +++ b/docs/site/Download-examples.md @@ -13,7 +13,6 @@ summary: Downloads a LoopBack example project from our [GitHub monorepo](https://github.com/strongloop/loopback-next). - ```text lb4 example [options] [] ``` @@ -35,10 +34,10 @@ available examples. The tool will prompt you for: - Name of the example to download. If the name had been supplied from the -command-line, the prompt is skipped. + command-line, the prompt is skipped. ### Output The example project is downloaded to a new directory. For example, when -downloading `getting-started` example, the tool stores the files -under the newly created `loopback4-example-getting-started` directory. +downloading `getting-started` example, the tool stores the files under the newly +created `loopback4-example-getting-started` directory. diff --git a/docs/site/Examples-and-tutorials.md b/docs/site/Examples-and-tutorials.md index 24d2f5577558..74ff2e5f89ba 100644 --- a/docs/site/Examples-and-tutorials.md +++ b/docs/site/Examples-and-tutorials.md @@ -25,9 +25,8 @@ LoopBack 4 comes with the following example projects: You can download any of the example projects usig our CLI tool `lb4`: - -``` -$ lb4 example +```sh +lb4 example ? What example would you like to clone? (Use arrow keys) ❯ getting-started: An application and tutorial on how to build with LoopBack 4. hello-world: A simple hello-world Application using LoopBack 4 @@ -35,4 +34,6 @@ $ lb4 example rpc-server: A basic RPC server using a made-up protocol. ``` -Please follow the instructions in [Install LoopBack4 CLI](Getting-started.md#install-loopback-4-cli) if you don't have `lb4` installed yet. +Please follow the instructions in +[Install LoopBack4 CLI](Getting-started.md#install-loopback-4-cli) if you don't +have `lb4` installed yet. diff --git a/docs/site/Extending-LoopBack-4.md b/docs/site/Extending-LoopBack-4.md index d801e24d49dc..3ca4acd5bf8f 100644 --- a/docs/site/Extending-LoopBack-4.md +++ b/docs/site/Extending-LoopBack-4.md @@ -10,27 +10,42 @@ summary: ## Overview -LoopBack 4 is designed to be highly extensible. For architectural rationale and motivation, see [Crafting LoopBack 4](Crafting-LoopBack-4.md). +LoopBack 4 is designed to be highly extensible. For architectural rationale and +motivation, see [Crafting LoopBack 4](Crafting-LoopBack-4.md). ## Building blocks for extensibility -The [@loopback/context](https://github.com/strongloop/loopback-next/tree/master/packages/context) module implements an [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control) (IoC) container called [Context](Context.md) as a service registry that supports [Dependency injection](Dependency-injection.md). +The +[@loopback/context](https://github.com/strongloop/loopback-next/tree/master/packages/context) +module implements an +[Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control) (IoC) +container called [Context](Context.md) as a service registry that supports +[Dependency injection](Dependency-injection.md). -The IoC container decouples service providers and consumers. A service provider can be bound to the context with a key, which can be treated as an address of the service provider. +The IoC container decouples service providers and consumers. A service provider +can be bound to the context with a key, which can be treated as an address of +the service provider. The diagram below shows how the Context manages services and their dependencies. ![loopback-ioc](./imgs/loopback-ioc.png) -In the example above, there are three services in the Context and each of them are bound to a unique key. +In the example above, there are three services in the Context and each of them +are bound to a unique key. -- *controllers.UserController*: A controller to implement user management APIs -- *repositories.UserRepository*: A repository to provide persistence for user records -- *utilities.PasswordHasher*: A utility function to hash passwords +- _controllers.UserController_: A controller to implement user management APIs +- _repositories.UserRepository_: A repository to provide persistence for user + records +- _utilities.PasswordHasher_: A utility function to hash passwords -Please also note that `UserController` depends on an instance of `UserRepository` and `PasswordHasher`. Such dependencies are also managed by the Context to provide composition capability for service instances. +Please also note that `UserController` depends on an instance of +`UserRepository` and `PasswordHasher`. Such dependencies are also managed by the +Context to provide composition capability for service instances. -Service consumers can then either locate the provider using the binding key or declare a dependency using `@inject('binding-key-of-a-service-provider')` so that the service provider can be injected into the consumer class. The code snippet below shows the usage of `@inject` for dependency injection. +Service consumers can then either locate the provider using the binding key or +declare a dependency using `@inject('binding-key-of-a-service-provider')` so +that the service provider can be injected into the consumer class. The code +snippet below shows the usage of `@inject` for dependency injection. ```ts import {inject, Context} from '@loopback/context'; @@ -69,15 +84,25 @@ const userController: UserController = await ctx.get('controller const ok = await userController.login('John', 'MyPassWord'); ``` -Now you might wonder why the IoC container is fundamental to extensibility. Here's how it's achieved. +Now you might wonder why the IoC container is fundamental to extensibility. +Here's how it's achieved. 1. An alternative implementation of the service provider can be bound the context to replace the existing one. For example, we can implement different hashing functions for password encryption. The user management system can then receive custom password hashing functions. -2. Services can be organized as extension points and extensions. For example, to allow multiple authentication strategies, the `authentication` component can define an extension point as `authentication-manager` and various authentication strategies such as user/password, LDAP, oAuth2 can be contributed to the extension point as extensions. The relation will look like: +2. Services can be organized as extension points and extensions. For example, + to allow multiple authentication strategies, the `authentication` component + can define an extension point as `authentication-manager` and various + authentication strategies such as user/password, LDAP, oAuth2 can be + contributed to the extension point as extensions. The relation will look + like: ![loopback-extension](./imgs/loopback-extension.png) -To allow a list of extensions to be contributed to LoopBack framework and applications, we introduce `Component` as the packaging model to bundle extensions. A component is either a npm module or a local folder structure that contains one or more extensions. It's then exported as a class implementing the `Component` interface. For example: +To allow a list of extensions to be contributed to LoopBack framework and +applications, we introduce `Component` as the packaging model to bundle +extensions. A component is either a npm module or a local folder structure that +contains one or more extensions. It's then exported as a class implementing the +`Component` interface. For example: ```ts ... @@ -94,7 +119,8 @@ export class UserManagementComponent implements Component { } ``` -The interaction between the application context and `UserManagement` component is illustrated below: +The interaction between the application context and `UserManagement` component +is illustrated below: ![loopback-component](./imgs/loopback-component.png) @@ -115,11 +141,13 @@ For more information about components, see: - Models - Mixins -For a list of candidate extensions, see [loopback-next issue #512](https://github.com/strongloop/loopback-next/issues/512). +For a list of candidate extensions, see +[loopback-next issue #512](https://github.com/strongloop/loopback-next/issues/512). ### System vs Application extensions -Some extensions are meant to extend the programming model and integration capability of the LoopBack 4 framework. Good examples of such extensions are: +Some extensions are meant to extend the programming model and integration +capability of the LoopBack 4 framework. Good examples of such extensions are: - Binding providers - Decorators @@ -128,7 +156,8 @@ Some extensions are meant to extend the programming model and integration capabi - Utility functions - Mixins (for application) -An application may consist of multiple components for the business logic. For example, an online shopping application typically has the following component: +An application may consist of multiple components for the business logic. For +example, an online shopping application typically has the following component: - UserManagement - ShoppingCart diff --git a/docs/site/Extension-generator.md b/docs/site/Extension-generator.md index f5a556f22e47..883730a86e2d 100644 --- a/docs/site/Extension-generator.md +++ b/docs/site/Extension-generator.md @@ -12,53 +12,49 @@ summary: Creates a new LoopBack4 extension. -``` +```sh lb4 extension [options] [] ``` ### Options -`--description` -: Description of the extension. +`--description` : Description of the extension. -`--outDir` -: Project root directory for the extension. +`--outDir` : Project root directory for the extension. -`--tslint` -: Add TSLint to LoopBack4 extension project. +`--tslint` : Add TSLint to LoopBack4 extension project. -`--prettier` -: Add Prettier to LoopBack4 extension project. +`--prettier` : Add Prettier to LoopBack4 extension project. -`--mocha` -: Add Mocha to LoopBack4 extension project. +`--mocha` : Add Mocha to LoopBack4 extension project. -`--loopbackBuild` -: Add @loopback/build module's script set to LoopBack4 extension project. +`--loopbackBuild` : Add @loopback/build module's script set to LoopBack4 +extension project. {% include_relative includes/CLI-std-options.md %} ### Arguments -`` - Optional name of the extension given as an argument to the command.  +`` - Optional name of the extension given as an argument to the command. If provided, the tool will use that as the default when prompting for the name. ### Interactive Prompts The tool will prompt you for: -- Name of the extension as will be shown in `package.json`. -If the name had been supplied from the command-line, the prompt is skipped and the extension is built with the name from the command-line argument. -Must follow npm naming conventions. +- Name of the extension as will be shown in `package.json`. If the name had been + supplied from the command-line, the prompt is skipped and the extension is + built with the name from the command-line argument. Must follow npm naming + conventions. - Description of the extension as will be shown in `package.json`. -- Name of the directory in which to create your extension. -Defaults to the name of the extension previously entered. +- Name of the directory in which to create your extension. Defaults to the name + of the extension previously entered. -- Optional modules to add to the extension. These modules are helpful tools to help format, test, and build a LoopBack4 extension. -Defaults to `true` for all of the modules. -The prompted modules are: +- Optional modules to add to the extension. These modules are helpful tools to + help format, test, and build a LoopBack4 extension. Defaults to `true` for all + of the modules. The prompted modules are: - [`tslint`](https://www.npmjs.com/package/tslint) - [`prettier`](https://www.npmjs.com/package/prettier) diff --git a/docs/site/FAQ.md b/docs/site/FAQ.md index 8e18c545d559..adad099fcf4c 100644 --- a/docs/site/FAQ.md +++ b/docs/site/FAQ.md @@ -5,9 +5,9 @@ keywords: LoopBack 4.0, LoopBack 4 tags: sidebar: lb4_sidebar permalink: /doc/en/lb4/FAQ.html -summary: LoopBack 4 is a completely new framework, sometimes referred to as LoopBack-Next. - +summary: LoopBack 4 is a completely new framework, also known as LoopBack-Next. --- + ### What’s the vision behind LoopBack 4? - Make it even easier to build apps that require complex integrations @@ -20,7 +20,8 @@ See [Crafting LoopBack 4](Crafting-LoopBack-4.md) for more details. ### What’s the timeline for LoopBack 4? -See [Upcoming releases](https://github.com/strongloop/loopback-next/wiki/Upcoming-Releases). +See +[Upcoming releases](https://github.com/strongloop/loopback-next/wiki/Upcoming-Releases). ### Where are the tutorials? @@ -30,9 +31,12 @@ See [Examples and tutorials](Examples-and-tutorials.md). - 100% promise-based APIs and async and await as first-class keywords. - Being able to choose to use JavaScript or TypeScript. -- Better extensibility, ability to override any aspect of the framework (for example, no more built-in User - model pain, easily replace parts of ACL with your own). +- Better extensibility, ability to override any aspect of the framework (for + example, no more built-in User - model pain, easily replace parts of ACL with + your own). - Define APIs / remote methods with OpenAPI (Swagger). -- Organize business and other logic into controllers with their own opinionated API or generate an PersistedModel style API. +- Organize business and other logic into controllers with their own opinionated + API or generate an PersistedModel style API. - Better routing performance - React SDK - Create GraphQL based APIs @@ -40,28 +44,52 @@ See [Examples and tutorials](Examples-and-tutorials.md). - Advanced declarative caching support - New DSL for defining APIs / Models - Completely new tooling w/ Visual Studio Code integration -- More at [Feature proposals](https://github.com/strongloop/loopback-next/wiki/Feature-proposals) +- More at + [Feature proposals](https://github.com/strongloop/loopback-next/wiki/Feature-proposals) -Add your feature requests at [loopback-next/issues/new](https://github.com/strongloop/loopback-next/issues/new). +Add your feature requests at +[loopback-next/issues/new](https://github.com/strongloop/loopback-next/issues/new). ### Why TypeScript? -Although developers can still write application logic in either JavaScript or TypeScript, LoopBack 4's core is written in TypeScript, for the following reasons: - -- **Improved developer productivity and scalability**. Our customers need a framework that scales to dozens and even hundreds of developers. This scalability is the reason TypeScript exists and is gaining traction. -- **Improved extensibility** and flexibility. LoopBack 4's core is simpler than LoopBack 3.x with well-defined extension points. A lot of responsibility will be shifted to extensions (componnets), which can be JavaScript or TypeScript. -- Unified tooling. TypeScript developers all use the same IDE: Visual Studio Code. The LoopBack ecosystem could someday be filled with useful best practices around that IDE and even great developer plugins. Right now that effort is split between various editors and basically non-existent. -- **Future-proofing**. Ability to leverage the latest and future JavaScript constructs. - -TypeScript's support for static analysis makes more robust tooling possible and is the foundation for its scalability. The ability to easily refactor code without the common human-introduced errors. Dev and Compile time checking. For example, most people don't have the same expertise and time we do to setup complex linting solutions (for example, a linting config that works across many projects). - -For more details, see the lengthy discussion in [#6](https://github.com/strongloop/loopback-next/issues/6). +Although developers can still write application logic in either JavaScript or +TypeScript, LoopBack 4's core is written in TypeScript, for the following +reasons: + +- **Improved developer productivity and scalability**. Our customers need a + framework that scales to dozens and even hundreds of developers. This + scalability is the reason TypeScript exists and is gaining traction. +- **Improved extensibility** and flexibility. LoopBack 4's core is simpler than + LoopBack 3.x with well-defined extension points. A lot of responsibility will + be shifted to extensions (componnets), which can be JavaScript or TypeScript. +- Unified tooling. TypeScript developers all use the same IDE: Visual Studio + Code. The LoopBack ecosystem could someday be filled with useful best + practices around that IDE and even great developer plugins. Right now that + effort is split between various editors and basically non-existent. +- **Future-proofing**. Ability to leverage the latest and future JavaScript + constructs. + +TypeScript's support for static analysis makes more robust tooling possible and +is the foundation for its scalability. The ability to easily refactor code +without the common human-introduced errors. Dev and Compile time checking. For +example, most people don't have the same expertise and time we do to setup +complex linting solutions (for example, a linting config that works across many +projects). + +For more details, see the lengthy discussion in +[#6](https://github.com/strongloop/loopback-next/issues/6). ### Does JavaScript still work? -LoopBack 4 itself is written in [TypeScript](https://www.typescriptlang.org) (that compiles to JavaScript), but it supports applications written in both TypeScript and JavaScript. The documentation assumes you have a basic understanding of the JavaScript language; and when it says "JavaScript" it is understood to mean ECMAScript version 6 (ES6). +LoopBack 4 itself is written in [TypeScript](https://www.typescriptlang.org) +(that compiles to JavaScript), but it supports applications written in both +TypeScript and JavaScript. The documentation assumes you have a basic +understanding of the JavaScript language; and when it says "JavaScript" it is +understood to mean ECMAScript version 6 (ES6). -Some of the examples use ES6 syntax. We encourage you to get familiar with ES6 constructs such as arrow functions, classes, template literals, let, and const statements. +Some of the examples use ES6 syntax. We encourage you to get familiar with ES6 +constructs such as arrow functions, classes, template literals, let, and const +statements. ### LoopBack 3 vs LoopBack 4 diff --git a/docs/site/Getting-started.md b/docs/site/Getting-started.md index 811bb911be59..f2eafc019a6e 100644 --- a/docs/site/Getting-started.md +++ b/docs/site/Getting-started.md @@ -5,8 +5,9 @@ keywords: LoopBack 4.0, LoopBack 4 tags: sidebar: lb4_sidebar permalink: /doc/en/lb4/Getting-started.html -summary: Write and run a LoopBack 4 "Hello World" project in JavaScript and TypeScript. +summary: Write and run a LoopBack 4 "Hello World" project in TypeScript. --- + ## Prerequisites Install [Node.js](https://nodejs.org/en/download/) (version 8.x.x or higher) if @@ -19,6 +20,7 @@ extension with more features under development. CLI provides the fastest way to get started with a LoopBack 4 project that adheres to best practices. Install the CLI globally by running + ```sh npm i -g @loopback/cli ``` @@ -28,11 +30,13 @@ npm i -g @loopback/cli The CLI tool will scaffold the project, configure TypeScript compiler and install all the required dependencies. To create a new project, run the CLI as follows and answer the prompts. + ```sh lb4 app ``` Answer the prompts as follows: + ```sh ? Project name: getting-started ? Project description: Getting started tutorial @@ -43,22 +47,26 @@ Answer the prompts as follows: ### Starting the project -The project comes with a "ping" route to test the project. Let's try it out by running the project. +The project comes with a "ping" route to test the project. Let's try it out by +running the project. + ```sh cd getting-started npm start ``` -In a browser, visit [http://127.0.0.1:3000/ping](http://127.0.0.1:3000/ping). +In a browser, visit . ## Adding your own controller -Now that we have a basic project created, it's time to add our own [controller](Controllers.md). -Let's add a simple "Hello World" controller as follows: +Now that we have a basic project created, it's time to add our own +[controller](Controllers.md). Let's add a simple "Hello World" controller as +follows: + +- Create a new file in `/src/controllers/` called `hello.controller.ts`. -* Create a new file in `/src/controllers/` called `hello.controller.ts`. +- Paste the following contents into the file: -* Paste the following contents into the file: ```ts import {get} from '@loopback/rest'; @@ -70,7 +78,9 @@ Let's add a simple "Hello World" controller as follows: } ``` -* Start the application using `npm start`. - * *Note: If your application is still running, press **CTRL+C** to stop it before restarting it* +- Start the application using `npm start`. + + - _Note: If your application is still running, press **CTRL+C** to stop it + before restarting it_ -* Visit [http://127.0.0.1:3000/hello](http://127.0.0.1:3000/hello) to see `Hello world!` +- Visit to see `Hello world!` diff --git a/docs/site/Glossary.md b/docs/site/Glossary.md index 1ac07c6c6c24..94ad6439ba2c 100644 --- a/docs/site/Glossary.md +++ b/docs/site/Glossary.md @@ -1,27 +1,41 @@ -**Action**: JavaScript functions that only accept or return Elements. Since the input of one action (an Element) is the output of another action (Element) they are easily composed. +**Action**: JavaScript functions that only accept or return Elements. Since the +input of one action (an Element) is the output of another action (Element) they +are easily composed. -**API specification**: An [OpenAPI](https://www.openapis.org) document (in YAML or JSON format) that describes a REST API. It specifies the metadata (verbs, paths, headers, and so on) a client needs to make a valid request to the API. +**API specification**: An [OpenAPI](https://www.openapis.org) document (in YAML +or JSON format) that describes a REST API. It specifies the metadata (verbs, +paths, headers, and so on) a client needs to make a valid request to the API. **Application**: A container of components. -**Component**: A reusable bundle of Bindings, [Controllers](Controllers.md), Services, [Repositories](Repositories.md), and models. For more information, see [Using components](Using-components.md) and [Creating components](Creating-components.md). +**Component**: A reusable bundle of Bindings, [Controllers](Controllers.md), +Services, [Repositories](Repositories.md), and models. For more information, see +[Using components](Using-components.md) and +[Creating components](Creating-components.md). -**Connector**: An interface that abstracts underlying backend systems (for example, database, web service, and so on). +**Connector**: An interface that abstracts underlying backend systems (for +example, database, web service, and so on). -**Context**: An encapsulation of request and response objects provides useful values for writing web applications and APIs. For more information, see [Context](Context.md). +**Context**: An encapsulation of request and response objects provides useful +values for writing web applications and APIs. For more information, see +[Context](Context.md). **Controller**: The implementation of API endpoints. -**DataSource**: A named configuration for a Connector instance that represents data in an external system. +**DataSource**: A named configuration for a Connector instance that represents +data in an external system. -**Element:** The building blocks of a Sequence, such as route, params, and result. For more information, see [Sequence](Sequence.md#elements). +**Element:** The building blocks of a Sequence, such as route, params, and +result. For more information, see [Sequence](Sequence.md#elements). **Mixin**: An interface for models. **Model**: Defines application data and how it is connected to other data. -**Sequence**: A stateless grouping of actions that control how an Application responds to requests. +**Sequence**: A stateless grouping of actions that control how an Application +responds to requests. **Service**: Operations implemented in an external system. -**Repository**: A type of Service that represents a collection of data within a DataSource. For more information, see [Repositories](Repositories.md). +**Repository**: A type of Service that represents a collection of data within a +DataSource. For more information, see [Repositories](Repositories.md). diff --git a/docs/site/Implementing-features.md b/docs/site/Implementing-features.md index c84a482a4aea..e31f543139de 100644 --- a/docs/site/Implementing-features.md +++ b/docs/site/Implementing-features.md @@ -8,27 +8,33 @@ permalink: /doc/en/lb4/Implementing-features.html summary: --- -{% include previous.html content=" -This article continues from [Defining your testing stategy(./Defining-your-testing-strategy.md). -" %} +{% include previous.html content=" This article continues from \[Defining your +testing stategy(./Defining-your-testing-strategy.md). " %} ## Incrementally implement features To recapitulate the status of your new project: - - You have defined your application's API and described it in an OpenAPI Spec document. - - You have empty controllers backing your new operations. - - Our project has a test verifying the validity of your API spec. This test passes. - - Our test suite includes a smoke test to verify conformance of your implementation - with the API spec. These checks are all failing now. +- You have defined your application's API and described it in an OpenAPI Spec + document. +- You have empty controllers backing your new operations. +- Our project has a test verifying the validity of your API spec. This test + passes. +- Our test suite includes a smoke test to verify conformance of your + implementation with the API spec. These checks are all failing now. -Now it's time to put your testing strategy outlined in the previous section into practice. Pick one of the new API operations, preferably the one that's easiest to implement, and get to work! +Now it's time to put your testing strategy outlined in the previous section into +practice. Pick one of the new API operations, preferably the one that's easiest +to implement, and get to work! Start with `GET /product/{slug}` in this guide. ### Write an acceptance test -One "acceptance test", where you start the application, make an HTTP request to search for a given product name, and verify that expected products were returned. This verifies that all parts of your application are correctly wired together. +One "acceptance test", where you start the application, make an HTTP request to +search for a given product name, and verify that expected products were +returned. This verifies that all parts of your application are correctly wired +together. Create `test/acceptance/product.acceptance.ts` with the following contents: @@ -58,7 +64,7 @@ describe('Product (acceptance)', () => { }); // act - const response = await request.get('/product/ink-pen') + const response = await request.get('/product/ink-pen'); // assert expect(response.body).to.deepEqual({ @@ -95,28 +101,37 @@ describe('Product (acceptance)', () => { }); ``` -Notice there are few missing pieces annotated with TODO comments. Well come back to them very soon. Remember, when practicing TDD in small steps, -the goal is to add as little test code as needed to uncover a missing piece in the production code, and then add just enough production code to -make your new test pass (while keeping all existing tests passing too). +Notice there are few missing pieces annotated with TODO comments. Well come back +to them very soon. Remember, when practicing TDD in small steps, the goal is to +add as little test code as needed to uncover a missing piece in the production +code, and then add just enough production code to make your new test pass (while +keeping all existing tests passing too). Run the tests and watch the new test fail: -``` +```text 1) Product (acceptance) retrieves product details: Error: expected 200 "OK", got 404 "Not Found" ``` -When you scroll up in the test output, you will see more information about the 404 error: +When you scroll up in the test output, you will see more information about the +404 error: -``` +```text Unhandled error in GET /product/ink-pen: 404 Error: Controller method not found: ProductController.getDetails ``` -Learn more about acceptance testing in [Test your individual REST API endpoints](./Testing-your-application.md#test-your-individual-rest-api-endpoints) from [Testing your application](./Testing-your-application.md). +Learn more about acceptance testing in +[Test your individual REST API endpoints](./Testing-your-application.md#test-your-individual-rest-api-endpoints) +from [Testing your application](./Testing-your-application.md). ### Write a unit-test for the new controller method -The new acceptance test is failing because there is no `getDetails` method implemented by `ProductController`. Start with a unit-test to drive the implementation of this new method. Please refer to [Unit-test your Controllers](./Testing-your-application.md#unit-test-your-controllers) for more details. +The new acceptance test is failing because there is no `getDetails` method +implemented by `ProductController`. Start with a unit-test to drive the +implementation of this new method. Please refer to +[Unit-test your Controllers](./Testing-your-application.md#unit-test-your-controllers) +for more details. Create `tests/unit/product-controller.test.ts` with the following contents: @@ -138,18 +153,22 @@ describe('ProductController', () => { }); ``` -This test is clearly not describing a final solution, for example there is no Product model and repository involved. However, it's a good first increment that drives enough of the initial controller implementation. -This shows the power of unit testing - you can test this new controller method in isolation, independent from the other moving parts of the -application, even before those other parts are implemented! +This test is clearly not describing a final solution, for example there is no +Product model and repository involved. However, it's a good first increment that +drives enough of the initial controller implementation. This shows the power of +unit testing - you can test this new controller method in isolation, independent +from the other moving parts of the application, even before those other parts +are implemented! Run `npm test` and watch the test fail with a helpful error message: -``` +```text TSError: ⨯ Unable to compile TypeScript test/unit/product-controller.test.ts (13,40): Property 'getDetails' does not exist on type 'ProductController'. (2339) ``` -Now it's time to write the first implementation of the `getDetails` method. Modify the file `src/controllers/product-controller.ts` as follows: +Now it's time to write the first implementation of the `getDetails` method. +Modify the file `src/controllers/product-controller.ts` as follows: ```ts export class ProductController { @@ -162,14 +181,19 @@ export class ProductController { } ``` -Run `npm test` to see your new test pass. Notice that the Dredd-powered test of `/product/{slug}` is passing too, and your acceptance test is failing -with a different error now - the response does not contain all expected product properties. +Run `npm test` to see your new test pass. Notice that the Dredd-powered test of +`/product/{slug}` is passing too, and your acceptance test is failing with a +different error now - the response does not contain all expected product +properties. -While it's possible to further iterate by adding more unit tests, a more valuable next step is to write an integration test to verify that your new API is using data from your backing database. +While it's possible to further iterate by adding more unit tests, a more +valuable next step is to write an integration test to verify that your new API +is using data from your backing database. ### Write an integration test for the new controller method -Create `tests/integration/product-controller.integration.ts` with the following contents: +Create `tests/integration/product-controller.integration.ts` with the following +contents: ```ts import {ProductController} from '../..'; @@ -211,7 +235,7 @@ describe('ProductController (integration)', () => { Run `npm test` to see the new test fail. -``` +```text AssertionError: expected Object { name: 'Ink Pen', slug: 'ink-pen' } to equal Object { name: 'Pencil', slug: 'pencil', id: 1 } (at name, Ahas 'Ink Pen' and B has 'Pencil') + expected - actual @@ -224,73 +248,89 @@ AssertionError: expected Object { name: 'Ink Pen', slug: 'ink-pen' } to equal Ob } ``` -Please refer to [Test your Controllers and Repositories together](./Testing-your-application.md#test-your-controllers-and-repositories-together) to learn more about integration testing. +Please refer to +[Test your Controllers and Repositories together](./Testing-your-application.md#test-your-controllers-and-repositories-together) +to learn more about integration testing. -Take a closer look at the new test. To make it fail with the current implementation, you need to find a different scenario compared to what is covered by the unit test. You could simply change the data, but that would add little value to the test suite. Instead, take this opportunity to cover another requirement of "get product details" operation: it should return the details of the product that matches the "slug" parameter passed in the arguments. +Take a closer look at the new test. To make it fail with the current +implementation, you need to find a different scenario compared to what is +covered by the unit test. You could simply change the data, but that would add +little value to the test suite. Instead, take this opportunity to cover another +requirement of "get product details" operation: it should return the details of +the product that matches the "slug" parameter passed in the arguments. -The next step is bigger than is usual in an incremental TDD workflow. You need to connect to the database and define classes to work with the data. +The next step is bigger than is usual in an incremental TDD workflow. You need +to connect to the database and define classes to work with the data. ### Define Product model, repository, and data source -LoopBack is agnostic when it comes to accessing databases. You can choose any package from the npm module ecosystem. On the other hand, we also want LoopBack users to have a recommended solution that's covered by technical support. Welcome to `@loopback/repository`, a TypeScript facade for the `loopback-datasource-juggler` implementation in LoopBack 3.x. +LoopBack is agnostic when it comes to accessing databases. You can choose any +package from the npm module ecosystem. On the other hand, we also want LoopBack +users to have a recommended solution that's covered by technical support. +Welcome to `@loopback/repository`, a TypeScript facade for the +`loopback-datasource-juggler` implementation in LoopBack 3.x. - 1. Define `Product` model in `src/models/product.model.ts` +1. Define `Product` model in `src/models/product.model.ts` - ```ts - import {Entity, model, property} from '@loopback/repository'; + ```ts + import {Entity, model, property} from '@loopback/repository'; - @model() - export class Product extends Entity { - @property({ - description: 'The unique identifier for a product', - id: true, - }) - id: number; + @model() + export class Product extends Entity { + @property({ + description: 'The unique identifier for a product', + id: true, + }) + id: number; - @property({required: true}) - name: string; + @property({required: true}) + name: string; - @property({required: true}) - slug: string; + @property({required: true}) + slug: string; - @property({required: true}) - price: number; + @property({required: true}) + price: number; - // Add the remaining properties yourself: - // description, available, category, label, endData - } - ``` + // Add the remaining properties yourself: + // description, available, category, label, endData + } + ``` - **TODO(bajtos) Find out how to re-use ProductBaseSchema for the model definition** + **TODO(bajtos) Find out how to re-use ProductBaseSchema for the model + definition** - 2. Define a data source representing a single source of data, typically a database. This example uses in-memory storage. In real applications, replace the `memory` connector with the actual database connector (`postgresql`, `mongodb`, etc.). +2. Define a data source representing a single source of data, typically a + database. This example uses in-memory storage. In real applications, replace + the `memory` connector with the actual database connector (`postgresql`, + `mongodb`, etc.). - Create `src/datasources/db.datasource.ts` with the following content: + Create `src/datasources/db.datasource.ts` with the following content: - ```ts - import {juggler, DataSourceConstructor} from '@loopback/repository'; + ```ts + import {juggler, DataSourceConstructor} from '@loopback/repository'; - export const db = new DataSourceConstructor({ - connector: 'memory', - }); - ``` - - 3. Define `ProductRepository` in `src/repositories/product.repository.ts` - - ```ts - import {DefaultCrudRepository, DataSourceType} from '@loopback/repository'; - import {Product} from '../models/product.model'; - import {db} from '../datasources/db.datasource'; - - export class ProductRepository extends DefaultCrudRepository< - Product, - typeof Product.prototype.id - > { - constructor() { - super(Product, db); - } - } - ``` + export const db = new DataSourceConstructor({ + connector: 'memory', + }); + ``` + +3. Define `ProductRepository` in `src/repositories/product.repository.ts` + + ```ts + import {DefaultCrudRepository, DataSourceType} from '@loopback/repository'; + import {Product} from '../models/product.model'; + import {db} from '../datasources/db.datasource'; + + export class ProductRepository extends DefaultCrudRepository< + Product, + typeof Product.prototype.id + > { + constructor() { + super(Product, db); + } + } + ``` See [Repositories](Repositories.md) for more details on this topic. @@ -304,25 +344,43 @@ async function givenEmptyDatabase() { } async function givenProduct(data: Partial) { - return await new ProductRepository().create(Object.assign({ - name: 'a-product-name', - slug: 'a-product-slug', - price: 1, - description: 'a-product-description', - available: true, - }, data)); + return await new ProductRepository().create( + Object.assign( + { + name: 'a-product-name', + slug: 'a-product-slug', + price: 1, + description: 'a-product-description', + available: true, + }, + data, + ), + ); } ``` -Notice that `givenProduct` is filling in required properties with sensible defaults. This is allow tests to provide only a small subset of properties that are strictly required by the tested scenario. This is important for multiple reasons: +Notice that `givenProduct` is filling in required properties with sensible +defaults. This is allow tests to provide only a small subset of properties that +are strictly required by the tested scenario. This is important for multiple +reasons: - 1. It makes tests easier to understand, because it's immediately clear what model properties are relevant to the test. If the test was setting all required properties, it would be difficult to tell whether some of those properties may be actually relevant to the tested scenario. +1. It makes tests easier to understand, because it's immediately clear what + model properties are relevant to the test. If the test was setting all + required properties, it would be difficult to tell whether some of those + properties may be actually relevant to the tested scenario. - 2. It makes tests easier to maintain. As the data model evolves, you will eventually need to add more required properties. If the tests were building product instances manually, you would have to fix all tests to set the new required property. With a shared helper, there is only a single place where to add a value for the new required property. +2. It makes tests easier to maintain. As the data model evolves, you will + eventually need to add more required properties. If the tests were building + product instances manually, you would have to fix all tests to set the new + required property. With a shared helper, there is only a single place where + to add a value for the new required property. -You can learn more about test data builders in [Use test data builders](./Testing-your-application.md#use-test-data-builders) section of [Testing your application](./Testing-your-application.md). +You can learn more about test data builders in +[Use test data builders](./Testing-your-application.md#use-test-data-builders) +section of [Testing your application](./Testing-your-application.md). -Now that the tests are setting up the test data correctly, it's time to rework `ProductController` to make the tests pass again. +Now that the tests are setting up the test data correctly, it's time to rework +`ProductController` to make the tests pass again. ```ts import {ProductRepository} from '../repositories/product.repository'; @@ -341,25 +399,44 @@ export class ProductController { ### Run tests -Run the tests again. These results may surprise you: +Run the tests again. These results may surprise you: - 1. The acceptance test is failing: the response contains some expected properties (slug, name), - but is missing most of other properties. +1. The acceptance test is failing: the response contains some expected + properties (slug, name), but is missing most of other properties. - 2. The API smoke test is failing with a cryptic error. +2. The API smoke test is failing with a cryptic error. - 3. The unit test is passing, despite the fact that it's not setting up any data at all! +3. The unit test is passing, despite the fact that it's not setting up any data + at all! -Examine the acceptance test first. A quick review of the source code should tell us what's the problem - the test is relying on `givenEmptyDatabase` and `givenProduct` helpers, but these helpers are not fully implemented yet. Fix that by reusing the helpers from the integration test: Move the helpers to `test/helpers/database.helpers.ts` and update both the acceptance and the integration tests to import the helpers from there. +Examine the acceptance test first. A quick review of the source code should tell +us what's the problem - the test is relying on `givenEmptyDatabase` and +`givenProduct` helpers, but these helpers are not fully implemented yet. Fix +that by reusing the helpers from the integration test: Move the helpers to +`test/helpers/database.helpers.ts` and update both the acceptance and the +integration tests to import the helpers from there. -To find out why the API smoke test is failing, you can start the application via `node .` and request the tested endpoint for example using `curl`. You will see that the server responds with 200 OK and an empty response body. This is because `getDetails` returns `undefined` when no product matching the slug was found. This can be fixed by adding a `before` hook to the smoke test. The hook should populate the database with the test data assumed by the examples in the Swagger specification by leveraging existing helpers `givenEmptyDatabase` and `givenProduct`. +To find out why the API smoke test is failing, you can start the application via +`node .` and request the tested endpoint for example using `curl`. You will see +that the server responds with 200 OK and an empty response body. This is because +`getDetails` returns `undefined` when no product matching the slug was found. +This can be fixed by adding a `before` hook to the smoke test. The hook should +populate the database with the test data assumed by the examples in the Swagger +specification by leveraging existing helpers `givenEmptyDatabase` and +`givenProduct`. -Now back to the first unit test. It may be a puzzle to figure out why the test is passing, although the answer is simple: the integration and acceptance tests are setting up data in the database, the unit test does not clear the database (because it should not use it at all!) and accidentally happen -to expect the same data as one of the other tests. +Now back to the first unit test. It may be a puzzle to figure out why the test +is passing, although the answer is simple: the integration and acceptance tests +are setting up data in the database, the unit test does not clear the database +(because it should not use it at all!) and accidentally happen to expect the +same data as one of the other tests. ### Decouple Controller from Repository -This shows us a flaw in the current design of the `ProductController` - it's difficult to test it in isolation. Fix that by making the dependency on `ModelRepository` explicit and allow controller users to provide a custom implementation. +This shows us a flaw in the current design of the `ProductController` - it's +difficult to test it in isolation. Fix that by making the dependency on +`ModelRepository` explicit and allow controller users to provide a custom +implementation. ```ts class ProductController { @@ -369,10 +446,22 @@ class ProductController { } ``` -{% include tip.html content="If you wanted to follow pure test-driven development, then you would define a minimal repository interface describing only the methods actually used by the controller. This will allow you to discover the best repository API that serves the need of the controller. However, you don't want to design a new repository API, you want to re-use the repository implementation provided by LoopBack. Therefore using the actual Repository class/interface is the right approach. -" %} - -In traditional object-oriented languages like Java or C#, in order to allow the unit tests to provide a custom implementation of the repository API, the controller needs to depend on an interface describing the API, and the repository implementation needs to implement this interface. The situation is easier in JavaScript and TypeScript. Thanks to the dynamic nature of the language, it's possible to mock/stub entire classes. The [sinon](http://sinonjs.org/) testing module, which comes bundled in `@loopback/testlab`, makes this very easy. +{% include tip.html content="If you wanted to follow pure test-driven +development, then you would define a minimal repository interface describing +only the methods actually used by the controller. This will allow you to +discover the best repository API that serves the need of the controller. +However, you don't want to design a new repository API, you want to re-use the +repository implementation provided by LoopBack. Therefore using the actual +Repository class/interface is the right approach. " %} + +In traditional object-oriented languages like Java or C#, in order to allow the +unit tests to provide a custom implementation of the repository API, the +controller needs to depend on an interface describing the API, and the +repository implementation needs to implement this interface. The situation is +easier in JavaScript and TypeScript. Thanks to the dynamic nature of the +language, it's possible to mock/stub entire classes. The +[sinon](http://sinonjs.org/) testing module, which comes bundled in +`@loopback/testlab`, makes this very easy. Here is the updated unit test leveraging dependency injection: @@ -412,19 +501,27 @@ describe('ProductController', () => { }); ``` -The new unit test is passing now, but the integration and acceptance tests are broken again! +The new unit test is passing now, but the integration and acceptance tests are +broken again! - 1. Fix the integration test by changing how the controller is created - inject `new ProductRepository()` into the repository argument. +1. Fix the integration test by changing how the controller is created - inject + `new ProductRepository()` into the repository argument. - 2. Fix the acceptance test by annotating `ProductController`'s `repository` argument with `@inject('repositories.Product')` - and binding the `ProductRepository` in the main application file where you are also binding controllers. +2. Fix the acceptance test by annotating `ProductController`'s `repository` + argument with `@inject('repositories.Product')` and binding the + `ProductRepository` in the main application file where you are also binding + controllers. -Learn more about this topic in [Unit-test your Controllers](./Testing-your-application.md#unit-test-your-controllers) -and [Use test doubles](./Testing-your-application.md#use-test-doubles) from [Testing your application](./Testing-your-application.md). +Learn more about this topic in +[Unit-test your Controllers](./Testing-your-application.md#unit-test-your-controllers) +and [Use test doubles](./Testing-your-application.md#use-test-doubles) from +[Testing your application](./Testing-your-application.md). ### Handle 'product not found' error -When you wrote the first implementation of `getDetails`, you assumed the slug always refer to an existing product, which obviously is not always true. Fix the controller to correctly handle this error situation. +When you wrote the first implementation of `getDetails`, you assumed the slug +always refer to an existing product, which obviously is not always true. Fix the +controller to correctly handle this error situation. Start with a failing unit test: @@ -463,22 +560,39 @@ export class ProductController { } ``` -More information on `HttpErrors` can be found in [Controllers](./Controllers.md#handling-errors-in-controllers) +More information on `HttpErrors` can be found in +[Controllers](./Controllers.md#handling-errors-in-controllers) ### Implement a custom Sequence -LoopBack 3.x is using Express middleware to customize the sequence of actions executed to handle an incoming request: body-parser middleware is converting the request body from JSON to a JavaScript object, strong-error-handler is creating an error response when the request failed. +LoopBack 3.x is using Express middleware to customize the sequence of actions +executed to handle an incoming request: body-parser middleware is converting the +request body from JSON to a JavaScript object, strong-error-handler is creating +an error response when the request failed. Express middleware has several shortcomings: - - It's based on callback flow control and does not support async functions returning Promises. - - The order in which middleware needs to be registered can be confusing, for example request logging middleware must be registered as the first one, despite the fact that the log is written only at the end, once the response has been sent. - - The invocation of middleware handlers is controlled by the framework, application developers have very little choices. -LoopBack 4, abandons Express/Koa-like middleware for a different approach that puts the application developer in the front seat. See [Sequence](Sequence.md) documentation to learn more about this concept. +- It's based on callback flow control and does not support async functions + returning Promises. +- The order in which middleware needs to be registered can be confusing, for + example request logging middleware must be registered as the first one, + despite the fact that the log is written only at the end, once the response + has been sent. +- The invocation of middleware handlers is controlled by the framework, + application developers have very little choices. + +LoopBack 4, abandons Express/Koa-like middleware for a different approach that +puts the application developer in the front seat. See [Sequence](Sequence.md) +documentation to learn more about this concept. -Now you are going to modify request handling in the application to print a line in the [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format) for each request handled. +Now you are going to modify request handling in the application to print a line +in the [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format) for +each request handled. -Start by writing an acceptance test, as described in [Test sequence customizations](Testing-your-application.md#test-sequence-customizations) from [Testing your application](Testing-your-application.md). Create a new test file (e.g. `sequence.acceptance.ts`) and add the following test: +Start by writing an acceptance test, as described in +[Test sequence customizations](Testing-your-application.md#test-sequence-customizations) +from [Testing your application](Testing-your-application.md). Create a new test +file (e.g. `sequence.acceptance.ts`) and add the following test: ```ts describe('Sequence (acceptance)', () => { @@ -491,7 +605,9 @@ describe('Sequence (acceptance)', () => { it('prints a log line for each incoming request', async () => { const logs: string[] = []; const server = await app.getServer(RestServer); - server.bind('sequence.actions.commonLog').to((msg: string) => logs.push(msg)); + server + .bind('sequence.actions.commonLog') + .to((msg: string) => logs.push(msg)); const product = await givenProduct({name: 'Pen', slug: 'pen'}); await request.get('/product/pen'); @@ -505,7 +621,8 @@ describe('Sequence (acceptance)', () => { Run the test suite and watch the test fail. -In the next step, copy the default Sequence implementation to a new project file `src/server/sequence.ts`: +In the next step, copy the default Sequence implementation to a new project file +`src/server/sequence.ts`: ```ts const RestSequenceActions = RestBindings.SequenceActions; @@ -513,7 +630,8 @@ const RestSequenceActions = RestBindings.SequenceActions; export class MySequence implements SequenceHandler { constructor( @inject(RestSequenceActions.FIND_ROUTE) protected findRoute: FindRoute, - @inject(RestSequenceActions.PARSE_PARAMS) protected parseParams: ParseParams, + @inject(RestSequenceActions.PARSE_PARAMS) + protected parseParams: ParseParams, @inject(RestSequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, @inject(RestSequenceActions.SEND) protected send: Send, @inject(RestSequenceActions.REJECT) protected reject: Reject, @@ -532,9 +650,12 @@ export class MySequence implements SequenceHandler { } ``` -Register your new sequence with your `Server`, for example by calling `server.sequence(MySequence)`. Run your tests to verify that everything works the same way as before and the new acceptance test is still failing. +Register your new sequence with your `Server`, for example by calling +`server.sequence(MySequence)`. Run your tests to verify that everything works +the same way as before and the new acceptance test is still failing. -Now it's time to customize the default sequence to print a common log line. Edit the `handle` method as follows: +Now it's time to customize the default sequence to print a common log line. Edit +the `handle` method as follows: ```ts async handle(req: ParsedRequest, res: ServerResponse) { @@ -558,13 +679,17 @@ async handle(req: ParsedRequest, res: ServerResponse) { } ``` -To inject the new method `log`, add the following line to `MySequence` constructor arguments: +To inject the new method `log`, add the following line to `MySequence` +constructor arguments: ```ts @inject('sequence.actions.log') protected log: (msg: string) => void ``` -When you run the tests now, you will see that the new acceptance tests for logging passes, but some of the older acceptance tests started to fail. This is because `sequence.actions.log` is not bound in the application. Fix that by adding the following line after you've retrieved your rest server instance: +When you run the tests now, you will see that the new acceptance tests for +logging passes, but some of the older acceptance tests started to fail. This is +because `sequence.actions.log` is not bound in the application. Fix that by +adding the following line after you've retrieved your rest server instance: ```ts // assuming you've called `const server = await app.getServer(RestServer)` @@ -573,8 +698,9 @@ server.bind('sequence.actions.log').to((msg: String) => console.log(msg)); With this last change in place, your test suite should be all green again. -The next task is left as an exercise for the reader: \Modify the `catch` block to print a common log entry too. Start by writing a unit-test that invokes `MySequence` directly. +The next task is left as an exercise for the reader: \\Modify the `catch` block +to print a common log entry too. Start by writing a unit-test that invokes +`MySequence` directly. {% include next.html content= " -[Preparing the API for consumption](./Preparing-the-API-for-consumption.md) -" %} +[Preparing the API for consumption](./Preparing-the-API-for-consumption.md) " %} diff --git a/docs/site/Introduction-to-LoopBack-Next-development.md b/docs/site/Introduction-to-LoopBack-Next-development.md index bae90ae92d73..3b12eada9570 100644 --- a/docs/site/Introduction-to-LoopBack-Next-development.md +++ b/docs/site/Introduction-to-LoopBack-Next-development.md @@ -7,6 +7,8 @@ sidebar: lb4_sidebar permalink: /doc/en/lb4/Intro-to-LB4-development.html summary: --- + MOVED TO strongloop.com. -Working draft: https://github.com/strongloop-forks/strongloop.com/blob/master/_posts/2017-10-15-intro-to-lb4-for-developers.md +Working draft: + diff --git a/docs/site/Language-related-concepts.md b/docs/site/Language-related-concepts.md new file mode 100644 index 000000000000..5d7147de2da2 --- /dev/null +++ b/docs/site/Language-related-concepts.md @@ -0,0 +1,13 @@ +--- +lang: en +title: 'Language-related concepts' +keywords: LoopBack 4.0, LoopBack 4 +tags: +sidebar: lb4_sidebar +permalink: /doc/en/lb4/Language-related-concepts.html +summary: +--- + +A module that exports JavaScript/TypeScript concept related functions. + +- [**Mixin**](Mixin.md): Add properties and methods to a class. diff --git a/docs/site/LoopBack-3.x.md b/docs/site/LoopBack-3.x.md index f97ea0cb2f58..a352bad42d98 100644 --- a/docs/site/LoopBack-3.x.md +++ b/docs/site/LoopBack-3.x.md @@ -9,10 +9,10 @@ summary: --- LoopBack 4 is the next generation of the LoopBack framework, with a completely -rewritten core foundation and significantly improved programming model. -If you're an existing LoopBack user, read [Crafting LoopBack 4](Crafting-LoopBack-4.html) -to understand the motivations, strategy, and innovations behind this exciting -new version. +rewritten core foundation and significantly improved programming model. If +you're an existing LoopBack user, read +[Crafting LoopBack 4](Crafting-LoopBack-4.html) to understand the motivations, +strategy, and innovations behind this exciting new version. This article will help existing users understand LoopBack 4: @@ -24,99 +24,98 @@ This article will help existing users understand LoopBack 4: At high-level, LoopBack 3.x applications consist of three big "parts" - - Persistence layer (this includes talking to backend services like SOAP/REST) - - Outwards facing REST API - - App-wide setup - Express middleware, boot scripts, etc. +- Persistence layer (this includes talking to backend services like SOAP/REST) +- Outwards facing REST API +- App-wide setup - Express middleware, boot scripts, etc. In the persistence layer, users can contribute the following artifacts: - 1. Definitions of Model data types (properties, validations) - 2. Definition of data sources - 3. Configuration of models (which datasource are they attached to) - 4. Operation hooks +1. Definitions of Model data types (properties, validations) +2. Definition of data sources +3. Configuration of models (which datasource are they attached to) +4. Operation hooks At the public API side, users can define: - 1. Which built-in methods should be exposed (think of `disableRemoteMethodByName`) - 2. Custom remote methods - 3. before/after/afterError hooks at application-level - 4. before/after/afterError hooks at model-level - 5. before/after/afterError hooks at model method level +1. Which built-in methods should be exposed (think of + `disableRemoteMethodByName`) +2. Custom remote methods +3. before/after/afterError hooks at application-level +4. before/after/afterError hooks at model-level +5. before/after/afterError hooks at model method level LoopBack 4 was intentionally designed to allow users to choose their own ORM/persistence solution. The juggler from LoopBack 3 has been packaged into -`@loopback/repository` so that it's possible for users to reuse their -existing model definitions, migrating their application incrementally. +`@loopback/repository` so that it's possible for users to reuse their existing +model definitions, migrating their application incrementally. ## Concept/feature mapping In Loopback 3.x (and earlier), models were responsible for both accessing data in other systems (databases, SOAP services, etc.) and providing the application's external REST API. This made it easy to quickly build a REST -interface for an existing database, but difficult to customize the REST API -and fine-tune it to the needs of application clients. +interface for an existing database, but difficult to customize the REST API and +fine-tune it to the needs of application clients. -LoopBack 4 is moving to the well-known Model-(View-)Controller pattern, -where the code responsible for data access and manipulation is separated -from the code responsible for implementing the REST API. +LoopBack 4 is moving to the well-known Model-(View-)Controller pattern, where +the code responsible for data access and manipulation is separated from the code +responsible for implementing the REST API. [loopback4-example-microservices](https://github.com/strongloop/loopback4-example-microservices) demonstrates this loose coupling. Facade is the top-level service that serves the account summary API, and is dependent on the three services Account, -Customer, and Transaction. But the facade only aggregates the calls to the -three services, and is not tightly coupled with the service implementation; -that's why it is independent of the three services. We can define the APIs in -facade the way we want. Thus, code responsible for data access and manipulation -is separated from the code responsible for implementing client side APIs. - - -| Concept/Feature | LoopBack 3.x | LoopBack 4 | -| --------------------- | ---------------------------------------------- | ------------------------------------------------- | -| Programming Language | Built with JavaScript ES5
Node.js callback | TypeScript 2.6.x & JavaScript ES2016/2017
Promise & Async/Await | -| Core foundation | Express with LoopBack extensions | Home-grown IoC container | -| Model Definition | Models can be defined with JavaScript or JSON | Models can be defined with TypeScript/JavaScript/JSON(TBA) | -| Model Persistence | A model can be attached to a datasource backed by a connector that implements CRUD operations | [Repositories](https://github.com/strongloop/loopback-next/tree/master/packages/repository) are introduced to represent persistence related operations; a repository binds a model metadata to a datasource | -| Model Relation | Relations can be defined between models | (TBA) Relations can be defined between models but they will be realized between repositories | -| Model Remoting | JavaScript/JSON remoting metadata is used to describe method signatures and their mapping to REST/HTTP
Swagger specs are generated after the fact | Remoting metadata can be supplied by OpenAPI JSON/YAML documents or generated automatically through TypeScript decorators | -| API Spec | Swagger 2.0 | OpenAPI Spec 3.0 and potentially other API specs such as GraphQL, gRPC, etc. | -| API Explorer | Built-in UI based on swagger-ui (/explorer) | (Beta) Expose OpenAPI specs and a browser redirect to Swagger UI hosted by loopback.io | -| DataSource | JSON and JS | JSON/JS/TypeScript | -| Connector | Plain JS | JS and TypeScript (TBA) | -| Mixin | Use a utility to add methods from the mixin to the target model class | Use ES2015 mixin classes pattern supported by [TypeScript 2.2 and above](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html) | -| Middleware | Express middleware with phase-based registration and ordering | Sequence consisting of actions | -| Boot script | Scripts to be invoked during bootstrapping | (TBD) | -| Remote hooks | Before/After hooks for remote methods | Sequence/actions | -| CRUD operation hooks | Hooks for CRUD operations | Sequence/actions | -| Built-in models | Built-in User/AccessToken/Application/ACL/Role/RoleMapping for AAA | (TBD) | -| Authentication | User model as the login provider
loopback-component-passport | (TBA) Authentication component ([@loopback/authentication](https://github.com/strongloop/loopback-next/tree/master/packages/authentication)) with extensibility to strategy providers | -| Authorization | Use built-in User/Application/AccessToken model for identity and ACL/Role/RoleMapping for authorization | (TBD) Authorization component | -| Component | A very simple implementation to configure and invoke other modules | A fully-fledged packaging model that allows contribution of extensions from other modules | -| Tooling | loopback-cli and API Connect UI | [@loopback/cli](https://github.com/strongloop/loopback-next/tree/master/packages/cli) | - +Customer, and Transaction. But the facade only aggregates the calls to the three +services, and is not tightly coupled with the service implementation; that's why +it is independent of the three services. We can define the APIs in facade the +way we want. Thus, code responsible for data access and manipulation is +separated from the code responsible for implementing client side APIs. + +| Concept/Feature | LoopBack 3.x | LoopBack 4 | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Programming Language | Built with JavaScript ES5
Node.js callback | TypeScript 2.6.x & JavaScript ES2016/2017
Promise & Async/Await | +| Core foundation | Express with LoopBack extensions | Home-grown IoC container | +| Model Definition | Models can be defined with JavaScript or JSON | Models can be defined with TypeScript/JavaScript/JSON(TBA) | +| Model Persistence | A model can be attached to a datasource backed by a connector that implements CRUD operations | [Repositories](https://github.com/strongloop/loopback-next/tree/master/packages/repository) are introduced to represent persistence related operations; a repository binds a model metadata to a datasource | +| Model Relation | Relations can be defined between models | (TBA) Relations can be defined between models but they will be realized between repositories | +| Model Remoting | JavaScript/JSON remoting metadata is used to describe method signatures and their mapping to REST/HTTP
Swagger specs are generated after the fact | Remoting metadata can be supplied by OpenAPI JSON/YAML documents or generated automatically through TypeScript decorators | +| API Spec | Swagger 2.0 | OpenAPI Spec 3.0 and potentially other API specs such as GraphQL, gRPC, etc. | +| API Explorer | Built-in UI based on swagger-ui (/explorer) | (Beta) Expose OpenAPI specs and a browser redirect to Swagger UI hosted by loopback.io | +| DataSource | JSON and JS | JSON/JS/TypeScript | +| Connector | Plain JS | JS and TypeScript (TBA) | +| Mixin | Use a utility to add methods from the mixin to the target model class | Use ES2015 mixin classes pattern supported by [TypeScript 2.2 and above](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html) | +| Middleware | Express middleware with phase-based registration and ordering | Sequence consisting of actions | +| Boot script | Scripts to be invoked during bootstrapping | (TBD) | +| Remote hooks | Before/After hooks for remote methods | Sequence/actions | +| CRUD operation hooks | Hooks for CRUD operations | Sequence/actions | +| Built-in models | Built-in User/AccessToken/Application/ACL/Role/RoleMapping for AAA | (TBD) | +| Authentication | User model as the login provider
loopback-component-passport | (TBA) Authentication component ([@loopback/authentication](https://github.com/strongloop/loopback-next/tree/master/packages/authentication)) with extensibility to strategy providers | +| Authorization | Use built-in User/Application/AccessToken model for identity and ACL/Role/RoleMapping for authorization | (TBD) Authorization component | +| Component | A very simple implementation to configure and invoke other modules | A fully-fledged packaging model that allows contribution of extensions from other modules | +| Tooling | loopback-cli and API Connect UI | [@loopback/cli](https://github.com/strongloop/loopback-next/tree/master/packages/cli) | ## What's new and exciting in LoopBack 4 Some of the highlights of LoopBack 4 include: - Leverage TypeScript for better code quality and productivity -- Unify and simplify the asynchronous programming model/style around Promise -and Async/Await +- Unify and simplify the asynchronous programming model/style around Promise and + Async/Await - Implement an IoC Container with Dependency Injection for better visibility, -extensibility and composability + extensibility and composability - Introduce Component as packaging model for extensions that can be plugged into -LoopBack 4 applications + LoopBack 4 applications - Make everything else as components (REST, Authentication, and Authorization) - Divide the responsibilities of LoopBack models into - Controllers: handle incoming API requests - Repositories: provide access to datasources - Models: define schemas for business objects - Services: interact with existing REST APIs, SOAP Web Services, and other -forms of services/microservices + forms of services/microservices - Refactor the ORM into separate modules for different concerns ## Tentative roadmap > Disclaimer: The release plan is tentative and it's subject to changes as the -core team and community contributors make progress incrementally. +> core team and community contributors make progress incrementally. - [Upcoming releases](https://github.com/strongloop/loopback-next/wiki/Upcoming-Releases) diff --git a/docs/site/MONOREPO.md b/docs/site/MONOREPO.md index e9afb35e26de..a6e68925a07f 100644 --- a/docs/site/MONOREPO.md +++ b/docs/site/MONOREPO.md @@ -1,29 +1,31 @@ # Monorepo overview -The [loopback-next](https://github.com/strongloop/loopback-next) repository uses [lerna](https://lernajs.io/) to manage multiple packages for LoopBack 4. +The [loopback-next](https://github.com/strongloop/loopback-next) repository uses +[lerna](https://lernajs.io/) to manage multiple packages for LoopBack 4. -| Package | npm | Description | -|-----------------------------------------------------------|-------------------------------|---------------------------| -|[authentication](packages/authentication) |@loopback/authentication | A component for authentication support | -|[boot](packages/boot) |@loopback/boot | Convention based Bootstrapper and Booters | -|[build](packages/build) |@loopback/build | A set of common scripts and default configurations to build LoopBack 4 or other TypeScript modules | -|[cli](packages/cli) |@loopback/cli | CLI for LoopBack 4 | -|[context](packages/context) |@loopback/context | Facilities to manage artifacts and their dependencies in your Node.js applications. The module exposes TypeScript/JavaScript APIs and decorators to register artifacts, declare dependencies, and resolve artifacts by keys. It also serves as an IoC container to support dependency injection. | -|[core](packages/core) |@loopback/core | Define and implement core constructs such as Application and Component | -|[example-getting-started](packages/example-getting-started)| _(private)_ | A basic tutorial for getting started with Loopback 4 | -|[example-hello-world](packages/example-hello-world) | _(private)_ | A simple hello-world application using LoopBack 4 | -|[example-log-extension](packages/example-log-extension) | _(private)_ | An example showing how to write a complex log extension for LoopBack 4 | -|[example-rpc-server](packages/example-rpc-server) | _(private)_ | An example RPC server and application to demonstrate the creation of your own custom server | -|[metadata](packages/metadata) |@loopback/metadata | Utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata | -|[openapi-spec-builder](packages/openapi-spec-builder) |@loopback/openapi-spec-builder | Builders to create OpenAPI (Swagger) specification documents in tests | -|[openapi-v3](packages/openapi-v3) |@loopback/openapi-v3 | Decorators that annotate LoopBack artifacts with OpenAPI v3 metadata and utilities that transform LoopBack metadata to OpenAPI v3 specifications| -|[openapi-v3-types](packages/openapi-v3-types) |@loopback/openapi-v3-types | TypeScript type definitions for OpenAPI Specifications | -|[repository-json-schema](packages/repository-json-schema) |@loopback/repository-json-schema| Convert a TypeScript class/model to a JSON Schema | -|[repository](packages/repository) |@loopback/repository | Define and implement a common set of interfaces for interacting with databases| -|[rest](packages/rest) |@loopback/rest | Expose controllers as REST endpoints and route REST API requests to controller methods | -|[testlab](packages/testlab) |@loopback/testlib | A collection of test utilities we use to write LoopBack tests | - -We use npm scripts declared in [package.json](package.json) to work with the monorepo managed by lerna. See [Developing LoopBack](./docs/DEVELOPING.md) for more details. +| Package | npm | Description | +| ----------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [authentication](packages/authentication) | @loopback/authentication | A component for authentication support | +| [boot](packages/boot) | @loopback/boot | Convention based Bootstrapper and Booters | +| [build](packages/build) | @loopback/build | A set of common scripts and default configurations to build LoopBack 4 or other TypeScript modules | +| [cli](packages/cli) | @loopback/cli | CLI for LoopBack 4 | +| [context](packages/context) | @loopback/context | Facilities to manage artifacts and their dependencies in your Node.js applications. The module exposes TypeScript/JavaScript APIs and decorators to register artifacts, declare dependencies, and resolve artifacts by keys. It also serves as an IoC container to support dependency injection. | +| [core](packages/core) | @loopback/core | Define and implement core constructs such as Application and Component | +| [example-getting-started](packages/example-getting-started) | _(private)_ | A basic tutorial for getting started with Loopback 4 | +| [example-hello-world](packages/example-hello-world) | _(private)_ | A simple hello-world application using LoopBack 4 | +| [example-log-extension](packages/example-log-extension) | _(private)_ | An example showing how to write a complex log extension for LoopBack 4 | +| [example-rpc-server](packages/example-rpc-server) | _(private)_ | An example RPC server and application to demonstrate the creation of your own custom server | +| [metadata](packages/metadata) | @loopback/metadata | Utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata | +| [openapi-spec-builder](packages/openapi-spec-builder) | @loopback/openapi-spec-builder | Builders to create OpenAPI (Swagger) specification documents in tests | +| [openapi-v3](packages/openapi-v3) | @loopback/openapi-v3 | Decorators that annotate LoopBack artifacts with OpenAPI v3 metadata and utilities that transform LoopBack metadata to OpenAPI v3 specifications | +| [openapi-v3-types](packages/openapi-v3-types) | @loopback/openapi-v3-types | TypeScript type definitions for OpenAPI Specifications | +| [repository-json-schema](packages/repository-json-schema) | @loopback/repository-json-schema | Convert a TypeScript class/model to a JSON Schema | +| [repository](packages/repository) | @loopback/repository | Define and implement a common set of interfaces for interacting with databases | +| [rest](packages/rest) | @loopback/rest | Expose controllers as REST endpoints and route REST API requests to controller methods | +| [testlab](packages/testlab) | @loopback/testlib | A collection of test utilities we use to write LoopBack tests | +We use npm scripts declared in [package.json](package.json) to work with the +monorepo managed by lerna. See [Developing LoopBack](./docs/DEVELOPING.md) for +more details. diff --git a/docs/site/Mixin.md b/docs/site/Mixin.md index aafe87b1f585..82637e2cc7c8 100644 --- a/docs/site/Mixin.md +++ b/docs/site/Mixin.md @@ -8,42 +8,44 @@ permalink: /doc/en/lb4/Mixin.html summary: --- -It is a commonly used JavaScript/TypeScript strategy to extend a class with new properties and methods. +It is a commonly used JavaScript/TypeScript strategy to extend a class with new +properties and methods. -A good approach to apply mixins is defining them as sub-class factories. -Then declare the new mixed class as: +A good approach to apply mixins is defining them as sub-class factories. Then +declare the new mixed class as: ```js -class MixedClass extends MixinFoo(MixinBar(BaseClass)) {}; +class MixedClass extends MixinFoo(MixinBar(BaseClass)) {} ``` -Check article [real mixins with javascript classes](http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/) +Check article +[real mixins with javascript classes](http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/) to learn more about it. ## Define Mixin -By defining a mixin, you create a mixin function that takes in a base class, -and returns a new class extending the base class with new properties and methods mixed to it. +By defining a mixin, you create a mixin function that takes in a base class, and +returns a new class extending the base class with new properties and methods +mixed to it. -For example you have a simple controller which only has a greeter function prints out 'hi!': +For example you have a simple controller which only has a greeter function +prints out 'hi!': {% include code-caption.html content="Controllers/myController.ts" %} ```ts class SimpleController { - constructor() { - - } - greet() { - console.log('hi!'); - } + constructor() {} + greet() { + console.log('hi!'); + } } ``` Now let's add mixins to it: -- A time stamp mixin that adds a property `createdAt` to a record when a -controller instance is created. +- A time stamp mixin that adds a property `createdAt` to a record when a + controller instance is created. - A logger mixin to provide logging tools. @@ -52,20 +54,20 @@ Define mixin `timeStampMixin`: {% include code-caption.html content="Mixins/timeStampMixin.ts" %} ```ts -import {Class} from "@loopback/repository"; +import {Class} from '@loopback/repository'; -export function timeStampMixin> (baseClass: T) { +export function timeStampMixin>(baseClass: T) { return class extends baseClass { - // add a new property `createdAt` - public createdAt: Date; - constructor(...args: any[]) { - super(args); - this.createTS = new Date(); - } - printTimeStamp() { - console.log('Instance created at: ' + this.createdAt); - } - } + // add a new property `createdAt` + public createdAt: Date; + constructor(...args: any[]) { + super(args); + this.createTS = new Date(); + } + printTimeStamp() { + console.log('Instance created at: ' + this.createdAt); + } + }; } ``` @@ -74,15 +76,15 @@ And define mixin `loggerMixin`: {% include code-caption.html content="Mixins/loggerMixin.ts" %} ```ts -import {Class} from "@loopback/repository"; +import {Class} from '@loopback/repository'; -function loggerMixin> (baseClass: T) { +function loggerMixin>(baseClass: T) { return class extends baseClass { // add a new method `log()` log(str: string) { console.log('Prints out a string: ' + str); - }; - } + } + }; } ``` @@ -95,15 +97,15 @@ import {timeStampMixin} from 'Mixins/timeStampMixin.ts'; import {loggerMixin} from 'Mixins/loggerMixin.ts'; class SimpleController { - constructor() { - - } - greet() { - console.log('hi!'); - } + constructor() {} + greet() { + console.log('hi!'); + } } -class AdvancedController extends loggerMixin(timeStampMixin(SimpleController)) {}; +class AdvancedController extends loggerMixin( + timeStampMixin(SimpleController), +) {} // verify new method and property are added to `AdvancedController`: let aControllerInst = new AdvancedController(); @@ -117,8 +119,8 @@ aControllerInst.logger('hello world!'); Here are some articles explaining ES2015 and TypeScript mixins in more details: -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Mix-ins +- -- http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/ +- -- https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html \ No newline at end of file +- diff --git a/docs/site/Model.md b/docs/site/Model.md index e47c24ac1fcb..343123ea45d1 100644 --- a/docs/site/Model.md +++ b/docs/site/Model.md @@ -7,16 +7,19 @@ sidebar: lb4_sidebar permalink: /doc/en/lb4/Model.html summary: --- + {% include content/tbd.html %} ## Overview -A `model` represents the definition of a model in LoopBack, with respect to -the [datasource juggler](https://github.com/strongloop/loopback-datasource-juggler). + +A `model` represents the definition of a model in LoopBack, with respect to the +[datasource juggler](https://github.com/strongloop/loopback-datasource-juggler). Currently, we provide the `@loopback/repository` module, which provides special -decorators for adding metadata to your TypeScript/JavaScript -classes in order to use them with the legacy implementation of the Juggler. +decorators for adding metadata to your TypeScript/JavaScript classes in order to +use them with the legacy implementation of the Juggler. ## Definition of a Model + At its core, a model in LoopBack is a simple JavaScript class. ```ts @@ -30,8 +33,8 @@ export class Customer { Extensibility is a core feature of LoopBack. There are external packages that add additional features, for example, integration with the legacy juggler or JSON Schema generation. These features become available to a LoopBack model -through the `@model` and `@property` decorators from the -`@loopback/repository` module. +through the `@model` and `@property` decorators from the `@loopback/repository` +module. ```ts import {model, property} from '@loopback/repository'; @@ -45,6 +48,7 @@ export class Customer { ``` ## Using Legacy Juggler + To define a model for use with the legacy juggler, extend your classes from `Entity` and decorate them with the `@model` and `@property` decorators. @@ -59,11 +63,9 @@ export class Product extends Entity { }) id: number; - @property() - name: string; + @property() name: string; - @property() - slug: string; + @property() slug: string; constructor(data?: Partial) { super(data); @@ -72,20 +74,24 @@ export class Product extends Entity { ``` ### Model Decorator + The model decorator can be used without any additional parameters, or can be passed in a + + [ModelDefinitionSyntax](https://loopback.io/doc/en/lb3/Model-definition-JSON-file.html) object which follows the general format provided in LoopBack 3: + ```ts @model({ - name: "Category", + name: 'Category', properties: { // define properties here. }, settings: { // etc... - } + }, }) class Category extends Entity { // etc... @@ -94,6 +100,7 @@ class Category extends Entity { However, the model decorator already knows the name of your model class, so you can omit it. + ```ts @model() class Product extends Entity { @@ -107,33 +114,37 @@ the information passed in or inferred by the property decorators, so the properties key value pair can be omitted as well by using property decorators. ### Property Decorator + The property decorator takes in the same arguments used in LoopBack 3 for individual property entries: + ```ts @model() class Product extends Entity { @property({ - name: "name", + name: 'name', description: "The product's common name.", - type: "string", + type: 'string', }) public name: string; } ``` The complete list of valid attributes for property definitions can be found in -LoopBack 3's [Model definition section](https://loopback.io/doc/en/lb3/Model-definition-JSON-file.md#properties). +LoopBack 3's +[Model definition section](https://loopback.io/doc/en/lb3/Model-definition-JSON-file.md#properties). -The property decorator leverages LoopBack's [metadata package](https://github.com/strongloop/loopback-next/tree/master/packages/metadata) + +The property decorator leverages LoopBack's +[metadata package](https://github.com/strongloop/loopback-next/tree/master/packages/metadata) to determine the type of a particular property. ```ts @model() class Product extends Entity { - @property() - public name: string; // The type information for this property is String. + @property() public name: string; // The type information for this property is String. } ``` @@ -147,9 +158,8 @@ primitive type (string, number, array, boolean), object or function, but this would not tell us anything about what the value would be if it were an object or function. -For consistency, we require the use of the `@property.array` -decorator, which adds the appropriate metadata for type inference of your array -properties. +For consistency, we require the use of the `@property.array` decorator, which +adds the appropriate metadata for type inference of your array properties. ```ts @model() @@ -167,22 +177,25 @@ class Thread extends Entity { Additionally, the `@property.array` decorator can still take an optional 2nd parameter to define or override metadata in the same fashion as the `@property` decorator. + ```ts @model() class Customer extends Entity { @property.array(String, { name: 'names', required: true, - }) aliases: string[]; + }) + aliases: string[]; } ``` ### JSON Schema inference -Use the `@loopback/repository-json-schema module` to build a JSON schema from -a decorated model. Type information is inferred from the `@model` and -`@property` decorators. The `@loopback/repository-json-schema` module contains -the `getJsonSchema` function to access the metadata stored by the decorators -to build a matching JSON Schema of your model. + +Use the `@loopback/repository-json-schema module` to build a JSON schema from a +decorated model. Type information is inferred from the `@model` and `@property` +decorators. The `@loopback/repository-json-schema` module contains the +`getJsonSchema` function to access the metadata stored by the decorators to +build a matching JSON Schema of your model. ```ts import {model, property} from '@loopback/repository'; @@ -195,7 +208,8 @@ class Category { @model() class Product { - @property({required: true}) name: string; + @property({required: true}) + name: string; @property() type: Category; } @@ -229,13 +243,14 @@ const jsonSchema = getJsonSchema(Product); ``` If a custom type is specified for a decorated property in a model definition, -then a reference [`$ref`](http://json-schema.org/latest/json-schema-core.html#rfc.section.8) +then a reference +[`$ref`](http://json-schema.org/latest/json-schema-core.html#rfc.section.8) field is created for it and a `definitions` sub-schema is created at the top-level of the schema. The `definitions` sub-schema is populated with the type -definition by recursively calling `getJsonSchema` to build its properties. -This allows for complex and nested custom type definition building. -The example above illustrates this point by having the custom type `Category` -as a property of our `Product` model definition. +definition by recursively calling `getJsonSchema` to build its properties. This +allows for complex and nested custom type definition building. The example above +illustrates this point by having the custom type `Category` as a property of our +`Product` model definition. #### Supported JSON keywords @@ -245,24 +260,24 @@ This feature is still a work in progress and is incomplete. " %} -Following are the supported keywords that can be explicitly passed into the decorators -to better tailor towards the JSON Schema being produced. +Following are the supported keywords that can be explicitly passed into the +decorators to better tailor towards the JSON Schema being produced. | Keywords | Decorator | Type | Default | Description | -|-------------|-------------|---------|--------------|---------------------------------------------------------| -| title | `@model` | string | *model name* | Name of the model | +| ----------- | ----------- | ------- | ------------ | ------------------------------------------------------- | +| title | `@model` | string | _model name_ | Name of the model | | description | `@model` | string | | Description of the model | | array | `@property` | boolean | | Used to specify whether the property is an array or not | | required | `@property` | boolean | | Used to specify whether the property is required or not | ## Other ORMs + You might decide to use an alternative ORM/ODM in your LoopBack application. LoopBack 4 no longer expects you to provide your data in its own custom Model -format for routing purposes, which means you are free to alter your classes -to suit these ORMs/ODMs. +format for routing purposes, which means you are free to alter your classes to +suit these ORMs/ODMs. However, this also means that the provided schema decorators will serve no purpose for these ORMs/ODMs. Some of these frameworks may also provide decorators with conflicting names (e.g. another `@model` decorator), which might warrant avoiding the provided juggler decorators. - diff --git a/docs/site/Preparing-the-API-for-consumption.md b/docs/site/Preparing-the-API-for-consumption.md index 4e6dc564c050..5da3f720fce1 100644 --- a/docs/site/Preparing-the-API-for-consumption.md +++ b/docs/site/Preparing-the-API-for-consumption.md @@ -8,31 +8,34 @@ permalink: /doc/en/lb4/Preparing-the-API-for-consumption.html summary: --- -{% include previous.html content=" -This article continues from [Implementing features](./Implementing-features.md). -" %} +{% include previous.html content=" This article continues +from [Implementing features](./Implementing-features.md). " %} ## Preparing your API for consumption ### Interacting with your API -We'll use the repo [loopback4-example-getting-started](https://github.com/strongloop/loopback4-example-getting-started) to demonstrate how Swagger UI can be used to test your endpoints. +We'll use the repo +[loopback4-example-getting-started](https://github.com/strongloop/loopback4-example-getting-started) +to demonstrate how Swagger UI can be used to test your endpoints. -First, git clone the repository, install its dependencies, and run the application: +First, git clone the repository, install its dependencies, and run the +application: -``` -$ git clone https://github.com/strongloop/loopback4-example-getting-started -$ cd loopback4-example-getting-started -$ npm i -$ npm start +```sh +git clone https://github.com/strongloop/loopback4-example-getting-started +cd loopback4-example-getting-started +npm i +npm start ``` -Open [http://localhost:3000/swagger-ui](http://localhost:3000/swagger-ui) to see the API endpoints defined by `swagger.json`. +Open to see the API endpoints defined by +`swagger.json`. -{% include note.html content=" - Swagger UI provides users with interactive environment to test the API endpoints defined by the raw spec found at [http://localhost:3000/openapi.json](http://localhost:3000/openapi.json). - The API spec is also available in YAML flavour at [http://localhost:3000/openapi.yaml](http://localhost:3000/openapi.yaml) -" %} +{% include note.html content=" Swagger UI provides users with interactive +environment to test the API endpoints defined by the raw spec found at +. The API spec is also available in YAML +flavour at " %} {% include image.html file="lb4/10000000.png" alt="" %} @@ -40,41 +43,66 @@ The Swagger UI displays all of the endpoints defined in your application. {% include image.html file="lb4/10000001.png" alt="" %} -Clicking on one of the endpoints will show the endpoint's documentation as defined in your API spec. Next, click on `Try It Out` to send a request to the endpoint. If the endpoint takes parameters, assign the values before the request is sent. If the parameter involves a body, a template is given for you to edit as specified in your spec. Click `Execute` to send the request: +Clicking on one of the endpoints will show the endpoint's documentation as +defined in your API spec. Next, click on `Try It Out` to send a request to the +endpoint. If the endpoint takes parameters, assign the values before the request +is sent. If the parameter involves a body, a template is given for you to edit +as specified in your spec. Click `Execute` to send the request: {% include image.html file="lb4/10000002.png" alt="" %} -The response to the request can be seen below the `Execute` button, where the response code and the body are displayed. Ideally, each endpoint should be tested with good and bad inputs to confirm that the returned responses are as expected. +The response to the request can be seen below the `Execute` button, where the +response code and the body are displayed. Ideally, each endpoint should be +tested with good and bad inputs to confirm that the returned responses are as +expected. ## Closing thoughts -Congratulations! You now have successfully created and tested an API with LoopBack 4. We hope you enjoy the test-drive. Your feedback matters and please share your thoughts with us. +Congratulations! You now have successfully created and tested an API with +LoopBack 4. We hope you enjoy the test-drive. Your feedback matters and please +share your thoughts with us. -This is just the beginning of the full LoopBack 4 developer experience. The first beta release lays out the new foundation of LoopBack for extension developers. It also demonstrates a path to create REST APIs from OpenAPI specs together with Controllers and Repositories. More features will be added in the coming weeks and months. +This is just the beginning of the full LoopBack 4 developer experience. The +first beta release lays out the new foundation of LoopBack for extension +developers. It also demonstrates a path to create REST APIs from OpenAPI specs +together with Controllers and Repositories. More features will be added in the +coming weeks and months. Here is a sneak peek of what's coming: -- More extensions and extension points an: [loopback-next issue #512](https://github.com/strongloop/loopback-next/issues/512) +- More extensions and extension points an: + [loopback-next issue #512](https://github.com/strongloop/loopback-next/issues/512) -- Authorization component: [loopback-next issue #538](https://github.com/strongloop/loopback-next/issues/538) +- Authorization component: + [loopback-next issue #538](https://github.com/strongloop/loopback-next/issues/538) -- Fully-fledged API explorer: [loopback-next issue #559](https://github.com/strongloop/loopback-next/issues/559) +- Fully-fledged API explorer: + [loopback-next issue #559](https://github.com/strongloop/loopback-next/issues/559) - Complete repository/service story for backend interactions + - [loopback-next issue #419](https://github.com/strongloop/loopback-next/issues/419) - [loopback-next issue #537](https://github.com/strongloop/loopback-next/issues/537) - [loopback-next issue #522](https://github.com/strongloop/loopback-next/issues/522) - Declarative support for various constructs + - [loopback-next issue #441](https://github.com/strongloop/loopback-next/issues/441) - [loopback-next issue #461](https://github.com/strongloop/loopback-next/issues/461) - Alignment of microservices and cloud native experience + - [loopback-next issue #442](https://github.com/strongloop/loopback-next/issues/442) - [loopback-next issue #25](https://github.com/strongloop/loopback-next/issues/25) -- Tooling: [loopback-next issue #361](https://github.com/strongloop/loopback-next/issues/361) +- Tooling: + [loopback-next issue #361](https://github.com/strongloop/loopback-next/issues/361) -- Plain JavaScript: [loopback-next issue #560](https://github.com/strongloop/loopback-next/issues/560) +- Plain JavaScript: + [loopback-next issue #560](https://github.com/strongloop/loopback-next/issues/560) -The train is moving and welcome on board! Your participation and contribution will make LoopBack 4 an even more powerful framework and greater community/ecosystem. The team is very excited about the new journey. We look forward to working with you on more ideas, more pull requests, and more extension modules. Let's make LoopBack 4 rock together! +The train is moving and welcome on board! Your participation and contribution +will make LoopBack 4 an even more powerful framework and greater +community/ecosystem. The team is very excited about the new journey. We look +forward to working with you on more ideas, more pull requests, and more +extension modules. Let's make LoopBack 4 rock together! diff --git a/docs/site/Repositories.md b/docs/site/Repositories.md index 88543b3c56b7..b6087a2c49a4 100644 --- a/docs/site/Repositories.md +++ b/docs/site/Repositories.md @@ -165,7 +165,7 @@ When you want to define new CRUD methods for your application, you will need to modify the API Definitions and their corresponding methods in your controller. Here are examples of some basic CRUD methods: -1. Create API Definition: +1. Create API Definition: ```json { @@ -208,7 +208,7 @@ async createAccount(accountInstance: Account) { } ``` -2. Find API Definition: +2. Find API Definition: ```json { @@ -256,7 +256,7 @@ application. examples may not work out of the box." %} LoopBack 4 gives you the flexibility to create your own custom Datasources which -utilize your own custom connector for your favourite back end database. You can +utilize your own custom connector for your favorite back end database. You can then fine tune your CRUD methods to your liking. ### Example Application @@ -264,43 +264,43 @@ then fine tune your CRUD methods to your liking. You can look at [the account-without-juggler application as an example.](https://github.com/strongloop/loopback-next-example/tree/master/services/account-without-juggler) -### Steps to create your own concrete DataSource + -1. Implement the `CrudConnector` interface from `@loopback/repository` package. - [Here is one way to do it](https://github.com/strongloop/loopback-next-example/blob/master/services/account-without-juggler/repositories/account/datasources/mysqlconn.ts) +1. Implement the `CrudConnector` interface from `@loopback/repository` package. + [Here is one way to do it](https://github.com/strongloop/loopback-next-example/blob/master/services/account-without-juggler/repositories/account/datasources/mysqlconn.ts) -2. Implement the `DataSource` interface from `@loopback/repository`. To - implement the `DataSource` interface, you must give it a name, supply your - custom connector class created in the previous step, and instantiate it: +2. Implement the `DataSource` interface from `@loopback/repository`. To + implement the `DataSource` interface, you must give it a name, supply your + custom connector class created in the previous step, and instantiate it: - ```ts - export class MySQLDs implements DataSource { - name: 'mysqlDs'; - connector: MySqlConn; - settings: Object; + ```ts + export class MySQLDs implements DataSource { + name: 'mysqlDs'; + connector: MySqlConn; + settings: Object; - constructor() { - this.settings = require('./mysql.json'); // connection configuration - this.connector = new MySqlConn(this.settings); - } - } - ``` + constructor() { + this.settings = require('./mysql.json'); // connection configuration + this.connector = new MySqlConn(this.settings); + } + } + ``` -3. Extend `CrudRepositoryImpl` class from `@loopback/repository` and supply - your custom DataSource and model to it: +3. Extend `CrudRepositoryImpl` class from `@loopback/repository` and supply + your custom DataSource and model to it: - ```ts - import {CrudRepositoryImpl} from '@loopback/repository'; - import {MySQLDs} from './datasources/mysqlds'; - import {Account} from './models/Account'; + ```ts + import {CrudRepositoryImpl} from '@loopback/repository'; + import {MySQLDs} from './datasources/mysqlds'; + import {Account} from './models/Account'; - export class NewRepository extends CrudRepositoryImpl { - constructor() { - const ds = new MySQLDs(); - super(ds, Account); - } - } - ``` + export class NewRepository extends CrudRepositoryImpl { + constructor() { + const ds = new MySQLDs(); + super(ds, Account); + } + } + ``` You can override the functions it provides, which ultimately call on your connector's implementation of them, or write new ones. @@ -311,28 +311,28 @@ The next step is to wire your new DataSource to your controller. This step is essentially the same as above, but can also be done as follows using Dependency Injection: -1. Bind instance of your repository to a certain key in your application class +1. Bind instance of your repository to a certain key in your application class - ```ts - class AccountMicroservice extends Application { - private _startTime: Date; + ```ts + class AccountMicroservice extends Application { + private _startTime: Date; - constructor() { - super(); - const app = this; - app.controller(AccountController); - app.bind('repositories.NewRepository').toClass(NewRepository); - } - ``` + constructor() { + super(); + const app = this; + app.controller(AccountController); + app.bind('repositories.NewRepository').toClass(NewRepository); + } + ``` -2. Inject the bound instance into the repository property of your controller. - `inject` can be imported from `@loopback/context`. +2. Inject the bound instance into the repository property of your controller. + `inject` can be imported from `@loopback/context`. - ```ts - export class AccountController { - @repository(NewRepository.name) private repository: NewRepository; - } - ``` + ```ts + export class AccountController { + @repository(NewRepository.name) private repository: NewRepository; + } + ``` ### Example custom connector CRUD methods diff --git a/docs/site/Reserved-binding-keys.md b/docs/site/Reserved-binding-keys.md index 11d003cfb41f..f587b0304afc 100644 --- a/docs/site/Reserved-binding-keys.md +++ b/docs/site/Reserved-binding-keys.md @@ -8,59 +8,71 @@ sidebar: lb4_sidebar permalink: /doc/en/lb4/Reserved-binding-keys.html summary: --- + ## Overview -When using [dependency injection](Dependency-injection.md) there are a few things to keep in mind with regards to binding keys. +When using [dependency injection](Dependency-injection.md) there are a few +things to keep in mind with regards to binding keys. -Different packages and components for LoopBack 4 may have some bindings already defined. You can change the default behavior by overriding the default binding, but you must ensure the interface of the new binding is the same as the default (but behavior can be different). +Different packages and components for LoopBack 4 may have some bindings already +defined. You can change the default behavior by overriding the default binding, +but you must ensure the interface of the new binding is the same as the default +(but behavior can be different). -Following is a list that documents the binding keys in use by various `@loopback` packages and their `Type` so you can easily look at their interface in the [API Docs](http://apidocs.loopback.io). +Following is a list that documents the binding keys in use by various +`@loopback` packages and their `Type` so you can easily look at their interface +in the [API Docs](http://apidocs.loopback.io). -It is recommended to use the CONSTANT defined for each binding key in it's respective namespace. You can import a namespace and access the binding key in your application as follows: +It is recommended to use the CONSTANT defined for each binding key in it's +respective namespace. You can import a namespace and access the binding key in +your application as follows: ```js -import { BindingKeyNameSpace } from 'package-name'; +import {BindingKeyNameSpace} from 'package-name'; app.bind(BindKeyNameSpace.KeyName).to('value'); ``` -{% include note.html title="Declaring new binding keys" content="For component developers creating a new Binding, to avoid conflict with other packages, it is recommended that the binding key start with the package name as the prefix. Example: `@loopback/authentication` component uses the prefix `authentication` for its binding keys. -" %} +{% include note.html title="Declaring new binding keys" content="For component +developers creating a new Binding, to avoid conflict with other packages, it is +recommended that the binding key start with the package name as the prefix. +Example: `@loopback/authentication` component uses the prefix `authentication` +for its binding keys. " %} ## Package: authentication **Reserved prefixes:** -``` +```text authentication.* ``` ### CONSTANT Namespace ```js -import { AuthenticationBindings } from '@loopback/authentication' +import {AuthenticationBindings} from '@loopback/authentication'; ``` ### Binding keys **Sequence Actions binding keys** -|Name|CONSTANT|`Type `|Description| -|---|---|---|---| -|`authentication.actions.authenticate`|`AUTH_ACTION`|`AuthenticateFn`|Provides the authenticate function to be called in Sequence action.| +| Name | CONSTANT | `Type` | Description | +| ------------------------------------- | ------------- | ---------------- | ------------------------------------------------------------------- | +| `authentication.actions.authenticate` | `AUTH_ACTION` | `AuthenticateFn` | Provides the authenticate function to be called in Sequence action. | **Other binding keys** -|Name|CONSTANT|Type|Description| -|---|---|---|---| -|`authentication.currentUser`|`CURRENT_USER`|`UserProfile`|Authenticated user profile for the current request| -|`authentication.operationMetadata`|`METADATA`|`AuthenticationMetadata`|Authentication Metadata| -|`authentication.strategy`|`STRATEGY`|`Strategy`|Provider for a [passport](http://passportjs.org/) strategy| +| Name | CONSTANT | Type | Description | +| ---------------------------------- | -------------- | ------------------------ | ---------------------------------------------------------- | +| `authentication.currentUser` | `CURRENT_USER` | `UserProfile` | Authenticated user profile for the current request | +| `authentication.operationMetadata` | `METADATA` | `AuthenticationMetadata` | Authentication Metadata | +| `authentication.strategy` | `STRATEGY` | `Strategy` | Provider for a [passport](http://passportjs.org/) strategy | ## Package: context **Reserved prefixes:** -``` +```text context.* ``` @@ -72,118 +84,129 @@ _None_ **Reserved prefixes:** -``` +```text core.* ``` -``` +```text controllers.* ``` ### CONSTANT Namespace ```js -import { CoreBindings } from '@loopback/authentication' +import {CoreBindings} from '@loopback/authentication'; ``` ### Binding keys -|Name|CONSTANT|Type|Description| -|---|---|---|---| -|`application.apiSpec`|`API_SPEC`|`OpenApiSpec`|OpenAPI Specification describing your application's routes| -|`bindElement`|`BIND_ELEMENT`|`BindElement`|Convenience provider function to bind value to `Context`| -|`components.${component.name}`||`Component`|Components used by your application| -|`controllers.${controller.name}`||`ControllerClass`|The controller's bound to the application| -|`controller.current.ctor`|`CONTROLLER_CLASS`|`ControllerClass`|The controller for the current request| -|`controller.current.operation`|`CONTROLLER_METHOD_NAME`|`string`|Name of the operation for the current request| -|`controller.method.meta`|`CONTROLLER_METHOD_META`|`ControllerMetaData`|Metadata for a given controller| -|`getFromContext`|`GET_FROM_CONTEXT`|`GetFromContext`|Convenience provider function to return the `BoundValue` from the `Context`| +| Name | CONSTANT | Type | Description | +| -------------------------------- | ------------------------ | -------------------- | --------------------------------------------------------------------------- | +| `application.apiSpec` | `API_SPEC` | `OpenApiSpec` | OpenAPI Specification describing your application's routes | +| `bindElement` | `BIND_ELEMENT` | `BindElement` | Convenience provider function to bind value to `Context` | +| `components.${component.name}` | | `Component` | Components used by your application | +| `controllers.${controller.name}` | | `ControllerClass` | The controller's bound to the application | +| `controller.current.ctor` | `CONTROLLER_CLASS` | `ControllerClass` | The controller for the current request | +| `controller.current.operation` | `CONTROLLER_METHOD_NAME` | `string` | Name of the operation for the current request | +| `controller.method.meta` | `CONTROLLER_METHOD_META` | `ControllerMetaData` | Metadata for a given controller | +| `getFromContext` | `GET_FROM_CONTEXT` | `GetFromContext` | Convenience provider function to return the `BoundValue` from the `Context` | ## Package: rest -|`rest.handler`|`HANDLER`|`HttpHandler`|The HTTP Request Handler| -|`rest.port`|`PORT`|`number`|HTTP Port the application will run on| -|`rest.http.request`|`Http.REQUEST`|`ServerRequest`|The raw `http` request object| -|`rest.http.request.context`|`Http.CONTEXT`|`Context`|Request level context| -|`rest.http.response`|`Http.RESPONSE`|`ServerResponse`|The raw `http` response object| -|`routes.${route.verb}.${route.path}`||`RouteEntry`|Route entry specified in api-spec| -|`rest.sequence`|`SEQUENCE`|`SequenceHandler`|Class that implements the sequence for your application| +| Name | CONSTANT | Type | Description | +| -------------------------------------------- | --------------- | ----------------- | ------------------------------------- | +| `rest.handler` | `HANDLER` | `HttpHandler` | The HTTP Request Handler | +| `rest.port` | `PORT` | `number` | HTTP Port the application will run on | +| `rest.http.request` | `Http.REQUEST` | `ServerRequest` | The raw `http` request | +| object | | | | +| `rest.http.request.context` | `Http.CONTEXT` | `Context` | Request level | +| context | | | | +| `rest.http.response` | `Http.RESPONSE` | `ServerResponse` | The raw `http` | +| response object | | | | +| `routes.${route.verb}.${route.path}` | | `RouteEntry` | Route entry | +| specified in api-spec | | | | +| `rest.sequence` | `SEQUENCE` | `SequenceHandler` | Class that | +| implements the sequence for your application | | | | **Rest Sequence Action Binding Keys** -To use the Rest Sequence Actions CONSTANTs, bind/inject to `RestBindings.SequenceActions.CONSTANT` *OR* +To use the Rest Sequence Actions CONSTANTs, bind/inject to +`RestBindings.SequenceActions.CONSTANT` _OR_ ```js const SequenceActions = RestBindings.SequenceActions; -SequenceActions.CONSTANT // CONSTANT to bind/inject +SequenceActions.CONSTANT; // CONSTANT to bind/inject ``` -|Name|CONSTANT|Type|Description| -|---|---|---|---| -|`sequence.actions.findRoute`|`FIND_ROUTE`|`FindRoute`|Sequence action to find the route for a given request| -|`sequence.actions.invokeMethod`|`INVOKE_METHOD`|`InvokeMethod`|Sequence action to invoke the operation method defined for the requested route| -|`sequence.actions.logError`|`LOG_ERROR`|`LogError`|Sequence action to log information about a failed request| -|`sequence.actions.parseParams`|`PARSE_PARAMS`|`ParseParams`|Sequence action to parse a request for arguments to be passed to the controller| -|`sequence.actions.reject`|`REJECT`|`Reject`|Sequence action to reject the request with an error| -|`sequence.actions.send`|`SEND`|`Send`|Sequence action to send the response back to client| +| Name | CONSTANT | Type | Description | +| ------------------------------- | --------------- | -------------- | ------------------------------------------------------------------------------- | +| `sequence.actions.findRoute` | `FIND_ROUTE` | `FindRoute` | Sequence action to find the route for a given request | +| `sequence.actions.invokeMethod` | `INVOKE_METHOD` | `InvokeMethod` | Sequence action to invoke the operation method defined for the requested route | +| `sequence.actions.logError` | `LOG_ERROR` | `LogError` | Sequence action to log information about a failed request | +| `sequence.actions.parseParams` | `PARSE_PARAMS` | `ParseParams` | Sequence action to parse a request for arguments to be passed to the controller | +| `sequence.actions.reject` | `REJECT` | `Reject` | Sequence action to reject the request with an error | +| `sequence.actions.send` | `SEND` | `Send` | Sequence action to send the response back to client | ## Package: openapi-spec **Reserved prefixes:** -``` +```text api-spec.* ``` ### Binding keys + _None_ ## Package: openapi-spec-builder **Reserved prefixes:** -``` +```text spec-builder.* ``` ### Binding keys + _None_ ## Package: repository **Reserved prefixes:** -``` +```text repository.* ``` -``` +```text repositories.*` ``` -``` +```text datasources.* ``` -``` +```text models.* ``` ### Binding keys -|Name|CONSTANT|Type|Description| -|---|---|---|---| -|`datasources.${dataSourceName}`||`DataSource`|Instance of a given datasource| -|`models.${modelName}`||`Model`|Instance of a given model| -|`repositories.${repositoryName}`||`Repository`|Instance of a given repository| +| Name | CONSTANT | Type | Description | +| -------------------------------- | -------- | ------------ | ------------------------------ | +| `datasources.${dataSourceName}` | | `DataSource` | Instance of a given datasource | +| `models.${modelName}` | | `Model` | Instance of a given model | +| `repositories.${repositoryName}` | | `Repository` | Instance of a given repository | ## Package: testlab **Reserved prefixes:** -``` +```text testlab.* ``` ### Binding keys + _None_ diff --git a/docs/site/Sequence.md b/docs/site/Sequence.md index 14cab99f98f0..2e20413454ee 100644 --- a/docs/site/Sequence.md +++ b/docs/site/Sequence.md @@ -21,6 +21,7 @@ instances handle requests and responses. The `DefaultSequence` looks like this: FIXME(kev): Should we be copying this logic into the docs directly? What if this code changes? --> + ```js class DefaultSequence { async handle(request: ParsedRequest, response: ServerResponse) { @@ -29,7 +30,7 @@ class DefaultSequence { const params = await this.parseParams(request, route); const result = await this.invoke(route, params); await this.send(response, result); - } catch(err) { + } catch (err) { await this.reject(response, err); } } @@ -38,21 +39,31 @@ class DefaultSequence { ## Elements -In the example above, `route`, `params`, and `result` are all Elements. When building sequences, you use LoopBack Elements to respond to a request: +In the example above, `route`, `params`, and `result` are all Elements. When +building sequences, you use LoopBack Elements to respond to a request: - [`Route`](http://apidocs.loopback.io/@loopback%2frest/#Route) -- [`Request`](http://apidocs.strongloop.com/loopback-next/) - (TBD) missing API docs link -- [`Response`](http://apidocs.strongloop.com/loopback-next/) - (TBD) missing API docs link +- [`Request`](http://apidocs.strongloop.com/loopback-next/) - (TBD) missing API + docs link +- [`Response`](http://apidocs.strongloop.com/loopback-next/) - (TBD) missing API + docs link - [`OperationRetVal`](http://apidocs.loopback.io/@loopback%2frest/#OperationRetval) -- [`Params`](http://apidocs.strongloop.com/loopback-next/) - (TBD) missing API docs link +- [`Params`](http://apidocs.strongloop.com/loopback-next/) - (TBD) missing API + docs link - [`OpenAPISpec`](http://apidocs.loopback.io/@loopback%2fopenapi-spec/) -- [`OperationError`](http://apidocs.strongloop.com/loopback-next/OperationError) - (TBD) missing API docs link -- [`OperationMeta`](http://apidocs.strongloop.com/loopback-next/OperationMeta) - (TBD) missing API docs link -- [`OperationRetMeta`](http://apidocs.strongloop.com/loopback-next/OperationRetMeta) - (TBD) missing API docs link +- [`OperationError`](http://apidocs.strongloop.com/loopback-next/OperationError) - + (TBD) missing API docs link +- [`OperationMeta`](http://apidocs.strongloop.com/loopback-next/OperationMeta) - + (TBD) missing API docs link +- [`OperationRetMeta`](http://apidocs.strongloop.com/loopback-next/OperationRetMeta) - + (TBD) missing API docs link ## Actions -Actions are JavaScript functions that only accept or return `Elements`. Since the input of one action (an Element) is the output of another action (Element) you can easily compose them. Below is an example that uses several built-in Actions: +Actions are JavaScript functions that only accept or return `Elements`. Since +the input of one action (an Element) is the output of another action (Element) +you can easily compose them. Below is an example that uses several built-in +Actions: ```js class MySequence extends DefaultSequence { @@ -71,7 +82,8 @@ class MySequence extends DefaultSequence { ## Custom Sequences -Most use cases can be accomplished with `DefaultSequence` or by slightly customizing it: +Most use cases can be accomplished with `DefaultSequence` or by slightly +customizing it: ```js class MySequence extends DefaultSequence { @@ -86,8 +98,8 @@ class MySequence extends DefaultSequence { } ``` -In order for LoopBack to use your custom sequence, you must register it -before starting your `Application`: +In order for LoopBack to use your custom sequence, you must register it before +starting your `Application`: ```js import {RestApplication, RestServer} from '@loopback/rest'; @@ -102,28 +114,31 @@ app.start(); ### Custom routing -A custom `Sequence` enables you to control exactly how requests are routed to endpoints such as `Controller` methods, plain JavaScript functions, Express applications, and so on. +A custom `Sequence` enables you to control exactly how requests are routed to +endpoints such as `Controller` methods, plain JavaScript functions, Express +applications, and so on. -This example demonstrates determining which endpoint (controller method) to invoke based on an API specification. +This example demonstrates determining which endpoint (controller method) to +invoke based on an API specification. ```ts -import {findRoute} from '@loopback/rest' +import {findRoute} from '@loopback/rest'; const API_SPEC = { basePath: '/', paths: { '/greet': { get: { - 'x-operation-name': "greet", + 'x-operation-name': 'greet', responses: { 200: { - description: "greeting text", - schema: { type: "string" } - } - } - } - } - } + description: 'greeting text', + schema: {type: 'string'}, + }, + }, + }, + }, + }, }; class MySequence extends DefaultSequence { @@ -151,25 +166,26 @@ To do this, we'll register a custom send action by binding a [Provider](http://apidocs.strongloop.com/@loopback%2fcontext/#Provider) to the `RestBindings.SequenceActions.SEND` key. -First, let's create our `CustomSendProvider` class, which will provide the -send function upon injection. +First, let's create our `CustomSendProvider` class, which will provide the send +function upon injection. {% include code-caption.html content="/src/providers/custom-send-provider.ts" %} **custom-send-provider.ts** + ```ts -import {Send, ServerResponse} from "@loopback/rest"; -import {Provider, BoundValue, inject} from "@loopback/context"; -import {writeResultToResponse, RestBindings} from "@loopback/rest"; +import {Send, ServerResponse} from '@loopback/rest'; +import {Provider, BoundValue, inject} from '@loopback/context'; +import {writeResultToResponse, RestBindings} from '@loopback/rest'; // Note: This is an example class; we do not provide this for you. -import {Formatter} from "../utils"; +import {Formatter} from '../utils'; export class CustomSendProvider implements Provider { // In this example, the injection key for formatter is simple constructor( - @inject('utils.formatter') public formatter: Formatter, - @inject(RestBindings.Http.REQUEST) public request: Request, - ) {} + @inject('utils.formatter') public formatter: Formatter, + @inject(RestBindings.Http.REQUEST) public request: Request, + ) {} value(): Send | Promise { // Use the lambda syntax to preserve the "this" scope for future calls! @@ -188,10 +204,9 @@ export class CustomSendProvider implements Provider { action(response: ServerResponse, result: OperationRetVal) { if (result) { // Currently, the headers interface doesn't allow arbitrary string keys! - const headers = this.request.headers as any || {}; + const headers = (this.request.headers as any) || {}; const header = headers.accept || 'application/json'; - const formattedResult = - this.formatter.convertToMimeType(result, header); + const formattedResult = this.formatter.convertToMimeType(result, header); response.setHeader('Content-Type', header); response.end(formattedResult); } else { @@ -209,6 +224,7 @@ Next, in our application class, we'll inject this provider on the `RestBindings.SequenceActions.SEND` key. {% include code-caption.html content="/src/application.ts" %} + ```ts import {Application} from '@loopback/core'; import {RestApplication, RestBindings} from '@loopback/rest'; @@ -249,28 +265,28 @@ Parsing and validating arguments from the request url, headers, and body. {% include content/tbd.html %} - - How to use `invoke()` in simple and advanced use cases. - - Explain what happens when you call `invoke()` - - Mention caching use case - - Can I call invoke multiple times? +- How to use `invoke()` in simple and advanced use cases. +- Explain what happens when you call `invoke()` +- Mention caching use case +- Can I call invoke multiple times? ### Writing the response {% include content/tbd.html %} - - Must call `sendResponse()` exactly once - - Streams? +- Must call `sendResponse()` exactly once +- Streams? ### Sending errors {% include content/tbd.html %} - - try/catch details +- try/catch details ### Keeping your Sequences {% include content/tbd.html %} - - Try and use existing actions - - Implement your own version of built in actions - - Publish reusable actions to npm +- Try and use existing actions +- Implement your own version of built in actions +- Publish reusable actions to npm diff --git a/docs/site/Server.md b/docs/site/Server.md index 6deee3928a1d..5c5660e853aa 100644 --- a/docs/site/Server.md +++ b/docs/site/Server.md @@ -10,12 +10,24 @@ summary: ## Overview -The [Server](https://apidocs.strongloop.com/@loopback%2fcore/#Server) interface defines the minimal required functions (start and stop) to implement for a LoopBack application. Servers in LoopBack 4 are used to represent implementations for inbound transports and/or protocols such as REST over http, gRPC over http2, graphQL over https, etc. They typically listen for requests on a specific port, handle them, and return appropriate responses. A single application can have multiple server instances listening on different ports and working with different protocols. - +The [Server](https://apidocs.strongloop.com/@loopback%2fcore/#Server) interface +defines the minimal required functions (start and stop) to implement for a +LoopBack application. Servers in LoopBack 4 are used to represent +implementations for inbound transports and/or protocols such as REST over http, +gRPC over http2, graphQL over https, etc. They typically listen for requests on +a specific port, handle them, and return appropriate responses. A single +application can have multiple server instances listening on different ports and +working with different protocols. ## Usage -LoopBack 4 currently offers the [`@loopback/rest`](https://github.com/strongloop/loopback-next/tree/master/packages/rest) package out of the box which provides an HTTP based server implementation handling requests over REST called `RestServer`. In order to use it in your application, all you need to do is have your application class extend `RestApplication`, and it will provide you with an instance of RestServer listening on port 3000. The following shows how to make use of it: +LoopBack 4 currently offers the +[`@loopback/rest`](https://github.com/strongloop/loopback-next/tree/master/packages/rest) +package out of the box which provides an HTTP based server implementation +handling requests over REST called `RestServer`. In order to use it in your +application, all you need to do is have your application class extend +`RestApplication`, and it will provide you with an instance of RestServer +listening on port 3000. The following shows how to make use of it: ```ts import {RestApplication, RestServer} from '@loopback/rest'; @@ -48,7 +60,10 @@ export class HelloWorldApp extends RestApplication { ### Add servers to application instance -You can add server instances to your application via the `app.server()` method individually or as an array using `app.servers()` method. Using `app.server()` allows you to uniquely name your binding key for your specific server instance. The following example demonstrates how to use these functions: +You can add server instances to your application via the `app.server()` method +individually or as an array using `app.servers()` method. Using `app.server()` +allows you to uniquely name your binding key for your specific server instance. +The following example demonstrates how to use these functions: ```ts import {Application} from '@loopback/core'; @@ -66,9 +81,11 @@ export class HelloWorldApp extends Application { } ``` -You can also add multiple servers in the constructor of your application class as shown [here](Application.md#servers). +You can also add multiple servers in the constructor of your application class +as shown [here](Application.md#servers). ## Next Steps - Learn about [Server-level Context](Context.md#server-level-context) -- Learn more about [creating your own servers!](Creating-components.md#creating-your-own-servers) +- Learn more about + [creating your own servers!](Creating-components.md#creating-your-own-servers) diff --git a/docs/site/Team.md b/docs/site/Team.md index e2f404415e4b..d55fd3f9e6fd 100644 --- a/docs/site/Team.md +++ b/docs/site/Team.md @@ -8,13 +8,13 @@ permalink: /doc/en/lb4/Team.html summary: The people working on LoopBack 4 --- -# IBM +## IBM - Core members/leads - All internal members (including docs, etc) -# Community contributors +## Community contributors -- loopback-maintaineres +- loopback-maintainers - loopback-swagger-maintainers - etc diff --git a/docs/site/Testing-Your-Extensions.md b/docs/site/Testing-Your-Extensions.md index eed779a858bf..8057d918cc94 100644 --- a/docs/site/Testing-Your-Extensions.md +++ b/docs/site/Testing-Your-Extensions.md @@ -10,20 +10,23 @@ summary: ## Overview -LoopBack 4 extensions are often used by other teams. A thorough test suite for your extension brings powerful benefits to all your users, including: +LoopBack 4 extensions are often used by other teams. A thorough test suite for +your extension brings powerful benefits to all your users, including: -* Validating the behavior of the extension -* Preventing unwanted changes to the API or functionality of the extension -* Providing working samples and code snippets that serve as functional documentation for your users +- Validating the behavior of the extension +- Preventing unwanted changes to the API or functionality of the extension +- Providing working samples and code snippets that serve as functional + documentation for your users ## Project Setup We recommend that you use `@loopback/cli` to create the extension, as it installs several tools you can use for testing, such as `mocha`, assertion libraries, linters, etc. -The `@loopback/cli` includes the `mocha` automated test runner and a -`test` folder containing recommended folders for various types of tests. -`Mocha` is enabled by default if `@loopback/cli` is used to -create the extension project. The `@loopback/cli` installs and configures `mocha`, creates the `test` folder, and also enters a `test` command in your `package.json`. +The `@loopback/cli` includes the `mocha` automated test runner and a `test` +folder containing recommended folders for various types of tests. `Mocha` is +enabled by default if `@loopback/cli` is used to create the extension project. +The `@loopback/cli` installs and configures `mocha`, creates the `test` folder, +and also enters a `test` command in your `package.json`. Assertion libraries such as [ShouldJS](http://shouldjs.github.io/) (as `expect`), [SinonJS](http://sinonjs.org/), and a test sandbox are made available @@ -31,38 +34,46 @@ through the convenient `@loopback/testlab` package. The `testlab` is also instal ### Manual Setup - Using Mocha -* Install `mocha` by running `npm i --save-dev mocha`. This will save the `mocha` package in -`package.json` as well. -* Under `scripts` in `package.json` add the following: -`test: npm run build && mocha --recursive ./dist/test` +- Install `mocha` by running `npm i --save-dev mocha`. This will save the + `mocha` package in `package.json` as well. +- Under `scripts` in `package.json` add the following: + `test: npm run build && mocha --recursive ./dist/test` ## Types of tests -A comprehensive test suite tests many aspects of your code. We recommend that you write unit, integration, and acceptance tests to test your application from a variety of perspectives. Comprehensive testing -ensures correctness, integration, and future compatibility. +A comprehensive test suite tests many aspects of your code. We recommend that +you write unit, integration, and acceptance tests to test your application from +a variety of perspectives. Comprehensive testing ensures correctness, +integration, and future compatibility. You may use any development methodology you want to write your extension; the -important thing is to test it with an automated test suite. In Traditional development methodology, you write the code first and then write the tests. In Test-driven development methodology, you write the tests first, see them fail, then write the code to pass the tests. +important thing is to test it with an automated test suite. In Traditional +development methodology, you write the code first and then write the tests. In +Test-driven development methodology, you write the tests first, see them fail, +then write the code to pass the tests. ### Unit Tests -A unit test tests the smallest unit of code possible, which in this case is a function. -Unit tests ensure variable and state changes by outside actors don't affect the -results. [Test doubles](https://en.wikipedia.org/wiki/Test_double) should be -used to substitute function dependencies. You can learn more about test doubles -and Unit testing here: [Testing your Application: Unit testing](Testing-your-application.md#unit-testing). +A unit test tests the smallest unit of code possible, which in this case is a +function. Unit tests ensure variable and state changes by outside actors don't +affect the results. [Test doubles](https://en.wikipedia.org/wiki/Test_double) +should be used to substitute function dependencies. You can learn more about +test doubles and Unit testing here: +[Testing your Application: Unit testing](Testing-your-application.md#unit-testing). #### Controllers At its core, a controller is a simple class that is responsible for related -actions on an object. Performing unit tests on a controller in an extension is the same as -performing unit tests on a controller in an application. +actions on an object. Performing unit tests on a controller in an extension is +the same as performing unit tests on a controller in an application. -To test a controller, you instantiate a new instance of your controller -class and test a function, providing a test double for constructor arguments as -needed. Following are examples that illustrate how to perform a unit test on a controller class: +To test a controller, you instantiate a new instance of your controller class +and test a function, providing a test double for constructor arguments as +needed. Following are examples that illustrate how to perform a unit test on a +controller class: **`src/controllers/ping.controller.ts`** + ```ts export class PingController { @get('/ping') @@ -73,6 +84,7 @@ export class PingController { ``` **`test/unit/controllers/ping.controller.ts`** + ```ts import {PingController} from '../../..'; import {expect} from '@loopback/testlab'; @@ -84,7 +96,7 @@ describe('PingController() unit', () => { expect(result).to.equal('You pinged with undefined'); }); - it('pings with msg \'hello\'', () => { + it("pings with msg 'hello'", () => { const controller = new PingController(); const result = controller.ping('hello'); expect(result).to.equal('You pinged with hello'); @@ -92,19 +104,22 @@ describe('PingController() unit', () => { }); ``` -You can find an advanced example on testing controllers in [Unit test your Controllers](Testing-your-application.md#unit-test-your-controllers). +You can find an advanced example on testing controllers in +[Unit test your Controllers](Testing-your-application.md#unit-test-your-controllers). #### Decorators The recommended usage of a decorator is to store metadata about a class or a -class method. The decorator implementation usually provides a function -to retrieve the related metadata based on the class name and method name. -For a unit test for a decorator, it is important to test that that it stores and retrieves the correct metadata. *The retrieval gets tested as a -result of validating whether the metadata was stored or not.* +class method. The decorator implementation usually provides a function to +retrieve the related metadata based on the class name and method name. For a +unit test for a decorator, it is important to test that that it stores and +retrieves the correct metadata. _The retrieval gets tested as a result of +validating whether the metadata was stored or not._ Following is an example for testing a decorator: **`src/decorators/test.decorator.ts`** + ```ts export function test(file: string) { return function(target: Object, methodName: string): void { @@ -130,6 +145,7 @@ export function getTestMetadata( ``` **`test/unit/decorators/test.decorator.ts`** + ```ts import {test, getTestMetadata} from '../../..'; import {expect} from '@loopback/testlab'; @@ -153,16 +169,18 @@ describe('test.decorator (unit)', () => { A Mixin is a TypeScript function that extends the `Application` Class, adding new constructor properties, methods, etc. It is difficult to write a unit test for a Mixin without the `Application` Class dependency. The recommended practice -is to write an integration test is described in [Mixin Integration Tests](#mixin-integration-tests). +is to write an integration test is described in +[Mixin Integration Tests](#mixin-integration-tests). #### Providers A Provider is a Class that implements the `Provider` interface. This interface requires the Class to have a `value()` function. A unit test for a provider -should test the `value()` function by instantiating a new `Provider` class, using -a test double for any constructor arguments. +should test the `value()` function by instantiating a new `Provider` class, +using a test double for any constructor arguments. **`src/providers/random-number.provider.ts`** + ```ts import {Provider} from '@loopback/context'; @@ -176,6 +194,7 @@ export class RandomNumberProvider implements Provider { ``` **`test/unit/providers/random-number.unit.test.ts`** + ```ts import {RandomNumberProvider} from '../../..'; import {expect} from '@loopback/testlab'; @@ -187,29 +206,31 @@ describe('RandomNumberProvider (unit)', () => { expect(random).to.be.a.Number(); expect(random).to.equalOneOf([1, 2, 3]); - }) -}) + }); +}); ``` #### Repositories -*This section will be provided in a future version.* +_This section will be provided in a future version._ ### Integration Tests An integration test plays an important part in your test suite by ensuring your -extension artifacts work together as well as `@loopback`. It is -recommended to test two items together and substitute other integrations as test doubles so it becomes apparent where the integration errors may occur. +extension artifacts work together as well as `@loopback`. It is recommended to +test two items together and substitute other integrations as test doubles so it +becomes apparent where the integration errors may occur. #### Mixin Integration Tests -A Mixin extends a base Class by returning an anonymous class. Thus, a Mixin is tested by actually -using the Mixin with its base Class. Since this requires two Classes to work -together, an integration test is needed. A Mixin test checks that -new or overridden methods exist and work as expected in the new Mixed class. Following is an example for an -integration test for a Mixin: +A Mixin extends a base Class by returning an anonymous class. Thus, a Mixin is +tested by actually using the Mixin with its base Class. Since this requires two +Classes to work together, an integration test is needed. A Mixin test checks +that new or overridden methods exist and work as expected in the new Mixed +class. Following is an example for an integration test for a Mixin: **`src/mixins/time.mixin.ts`** + ```ts import {Constructor} from '@loopback/context'; export function TimeMixin>(superClass: T) { @@ -234,6 +255,7 @@ export function TimeMixin>(superClass: T) { ``` **`test/integration/mixins/time.intg.test.ts`** + ```ts import {expect} from '@loopback/testlab'; import {Application} from '@loopback/core'; @@ -267,12 +289,12 @@ describe('TimeMixin (integration)', () => { ### Acceptance Test -An Acceptance test for an extension is a comprehensive test written -end-to-end. Acceptance tests cover the user scenarios. An acceptance -test uses all of the extension artifacts such as decorators, mixins, -providers, repositories, etc. No test doubles are needed for an -Acceptance test. This is a black box test where you don't know or care about the -internals of the extensions. You will be using the extension as if you were the consumer. +An Acceptance test for an extension is a comprehensive test written end-to-end. +Acceptance tests cover the user scenarios. An acceptance test uses all of the +extension artifacts such as decorators, mixins, providers, repositories, etc. No +test doubles are needed for an Acceptance test. This is a black box test where +you don't know or care about the internals of the extensions. You will be using +the extension as if you were the consumer. Due to the complexity of an Acceptance test, there is no example given here. Have a look at [loopback4-example-log-extension](https://github.com/strongloop/loopback-next/tree/master/packages/example-log-extension) diff --git a/docs/site/Testing-the-API.md b/docs/site/Testing-the-API.md index e270bd1b0cce..7d9afedaac09 100644 --- a/docs/site/Testing-the-API.md +++ b/docs/site/Testing-the-API.md @@ -8,26 +8,35 @@ permalink: /doc/en/lb4/Testing-the-API.html summary: --- -{% include previous.md content=" -This article continues off from [Defining and validating the API](./Defining-and-validating-the-API.md). -" %} +{% include previous.md content=" This article continues off +from [Defining and validating the API](./Defining-and-validating-the-API.md). " +%} {% include important.html content="The top-down approach for building LoopBack applications is not yet fully supported. Therefore, the steps outlined in this page are outdated and may not work out of the box. They will be revisited after -our MVP release. -"%} +our MVP release. "%} ## Smoke test API input/output -Once you confirm that the API specification is valid, it's time to verify that the application implements the API as you have specified it. The input/output testing described below uses [Dredd](https://www.npmjs.com/package/dredd), specifically `hello-world` in this section. Concrete sample code of `hello-world` can be found in the [hello-world tutorial](https://github.com/strongloop/loopback-next-hello-world) repository. Although the sample code includes a validated API spec and fully functional `hello-world` controller, let's pretend the controller is completely empty. Try it yourself by cloning the repository from GitHub. +Once you confirm that the API specification is valid, it's time to verify that +the application implements the API as you have specified it. The input/output +testing described below uses [Dredd](https://www.npmjs.com/package/dredd), +specifically `hello-world` in this section. Concrete sample code of +`hello-world` can be found in the +[hello-world tutorial](https://github.com/strongloop/loopback-next-hello-world) +repository. Although the sample code includes a validated API spec and fully +functional `hello-world` controller, let's pretend the controller is completely +empty. Try it yourself by cloning the repository from GitHub. For input/output testing, you are going to create three parts: + 1. Input data definition. 2. Expected output response definition. 3. Test code. -Parts one and two are included in the API specification. The input data is given as `x-example` as follows: +Parts one and two are included in the API specification. The input data is given +as `x-example` as follows: ```js "x-example": "Ted" @@ -41,77 +50,85 @@ The expected output as `examples`: } ``` -The `Dredd` module reserves `x-example` to set the input parameter. the OpenAPI standard defines the [`examples` object](https://swagger.io/specification/#examples-object-92) as a map from `MIME type` to the content value. Here, it's `text/plain` MIME type. As you see, they are a pair: When you change the input value `x-example`, you must change `examples` value as well. +The `Dredd` module reserves `x-example` to set the input parameter. the OpenAPI +standard defines the +[`examples` object](https://swagger.io/specification/#examples-object-92) as a +map from `MIME type` to the content value. Here, it's `text/plain` MIME type. As +you see, they are a pair: When you change the input value `x-example`, you must +change `examples` value as well. The complete `hello-world` API specification is the following: ```js -export const controllerSpec = -{ +export const controllerSpec = { swagger: '2.0', basePath: '/', info: { title: 'LoopBack Application', version: '1.0.0', }, - "paths": { - "/helloworld": { - "get": { - "x-operation-name": "helloWorld", - "parameters": [ + paths: { + '/helloworld': { + get: { + 'x-operation-name': 'helloWorld', + parameters: [ { - "name": "name", - "in": "query", - "description": "Your name.", - "required": false, - "type": "string", - "x-example": "Ted" - } + name: 'name', + in: 'query', + description: 'Your name.', + required: false, + type: 'string', + 'x-example': 'Ted', + }, ], - "responses": { - "200": { - "description": "Returns a hello world with your (optional) name.", - "examples": { - "text/plain": "Hello world Ted." - } - } - } - } - } - } -} + responses: { + '200': { + description: 'Returns a hello world with your (optional) name.', + examples: { + 'text/plain': 'Hello world Ted.', + }, + }, + }, + }, + }, + }, +}; ``` -The third piece is the test code. To initialize the test environment, you need to create a `Dredd` instance specifying the configuration. There are two required fields in the configuration object: `server` and `options.path`. - `localhostAndPort + \'/swagger.json\'` is the predefined end point LoopBack 4 uses for the client to access the API specification of the service API. +The third piece is the test code. To initialize the test environment, you need +to create a `Dredd` instance specifying the configuration. There are two +required fields in the configuration object: `server` and `options.path`. +`localhostAndPort + \'/swagger.json\'` is the predefined end point LoopBack 4 +uses for the client to access the API specification of the service API. ```js - async function initEnvironment() { - // By default, the port is set to 3000. - const app: Application = new HelloWorldApp(); - const server = app.getServer(RestServer); - // For testing, you'll let the OS pick an available port by setting - // RestBindings.PORT to 0. - server.bind(RestBindings.PORT).to(0); - // app.start() starts up the HTTP server and binds the acquired port - // number to RestBindings.PORT. - await app.start(); - // Get the real port number. - const port: number = await server.get(RestBindings.PORT); - const localhostAndPort: string = 'http://localhost:' + port; - const config: object = { - server: localhostAndPort, // base path to the end points - options: { - level: 'fail', // report 'fail' case only - silent: false, // false for helpful debugging info - path: [localhostAndPort + '/swagger.json'], // to download apiSpec from the service - } - }; - dredd = new Dredd(config); - } +async function initEnvironment() { + // By default, the port is set to 3000. + const app: Application = new HelloWorldApp(); + const server = app.getServer(RestServer); + // For testing, you'll let the OS pick an available port by setting + // RestBindings.PORT to 0. + server.bind(RestBindings.PORT).to(0); + // app.start() starts up the HTTP server and binds the acquired port + // number to RestBindings.PORT. + await app.start(); + // Get the real port number. + const port: number = await server.get(RestBindings.PORT); + const localhostAndPort: string = 'http://localhost:' + port; + const config: object = { + server: localhostAndPort, // base path to the end points + options: { + level: 'fail', // report 'fail' case only + silent: false, // false for helpful debugging info + path: [localhostAndPort + '/swagger.json'], // to download apiSpec from the service + }, + }; + dredd = new Dredd(config); +} ``` -Since the specification above includes definition of input data and the expected output, you have all the pieces to write the test code: +Since the specification above includes definition of input data and the expected +output, you have all the pieces to write the test code: ```js describe('Api Spec End Points', () => { @@ -178,7 +195,10 @@ complete: 0 passing, 1 failing, 0 errors, 0 skipped, 1 total complete: Tests took 27ms ``` -The test report correctly shows that the input is `name=Ted` and the expected result is `Hello world Ted`, but the actual result was `statusCode: 500` which does not match the expectation. When the `hello-world` API is implemented, the result would be something like the following: +The test report correctly shows that the input is `name=Ted` and the expected +result is `Hello world Ted`, but the actual result was `statusCode: 500` which +does not match the expectation. When the `hello-world` API is implemented, the +result would be something like the following: ```shell $ npm test @@ -189,12 +209,17 @@ complete: 1 passing, 0 failing, 0 errors, 0 skipped, 1 total complete: Tests took 21ms ``` -It's a powerful proposition to use the API specification not only for API declaration but for test case declaration. The discussion so far paves the road to "automated controller wireframe-code generation and test-driven development" based on the OpenAPI standard. +It's a powerful proposition to use the API specification not only for API +declaration but for test case declaration. The discussion so far paves the road +to "automated controller wireframe-code generation and test-driven development" +based on the OpenAPI standard. -At this point, you are ready to make these tests pass by coding up your business logic. +At this point, you are ready to make these tests pass by coding up your business +logic. -Please refer to [Perform an auto-generated smoke test of your REST API](Testing-your-application.md#perform-an-auto-generated-smoke-test-of-your-rest-api) from [Testing your application](Testing-your-application.md) for more details. +Please refer to +[Perform an auto-generated smoke test of your REST API](Testing-your-application.md#perform-an-auto-generated-smoke-test-of-your-rest-api) +from [Testing your application](Testing-your-application.md) for more details. {% include next.html content= " -[Defining your testing strategy](./Defining-your-testing-strategy.md) -" %} +[Defining your testing strategy](./Defining-your-testing-strategy.md) " %} diff --git a/docs/site/Testing-your-application.md b/docs/site/Testing-your-application.md index 9e486adf9c6f..b6ea4a1542a6 100644 --- a/docs/site/Testing-your-application.md +++ b/docs/site/Testing-your-application.md @@ -20,21 +20,23 @@ A thorough automated test suite is important because it: ### Types of tests -We encourage writing tests from a few perspectives, mainly [black-box testing](https://en.wikipedia.org/wiki/Black-box_testing) -(acceptance) and [white-box testing](https://en.wikipedia.org/wiki/White-box_testing) +We encourage writing tests from a few perspectives, mainly +[black-box testing](https://en.wikipedia.org/wiki/Black-box_testing) +(acceptance) and +[white-box testing](https://en.wikipedia.org/wiki/White-box_testing) (integration and unit). Tests are usually written using typical patterns such as [`arrange/act/assert`](https://msdn.microsoft.com/en-us/library/hh694602.aspx#Anchor_3) -or [`given/when/then`](https://martinfowler.com/bliki/GivenWhenThen.html). -Both styles work well, so pick one that you're comfortable with and -start writing tests! +or [`given/when/then`](https://martinfowler.com/bliki/GivenWhenThen.html). Both +styles work well, so pick one that you're comfortable with and start writing +tests! -For an introduction to automated testing, see [Define your testing strategy](Defining-your-testing-strategy.md). -For a step-by-step tutorial, see [Incrementally implement features](Implementing-features.md). +For an introduction to automated testing, see +[Define your testing strategy](Defining-your-testing-strategy.md). For a +step-by-step tutorial, see +[Incrementally implement features](Implementing-features.md). -{% include important.html content=" -A great test suite requires you to think smaller and favor fast and focused -unit tests over slow end-to-end tests. -" %} +{% include important.html content=" A great test suite requires you to think +smaller and favor fast and focused unit tests over slow end-to-end tests. " %} This article is a reference guide for common types of tests and test helpers. @@ -48,7 +50,8 @@ In addition to a test runner, the test suites generally require: - An assertion library (we recommend [Should.js](https://shouldjs.github.io)). - A library for making HTTP calls and verifying their results (we recommend [supertest](https://github.com/visionmedia/supertest)). -- A library for creating test doubles (we recommend [Sinon.JS](http://sinonjs.org/)). +- A library for creating test doubles (we recommend + [Sinon.JS](http://sinonjs.org/)). The [@loopback/testlab](https://www.npmjs.com/package/@loopback/testlab) module integrates these packages and makes them easy to use together with LoopBack. @@ -56,8 +59,8 @@ integrates these packages and makes them easy to use together with LoopBack. ### Set up testing infrastructure with LoopBack CLI LoopBack applications that have been generated using the `lb4 app` command from -`@loopback/cli` come with `@loopback/testlab` and `mocha` as a default, -so no other testing infrastructure setup is needed. +`@loopback/cli` come with `@loopback/testlab` and `mocha` as a default, so no +other testing infrastructure setup is needed. ### Setup testing infrastructure manually @@ -86,28 +89,27 @@ Your `package.json` should then look something like this: ## Data handling -Tests accessing a real database often require existing data. For example, -a method listing all products needs some products in the database; a method -to create a new product instance must determine which properties are required -and any restrictions on their values. There are various approaches to address -this issue. Many of them unfortunately make the test suite difficult -to understand, difficult to maintain, and prone to test failures unrelated -to the changes made. +Tests accessing a real database often require existing data. For example, a +method listing all products needs some products in the database; a method to +create a new product instance must determine which properties are required and +any restrictions on their values. There are various approaches to address this +issue. Many of them unfortunately make the test suite difficult to understand, +difficult to maintain, and prone to test failures unrelated to the changes made. Our approach to data handling, based on our experience, is described in this section. ### Clean the database before each test -Start with a clean database before each test. This may seem -counter-intuitive: why not reset the database after the test has finished? -When a test fails and the database is cleaned after the test has finished, -then it's difficult to observe what was stored in the database and why the test -failed. When the database is cleaned in the beginning, then any failing test -will leave the database in the state that caused the test to fail. +Start with a clean database before each test. This may seem counter-intuitive: +why not reset the database after the test has finished? When a test fails and +the database is cleaned after the test has finished, then it's difficult to +observe what was stored in the database and why the test failed. When the +database is cleaned in the beginning, then any failing test will leave the +database in the state that caused the test to fail. -To clean the database before each test, set up a `beforeEach` hook to call -a helper method; for example: +To clean the database before each test, set up a `beforeEach` hook to call a +helper method; for example: {% include code-caption.html content="test/helpers/database.helpers.ts" %} @@ -121,7 +123,8 @@ export async function givenEmptyDatabase() { } ``` -{% include code-caption.html content="test/integration/controllers/product.controller.test.ts" %} +{% include code-caption.html +content="test/integration/controllers/product.controller.test.ts" %} ```ts // in your test file @@ -136,23 +139,23 @@ describe('ProductController (integration)', () => { ### Use test data builders To avoid duplicating code for creating model data that is complete with required -properties, use shared [test data builders](http://www.natpryce.com/articles/000714.html). -This enables tests to provide the small subset of properties that is strictly -required by the tested scenario. Using shared test builders will help your tests -to be: +properties, use shared +[test data builders](http://www.natpryce.com/articles/000714.html). This enables +tests to provide the small subset of properties that is strictly required by the +tested scenario. Using shared test builders will help your tests to be: - Easier to understand, since it's immediately clear what model properties are - relevant to the tests. If the tests set the required properties, - it is difficult to tell whether the properties are actually - relevant to the tested scenario. + relevant to the tests. If the tests set the required properties, it is + difficult to tell whether the properties are actually relevant to the tested + scenario. -- Easier to maintain. As your data model evolves, you will need to add - more required properties. If the tests build the model instance data manually, - all the tests must be manually updated to set a new required property. - With a shared test data builder, you update a single location with the new - property. +- Easier to maintain. As your data model evolves, you will need to add more + required properties. If the tests build the model instance data manually, all + the tests must be manually updated to set a new required property. With a + shared test data builder, you update a single location with the new property. -See [@loopback/openapi-spec-builder](https://www.npmjs.com/package/@loopback/openapi-spec-builder) +See +[@loopback/openapi-spec-builder](https://www.npmjs.com/package/@loopback/openapi-spec-builder) for an example of how to apply this design pattern for building OpenAPI Spec documents. @@ -184,36 +187,35 @@ export async function givenProduct(data?: Partial) { ### Avoid sharing the same data for multiple tests -It's tempting to define a small set of data to be shared by all tests. -For example, in an e-commerce application, you might pre-populate the database -with a few categories, some products, an admin user and a customer. -This approach has several downsides: +It's tempting to define a small set of data to be shared by all tests. For +example, in an e-commerce application, you might pre-populate the database with +a few categories, some products, an admin user and a customer. This approach has +several downsides: - When trying to understand any individual test, it's difficult to tell what part of the pre-populated data is essential for the test and what's irrelevant. For example, in a test checking the method counting the number of - products in a category using a pre-populated category "Stationery", - is it important that "Stationery" contains nested sub-categories or is that - fact irrelevant? If it's irrelevant, then what are the other tests that - depend on it? + products in a category using a pre-populated category "Stationery", is it + important that "Stationery" contains nested sub-categories or is that fact + irrelevant? If it's irrelevant, then what are the other tests that depend on + it? - As the application grows and new features are added, it's easier to add more properties to existing model instances rather than create new instances using - only the properties required by the new features. For example, when adding - a category image, it's easier to add image to an existing category - "Stationery" and perhaps keep another category "Groceries" without any image, - rather than creating two new categories "CategoryWithAnImage" and - "CategoryMissingImage". This further amplifies the previous problem, - because it's not clear that "Groceries" is the category that should be used - by tests requiring a category with no image - the category name does not - provide any hints on that. + only the properties required by the new features. For example, when adding a + category image, it's easier to add image to an existing category "Stationery" + and perhaps keep another category "Groceries" without any image, rather than + creating two new categories "CategoryWithAnImage" and "CategoryMissingImage". + This further amplifies the previous problem, because it's not clear that + "Groceries" is the category that should be used by tests requiring a category + with no image - the category name does not provide any hints on that. - As the shared dataset grows (together with the application), the time required to bring the database into its initial state grows too. Instead of running a - few "DELETE ALL" queries before each test (which is relatively fast), - you may have to run tens or hundreds of different commands used to create - different model instances, thus triggering slow index rebuilds along the way - and slowing down the test suite considerably. + few "DELETE ALL" queries before each test (which is relatively fast), you may + have to run tens or hundreds of different commands used to create different + model instances, thus triggering slow index rebuilds along the way and slowing + down the test suite considerably. Use the test data builders described in the previous section to populate your database with the data specific to your test only. @@ -274,7 +276,8 @@ async function givenAdminAndCustomer() { Unit tests are considered "white-box" tests because they use an "inside-out" approach where the tests know about the internals and control all the variables of the system being tested. Individual units are tested in isolation and their -dependencies are replaced with [Test doubles](https://en.wikipedia.org/wiki/Test_double). +dependencies are replaced with +[Test doubles](https://en.wikipedia.org/wiki/Test_double). ### Use test doubles @@ -282,48 +285,45 @@ Test doubles are functions or objects that look and behave like the real variants used in production, but are actually simplified versions that give the test more control of the behavior. For example, reproducing the situation where reading from a file failed because of a hard-drive error is pretty much -impossible. However, using a test double to simulate the file-system API -will provide control over what each call returns. +impossible. However, using a test double to simulate the file-system API will +provide control over what each call returns. -[Sinon.JS](http://sinonjs.org/) has become the de-facto standard for -test doubles in Node.js and JavaScript/TypeScript in general. -The `@loopback/testlab` package comes with Sinon preconfigured with TypeScript -type definitions and integrated with Should.js assertions. +[Sinon.JS](http://sinonjs.org/) has become the de-facto standard for test +doubles in Node.js and JavaScript/TypeScript in general. The `@loopback/testlab` +package comes with Sinon preconfigured with TypeScript type definitions and +integrated with Should.js assertions. There are three kinds of test doubles provided by Sinon.JS: - [Test spies](http://sinonjs.org/releases/v4.0.1/spies/) are functions that record arguments, the return value, the value of `this`, and exceptions thrown - (if any) for all its calls. There are two types of spies: Some are - anonymous functions, while others wrap methods that already exist in the system - under test. + (if any) for all its calls. There are two types of spies: Some are anonymous + functions, while others wrap methods that already exist in the system under + test. - [Test stubs](http://sinonjs.org/releases/v4.0.1/stubs/) are functions (spies) with pre-programmed behavior. As spies, stubs can be either anonymous, or wrap - existing functions. When wrapping an existing function with a stub, the original - function is not called. + existing functions. When wrapping an existing function with a stub, the + original function is not called. -- [Test mocks](http://sinonjs.org/releases/v4.0.1/mocks/) - (and mock expectations) are fake methods (like spies) with pre-programmed - behavior (like stubs) as well as pre-programmed expectations. A mock will fail - your test if it is not used as expected. +- [Test mocks](http://sinonjs.org/releases/v4.0.1/mocks/) (and mock + expectations) are fake methods (like spies) with pre-programmed behavior (like + stubs) as well as pre-programmed expectations. A mock will fail your test if + it is not used as expected. -{% include note.html content=" -We recommend against using test mocks. With test mocks, the expectations must -be defined before the tested scenario is executed, which breaks the -recommended test layout 'arrange-act-assert' (or 'given-when-then') and also -produces code that's difficult to comprehend. -" %} +{% include note.html content=" We recommend against using test mocks. With test +mocks, the expectations must be defined before the tested scenario is executed, +which breaks the recommended test layout 'arrange-act-assert' (or +'given-when-then') and also produces code that's difficult to comprehend. " %} #### Create a stub Repository -When writing an application that accesses data in a database, the best -practice is to use [repositories](Repositories.md) to encapsulate all -data-access/persistence-related code. Other parts of the application -(typically [controllers](Controllers.md)) can then depend on these -repositories for data access. To test Repository dependents -(for example, Controllers) in isolation, we need to provide a test double, -usually as a test stub. +When writing an application that accesses data in a database, the best practice +is to use [repositories](Repositories.md) to encapsulate all +data-access/persistence-related code. Other parts of the application (typically +[controllers](Controllers.md)) can then depend on these repositories for data +access. To test Repository dependents (for example, Controllers) in isolation, +we need to provide a test double, usually as a test stub. In traditional object-oriented languages like Java or C#, to enable unit tests to provide a custom implementation of the repository API, the controller needs @@ -351,11 +351,11 @@ describe('ProductController', () => { ``` In your unit tests, you will usually want to program the behavior of stubbed -methods (what they should return) and then verify that the Controller -(unit under test) called the right method with the correct arguments. +methods (what they should return) and then verify that the Controller (unit +under test) called the right method with the correct arguments. -Configure stub's behavior at the beginning of your unit test -(in the "arrange" or "given" section): +Configure stub's behavior at the beginning of your unit test (in the "arrange" +or "given" section): ```ts // repository.find() will return a promise that @@ -364,8 +364,8 @@ const findStub = repository.find as sinon.SinonStub; findStub.resolves([{id: 1, name: 'Pen'}]); ``` -Verify how was the stubbed method executed at the end of your unit test -(in the "assert" or "then" section): +Verify how was the stubbed method executed at the end of your unit test (in the +"assert" or "then" section): ```ts // expect that repository.find() was called with the first @@ -373,15 +373,14 @@ Verify how was the stubbed method executed at the end of your unit test sinon.assert.calledWithMatch({where: {id: 1}}); ``` -See [Unit test your controllers](#unit-test-your-controllers) for a -full example. +See [Unit test your controllers](#unit-test-your-controllers) for a full +example. #### Create a stub Service {% include content/tbd.html %} -The initial beta release does not include Services as a first-class -feature. +The initial beta release does not include Services as a first-class feature. ### Unit test your Controllers @@ -389,12 +388,13 @@ Unit tests should apply to the smallest piece of code possible to ensure that other variables and state changes do not pollute the result. A typical unit test creates a controller instance with dependencies replaced by test doubles and directly calls the tested method. The example below gives the controller a stub -implementation of its repository dependency, ensures the controller -calls the repository's `find()` method with a correct query, and returns back -the query results. See [Create a stub repository](#create-a-stub-repository) -for a detailed explanation. +implementation of its repository dependency, ensures the controller calls the +repository's `find()` method with a correct query, and returns back the query +results. See [Create a stub repository](#create-a-stub-repository) for a +detailed explanation. -{% include code-caption.html content="test/unit/controllers/product.controller.test.ts" %} +{% include code-caption.html +content="test/unit/controllers/product.controller.test.ts" %} ```ts import {expect, sinon} from '@loopback/testlab'; @@ -477,22 +477,24 @@ describe('Person (unit)', () => { ``` Writing a unit test for custom repository methods is not as straightforward -because `CrudRepository` is based on legacy [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler) +because `CrudRepository` is based on legacy +[loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler) which was not designed with dependency injection in mind. Instead, use -integration tests to verify the implementation of custom repository methods. -For more information, refer to [Test your repositories against a real database](#test-your-repositories-against-a-real-database) +integration tests to verify the implementation of custom repository methods. For +more information, refer to +[Test your repositories against a real database](#test-your-repositories-against-a-real-database) in [Integration Testing](#integration-testing). ### Unit test your Sequence -While it's possible to test a custom Sequence class in isolation, it's better -to rely on acceptance-level tests in this exceptional case. The reason is that -a custom Sequence class typically has many dependencies (which can make test -setup long and complex), and at the same time it provides very little -functionality on top of the injected sequence actions. Bugs are much more likely -to be caused by the way the real sequence action implementations interact -together (which is not covered by unit tests), instead of the Sequence code -itself (which is the only thing covered). +While it's possible to test a custom Sequence class in isolation, it's better to +rely on acceptance-level tests in this exceptional case. The reason is that a +custom Sequence class typically has many dependencies (which can make test setup +long and complex), and at the same time it provides very little functionality on +top of the injected sequence actions. Bugs are much more likely to be caused by +the way the real sequence action implementations interact together (which is not +covered by unit tests), instead of the Sequence code itself (which is the only +thing covered). See [Test Sequence customizations](#test-sequence-customizations) in [Acceptance Testing](#acceptance-end-to-end-testing). @@ -506,7 +508,8 @@ The initial beta release does not include Services as a first-class feature. See the following related GitHub issues: - Define services to represent interactions with REST APIs, SOAP Web Services, - gRPC services, and more: [#522](https://github.com/strongloop/loopback-next/issues/522) + gRPC services, and more: + [#522](https://github.com/strongloop/loopback-next/issues/522) - Guide: Services [#451](https://github.com/strongloop/loopback-next/issues/451) ## Integration testing @@ -535,7 +538,8 @@ Integration tests are one of the places to put the best practices in Here is an example showing how to write an integration test for a custom repository method `findByName`: -{% include code-caption.html content= "test/integration/repositories/category.repository.test.ts" %} +{% include code-caption.html content= +"test/integration/repositories/category.repository.test.ts" %} ```ts import { @@ -567,10 +571,11 @@ describe('CategoryRepository (integration)', () => { Integration tests running controllers with real repositories are important to verify that the controllers use the repository API correctly, and that the commands and queries produce expected results when executed on a real database. -These tests are similar to repository tests with controllers added as -another ingredient. +These tests are similar to repository tests with controllers added as another +ingredient. -{% include code-caption.html content= "test/integration/controllers/product.controller.test.ts" %} +{% include code-caption.html content= +"test/integration/controllers/product.controller.test.ts" %} ```ts import {expect} from '@loopback/testlab'; @@ -610,9 +615,10 @@ requests) as the clients and consumers of your API will do, and verify that the results returned by the system match the expected results. Typically, acceptance tests start the application, make HTTP requests to the -server, and verify the returned response. LoopBack uses [supertest](https://github.com/visionmedia/supertest) -to create test code that simplifies both the execution of HTTP requests and the -verification of responses. Remember to follow the best practices from +server, and verify the returned response. LoopBack uses +[supertest](https://github.com/visionmedia/supertest) to create test code that +simplifies both the execution of HTTP requests and the verification of +responses. Remember to follow the best practices from [Data handling](#data-handling) when setting up your database for tests: - Clean the database before each test @@ -621,13 +627,13 @@ verification of responses. Remember to follow the best practices from ### Validate your OpenAPI specification -The OpenAPI specification is a cornerstone of applications that provide -REST APIs. It enables API consumers to leverage a whole ecosystem of related -tooling. To make the spec useful, you must ensure it's a valid OpenAPI Spec -document, ideally in an automated way that's an integral part of regular CI -builds. LoopBack's [testlab](https://www.npmjs.com/package/@loopback/testlab) -module provides a helper method `validateApiSpec` that builds on top of the -popular [swagger-parser](https://www.npmjs.com/package/swagger-parser) package. +The OpenAPI specification is a cornerstone of applications that provide REST +APIs. It enables API consumers to leverage a whole ecosystem of related tooling. +To make the spec useful, you must ensure it's a valid OpenAPI Spec document, +ideally in an automated way that's an integral part of regular CI builds. +LoopBack's [testlab](https://www.npmjs.com/package/@loopback/testlab) module +provides a helper method `validateApiSpec` that builds on top of the popular +[swagger-parser](https://www.npmjs.com/package/swagger-parser) package. Example usage: @@ -651,21 +657,23 @@ describe('API specification', () => { ### Perform an auto-generated smoke test of your REST API -{% include important.html content=" -The top-down approach for building LoopBack applications is not yet fully -supported. Therefore, the code outlined in this section is outdated and may not -work out of the box. It will be revisitedafter our MVP release. -" %} +{% include important.html content=" The top-down approach for building LoopBack +applications is not yet fully supported. Therefore, the code outlined in this +section is outdated and may not work out of the box. It will be revisitedafter +our MVP release. " %} The formal validity of your application's spec does not guarantee that your -implementation is actually matching the specified behavior. To keep your spec -in sync with your implementation, you should use an automated tool like [Dredd](https://www.npmjs.com/package/dredd) -to run a set of smoke tests to verify your app conforms to the spec. - -Automated testing tools usually require hints in your specification -to tell them how to create valid requests or what response data to expect. -Dredd in particular relies on response [examples](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#exampleObject) -and request parameter [x-example](http://dredd.org/en/latest/how-to-guides.html#example-values-for-request-parameters) +implementation is actually matching the specified behavior. To keep your spec in +sync with your implementation, you should use an automated tool like +[Dredd](https://www.npmjs.com/package/dredd) to run a set of smoke tests to +verify your app conforms to the spec. + +Automated testing tools usually require hints in your specification to tell them +how to create valid requests or what response data to expect. Dredd in +particular relies on response +[examples](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#exampleObject) +and request parameter +[x-example](http://dredd.org/en/latest/how-to-guides.html#example-values-for-request-parameters) fields. Extending your API spec with examples is a good thing on its own, since developers consuming your API will find them useful too. @@ -727,19 +735,19 @@ describe('API (acceptance)', () => { }); ``` -The user experience needs improvement and we are looking into -better solutions. See [GitHub issue #644](https://github.com/strongloop/loopback-next/issues/644). +The user experience needs improvement and we are looking into better solutions. +See [GitHub issue #644](https://github.com/strongloop/loopback-next/issues/644). Let us know if you have any recommendations! ### Test your individual REST API endpoints -You should have at least one acceptance (end-to-end) test for each of your -REST API endpoints. Consider adding more tests if your endpoint depends on -(custom) sequence actions to modify the behavior when the corresponding -controller method is invoked via REST, compared to behavior observed when -the controller method is invoked directly via JavaScript/TypeScript API. -For example, if your endpoint returns different responses to regular users -and to admin users, then you should two tests (one test for each user role). +You should have at least one acceptance (end-to-end) test for each of your REST +API endpoints. Consider adding more tests if your endpoint depends on (custom) +sequence actions to modify the behavior when the corresponding controller method +is invoked via REST, compared to behavior observed when the controller method is +invoked directly via JavaScript/TypeScript API. For example, if your endpoint +returns different responses to regular users and to admin users, then you should +two tests (one test for each user role). Here is an example of an acceptance test: diff --git a/docs/site/Using-decorators.md b/docs/site/Using-decorators.md index c1a5a79b8a3a..9407b5876a83 100644 --- a/docs/site/Using-decorators.md +++ b/docs/site/Using-decorators.md @@ -10,9 +10,11 @@ summary: {% include content/tbd.html %} -> This will describe how to use existing decorators to build apps per [#552](https://github.com/strongloop/loopback-next/issues/552) +> This will describe how to use existing decorators to build apps per +> [#552](https://github.com/strongloop/loopback-next/issues/552) Potential topics: + - What is a decorator? Allows you annotate your classes with metadata. - simple example `@authenticate` - How to use decorators (what are the features) diff --git a/docs/site/VSCODE.md b/docs/site/VSCODE.md index eee43e252515..441ac8cf7003 100644 --- a/docs/site/VSCODE.md +++ b/docs/site/VSCODE.md @@ -1,14 +1,20 @@ # Developing with VisualStudio Code -We recommend our contributors to use [VisualStudio Code](https://code.visualstudio.com/) with the following extensions installed: - - [Prettier - Code Formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) for automatic formatting of source files on save. - - [TSLint](https://marketplace.visualstudio.com/items?itemName=eg2.tslint) to highlight and auto-fix linting problems directly in the editor. +We recommend our contributors to use +[VisualStudio Code](https://code.visualstudio.com/) with the following +extensions installed: -Our monorepo comes with few preconfigured [VSCode -Tasks](https://code.visualstudio.com/docs/editor/tasks): +- [Prettier - Code Formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + for automatic formatting of source files on save. +- [TSLint](https://marketplace.visualstudio.com/items?itemName=eg2.tslint) to + highlight and auto-fix linting problems directly in the editor. - - The build task is configured to run the TypeScript compiler - - The test task is configured to run `npm test` (which runs the build before running any tests). +Our monorepo comes with few preconfigured +[VSCode Tasks](https://code.visualstudio.com/docs/editor/tasks): + +- The build task is configured to run the TypeScript compiler +- The test task is configured to run `npm test` (which runs the build before + running any tests). ## How to verify TypeScript setup @@ -18,35 +24,50 @@ Tasks](https://code.visualstudio.com/docs/editor/tasks): 2. Add a small bit of code to break TypeScript's type checks, for example: - ```ts - const foo: number = 'bar'; - ``` - -3. Verify that VS Code editor has marked `foo` with a red underscore. Hover over `foo` and check the problem message. It should mention `[ts]` source, e.g. + ```ts + const foo: number = 'bar'; + ``` - ```text - [ts] Type '"bar"' is not assignable to type 'number'. +3. Verify that VS Code editor has marked `foo` with a red underscore. Hover + over `foo` and check the problem message. It should mention `[ts]` source, + e.g. - ``` + ```text + [ts] Type '"bar"' is not assignable to type 'number'. + ``` -4. Check VS Code's [PROBLEMS Window](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_errors-and-warnings). There should be an entry showing the same tslint error. When you click on the entry, it should jump on the problematic line. +4. Check VS Code's + [PROBLEMS Window](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_errors-and-warnings). + There should be an entry showing the same tslint error. When you click on + the entry, it should jump on the problematic line. -5. Close the editor tab. (This will clear the PROBLEMS entry reported by TSLint extension). +5. Close the editor tab. (This will clear the PROBLEMS entry reported by TSLint + extension). -6. Run the test task ("Tasks: Run test task"). This will invoke package scripts like `npm test` under the hood. +6. Run the test task ("Tasks: Run test task"). This will invoke package scripts + like `npm test` under the hood. -7. Open "Tasks" OUTPUT window and verify that compilation error was parsed by VSCode. +7. Open "Tasks" OUTPUT window and verify that compilation error was parsed by + VSCode. -8. Verify that compilation errors are correctly associated with the problematic source code line. - _(This is does not work now, `tsc` is reporting paths relative to individual package directories. See https://github.com/strongloop/loopback-next/issues/1010)_ +8. Verify that compilation errors are correctly associated with the problematic + source code line. _(This is does not work now, `tsc` is reporting paths + relative to individual package directories. See + )_ ### Navigation in VS Code -Verify that "Go to definition" works across package boundaries. Find a place where we are calling `@inject` in `authentication` package, press F12 to go to the definition of `inject`. VSCode should jump to the `.ts` file in `src` (as opposed to jumping to a `.d.ts` file in `dist`) +Verify that "Go to definition" works across package boundaries. Find a place +where we are calling `@inject` in `authentication` package, press F12 to go to +the definition of `inject`. VSCode should jump to the `.ts` file in `src` (as +opposed to jumping to a `.d.ts` file in `dist`) #### Refactoring in VS Code -Verify that refactorings like "rename symbol" (`F2`) will change all places using the renamed entity. Two different scenarios to verify: rename at the place where the entity is defined, rename at the place where the entity is used. (You can e.g. rename `inject` to test.) +Verify that refactorings like "rename symbol" (`F2`) will change all places +using the renamed entity. Two different scenarios to verify: rename at the place +where the entity is defined, rename at the place where the entity is used. (You +can e.g. rename `inject` to test.) ## How to verify TSLint setup @@ -54,40 +75,51 @@ Verify that refactorings like "rename symbol" (`F2`) will change all places usin 2. Verify that TSLint extension is not reporting any warnings in the output window: - - pres _Cmd+shift+P_ or _Ctrl+shift+P_ to open task selector - - find and run the task `TSLint: Show Output` - - check the logs - An example of a warning we want to **avoid**: + - pres _Cmd+shift+P_ or _Ctrl+shift+P_ to open task selector + - find and run the task `TSLint: Show Output` + - check the logs - ```text - Warning: The 'no-unused-variable' rule requires type information. - ``` + An example of a warning we want to **avoid**: -3. Introduce two kinds linting problems - one that does and another that does not require type information to be detected. For example, you can add the following line at the end of the opened `index.ts`: + ```text + Warning: The 'no-unused-variable' rule requires type information. + ``` - ```ts - const foo: any = 'bar'; - ``` +3. Introduce two kinds linting problems - one that does and another that does + not require type information to be detected. For example, you can add the + following line at the end of the opened `index.ts`: -4. Verify that VS Code editor has marked `any` with a red underscore. Hover over `any` and check the problem message. It should mention `no-any` rule, e.g. + ```ts + const foo: any = 'bar'; + ``` - ```text - [tslint] Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type. (no-any) - ``` +4. Verify that VS Code editor has marked `any` with a red underscore. Hover + over `any` and check the problem message. It should mention `no-any` rule, + e.g. -5. Check VS Code's [PROBLEMS Window](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_errors-and-warnings). There should be an entry showing the same tslint error. When you click on the entry, it should jump on the problematic line. + ```text + [tslint] Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type. (no-any) + ``` -6. Close the editor tab. (This will clear the PROBLEMS entry reported by TSLint extension). +5. Check VS Code's + [PROBLEMS Window](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_errors-and-warnings). + There should be an entry showing the same tslint error. When you click on + the entry, it should jump on the problematic line. -7. Run the test task ("Tasks: Run test task"). This will invoke package scripts like `npm test` under the hood. +6. Close the editor tab. (This will clear the PROBLEMS entry reported by TSLint + extension). -8. Open "Tasks" OUTPUT window and verify that two tslint problems were reported: +7. Run the test task ("Tasks: Run test task"). This will invoke package scripts + like `npm test` under the hood. - ```text - ERROR: /Users/(...)/packages/core/src/index.ts[16, 7]: 'foo' is declared but its value is never read. - ERROR: /Users/(...)/packages/core/src/index.ts[16, 12]: Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type. - ``` +8. Open "Tasks" OUTPUT window and verify that two tslint problems were + reported: -9. Open "PROBLEMS" window again. Verify that both problems were parsed by VS Code and are correctly associated with the problematic source code line. + ```text + ERROR: /Users/(...)/packages/core/src/index.ts[16, 7]: 'foo' is declared but its value is never read. + ERROR: /Users/(...)/packages/core/src/index.ts[16, 12]: Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type. + ``` +9. Open "PROBLEMS" window again. Verify that both problems were parsed by VS + Code and are correctly associated with the problematic source code line. diff --git a/docs/site/index.md b/docs/site/index.md index 8ac5b90f51bd..da9ae59f30f2 100644 --- a/docs/site/index.md +++ b/docs/site/index.md @@ -9,27 +9,34 @@ permalink: /doc/en/lb4/index.html summary: LoopBack is a platform for building APIs and microservices in Node.js --- -{% include important.html content="LoopBack 4 is the next step in the evolution of LoopBack. It is still in early development and is not yet released. -" %} +{% include important.html content="LoopBack 4 is the next step in the evolution +of LoopBack. It is still in early development and is not yet released. " %} + +{% include see-also.html title="GitHub Repo" content=' LoopBack 4 framework code +is being developed in one "mono-repository", +[loopback-next](https://github.com/strongloop/loopback-next), rather than +multiple repos, as in v3. However, examples and externally-developed components +will be in separate repositories. '%} -{% include see-also.html title="GitHub Repo" content=' -LoopBack 4 framework code is being developed in one "mono-repository", -[loopback-next](https://github.com/strongloop/loopback-next), rather than multiple repos, as in v3. However, examples and externally-developed components will be in separate repositories. -'%} ## Built for API developers - - Define your API endpoints and schemas using the [OpenAPI](https://www.openapis.org/) standard. - - Write your endpoints in modern JavaScript using ES2017, `async` / `await`, modules. - - Use your defined endpoints and schemas as the source of truth without generating code. +- Define your API endpoints and schemas using the + [OpenAPI](https://www.openapis.org/) standard. +- Write your endpoints in modern JavaScript using ES2017, `async` / `await`, + modules. +- Use your defined endpoints and schemas as the source of truth without + generating code. ## Built for teams - - Review changes to API endpoints without digging through JavaScript. - - Maintain consistency by automating the validation of your endpoints and schemas. - - First class support for [TypeScript](https://www.typescriptlang.org) (strongly typed JavaScript). +- Review changes to API endpoints without digging through JavaScript. +- Maintain consistency by automating the validation of your endpoints and + schemas. +- First class support for [TypeScript](https://www.typescriptlang.org) (strongly + typed JavaScript). ## Built for your platform - - Use LoopBack as a starting point for your own framework or platform. - - Build libraries of reusable components in a standardized way. - - Integrate with databases, web services, and other platforms using connectors. +- Use LoopBack as a starting point for your own framework or platform. +- Build libraries of reusable components in a standardized way. +- Integrate with databases, web services, and other platforms using connectors. diff --git a/packages/authentication/README.md b/packages/authentication/README.md index 30ff2eb95b63..2dc7bfa5e464 100644 --- a/packages/authentication/README.md +++ b/packages/authentication/README.md @@ -2,7 +2,8 @@ A LoopBack 4 component for authentication support. -**This is a reference implementation showing how to implement an authentication component, it is not production ready.** +**This is a reference implementation showing how to implement an authentication +component, it is not production ready.** ## Overview @@ -17,8 +18,8 @@ npm install --save @loopback/authentication ## Basic use -Start by decorating your controller methods with `@authenticate` to require -the request to be authenticated. +Start by decorating your controller methods with `@authenticate` to require the +request to be authenticated. In this example, we make the user profile available via dependency injection using a key available from `@loopback/authentication` package. @@ -46,10 +47,10 @@ export class WhoAmIController { } ``` -Next, implement a Strategy provider to map strategy names specified -in `@authenticate` decorators into Passport Strategy instances. -Remember to install `passport`, `passport-http`, `@types/passport`, and -`@types/passport-http` modules beforehand. +Next, implement a Strategy provider to map strategy names specified in +`@authenticate` decorators into Passport Strategy instances. Remember to install +`passport`, `passport-http`, `@types/passport`, and `@types/passport-http` +modules beforehand. ```shell npm install --save passport passport-http @@ -217,7 +218,8 @@ run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/boot/README.md b/packages/boot/README.md index 1c89ccf0dd53..17b2f7c69d2b 100644 --- a/packages/boot/README.md +++ b/packages/boot/README.md @@ -2,22 +2,22 @@ A convention based project Bootstrapper and Booters for LoopBack Applications -# Overview +## Overview -A Booter is a Class that can be bound to an Application and is called -to perform a task before the Application is started. A Booter may have multiple -phases to complete its task. The task for a convention based Booter is to discover -and bind Artifacts (Controllers, Repositories, Models, etc.). +A Booter is a Class that can be bound to an Application and is called to perform +a task before the Application is started. A Booter may have multiple phases to +complete its task. The task for a convention based Booter is to discover and +bind Artifacts (Controllers, Repositories, Models, etc.). -An example task of a Booter may be to discover and bind all artifacts of a -given type. +An example task of a Booter may be to discover and bind all artifacts of a given +type. -A Bootstrapper is needed to manage the Booters and execute them. This is provided -in this package. For ease of use, everything needed is packages using a BootMixin. -This Mixin will add convenience methods such as `boot` and `booter`, as well as -properties needed for Bootstrapper such as `projectRoot`. The Mixin also adds the -`BootComponent` to your `Application` which binds the `Bootstrapper` and default -`Booters` made available by this package. +A Bootstrapper is needed to manage the Booters and execute them. This is +provided in this package. For ease of use, everything needed is packages using a +BootMixin. This Mixin will add convenience methods such as `boot` and `booter`, +as well as properties needed for Bootstrapper such as `projectRoot`. The Mixin +also adds the `BootComponent` to your `Application` which binds the +`Bootstrapper` and default `Booters` made available by this package. ## Installation @@ -40,36 +40,41 @@ await app.start(); ``` ### BootOptions + List of Options available on BootOptions Object. -|Option|Type|Description| -|-|-|-| -|`controllers`|`ArtifactOptions`|ControllerBooter convention options| -|`repositories`|`ArtifactOptions`|RepositoryBooter convention options| +| Option | Type | Description | +| -------------- | ----------------- | ----------------------------------- | +| `controllers` | `ArtifactOptions` | ControllerBooter convention options | +| `repositories` | `ArtifactOptions` | RepositoryBooter convention options | ### ArtifactOptions -|Options|Type|Description| -|-|-|-| -|`dirs`|`string \| string[]`|Paths relative to projectRoot to look in for Artifact| -|`extensions`|`string \| string[]`|File extensions to match for Artifact| -|`nested`|`boolean`|Look in nested directories in `dirs` for Artifact| -|`glob`|`string`|A `glob` pattern string. This takes precedence over above 3 options (which are used to make a glob pattern).| +| Options | Type | Description | +| ------------ | -------------------- | ------------------------------------------------------------------------------------------------------------ | +| `dirs` | `string \| string[]` | Paths relative to projectRoot to look in for Artifact | +| `extensions` | `string \| string[]` | File extensions to match for Artifact | +| `nested` | `boolean` | Look in nested directories in `dirs` for Artifact | +| `glob` | `string` | A `glob` pattern string. This takes precedence over above 3 options (which are used to make a glob pattern). | ### BootExecOptions -**Experimental support. May be removed or changed in a non-compatible way in future without warning** +**Experimental support. May be removed or changed in a non-compatible way in +future without warning** -To use `BootExecOptions` you must directly call `bootstrapper.boot()` and pass in `BootExecOptions`. -`app.boot()` provided by `BootMixin` does not take any paramters. +To use `BootExecOptions` you must directly call `bootstrapper.boot()` and pass +in `BootExecOptions`. `app.boot()` provided by `BootMixin` does not take any +paramters. ```ts -const bootstrapper: Bootstrapper = await this.get(BootBindings.BOOTSTRAPPER_KEY); +const bootstrapper: Bootstrapper = await this.get( + BootBindings.BOOTSTRAPPER_KEY, +); const execOptions: BootExecOptions = { booters: [MyBooter1, MyBooter2], filter: { - phases: ['configure', 'discover'] - } + phases: ['configure', 'discover'], + }, }; const ctx = bootstrapper.boot(execOptions); @@ -88,41 +93,47 @@ You can pass in the `BootExecOptions` object with the following properties: ### ControllerBooter #### Description + Discovers and binds Controller Classes using `app.controller()`. #### Options -The Options for this can be passed via `BootOptions` when calling `app.boot(options:BootOptions)`. + +The Options for this can be passed via `BootOptions` when calling +`app.boot(options:BootOptions)`. The options for this are passed in a `controllers` object on `BootOptions`. Available Options on the `controllers` object on `BootOptions` are as follows: -|Options|Type|Default|Description| -|-|-|-|-| -|`dirs`|`string \| string[]`|`['controllers']`|Paths relative to projectRoot to look in for Controller artifacts| -|`extensions`|`string \| string[]`|`['.controller.js']`|File extensions to match for Controller artifacts| -|`nested`|`boolean`|`true`|Look in nested directories in `dirs` for Controller artifacts| -|`glob`|`string`||A `glob` pattern string. This takes precendence over above 3 options (which are used to make a glob pattern).| +| Options | Type | Default | Description | +| ------------ | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------- | +| `dirs` | `string \| string[]` | `['controllers']` | Paths relative to projectRoot to look in for Controller artifacts | +| `extensions` | `string \| string[]` | `['.controller.js']` | File extensions to match for Controller artifacts | +| `nested` | `boolean` | `true` | Look in nested directories in `dirs` for Controller artifacts | +| `glob` | `string` | | A `glob` pattern string. This takes precendence over above 3 options (which are used to make a glob pattern). | ### RepositoryBooter #### Description -Discovers and binds Repository Classes using `app.repository()` (Application must use -`RepositoryMixin` from `@loopback/repository`). + +Discovers and binds Repository Classes using `app.repository()` (Application +must use `RepositoryMixin` from `@loopback/repository`). #### Options -The Options for this can be passed via `BootOptions` when calling `app.boot(options:BootOptions)`. + +The Options for this can be passed via `BootOptions` when calling +`app.boot(options:BootOptions)`. The options for this are passed in a `repositories` object on `BootOptions`. Available Options on the `repositories` object on `BootOptions` are as follows: -|Options|Type|Default|Description| -|-|-|-|-| -|`dirs`|`string \| string[]`|`['repositories']`|Paths relative to projectRoot to look in for Repository artifacts| -|`extensions`|`string \| string[]`|`['.repository.js']`|File extensions to match for Repository artifacts| -|`nested`|`boolean`|`true`|Look in nested directories in `dirs` for Repository artifacts| -|`glob`|`string`||A `glob` pattern string. This takes precedence over above 3 options (which are used to make a glob pattern).| +| Options | Type | Default | Description | +| ------------ | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------ | +| `dirs` | `string \| string[]` | `['repositories']` | Paths relative to projectRoot to look in for Repository artifacts | +| `extensions` | `string \| string[]` | `['.repository.js']` | File extensions to match for Repository artifacts | +| `nested` | `boolean` | `true` | Look in nested directories in `dirs` for Repository artifacts | +| `glob` | `string` | | A `glob` pattern string. This takes precedence over above 3 options (which are used to make a glob pattern). | ## Contributions @@ -135,7 +146,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/build/README.md b/packages/build/README.md index b0c80469dc36..60b50ff9e304 100644 --- a/packages/build/README.md +++ b/packages/build/README.md @@ -1,16 +1,21 @@ # @loopback/build -This module contains a set of common scripts and default configurations to build LoopBack 4 or other TypeScript modules, including: +This module contains a set of common scripts and default configurations to build +LoopBack 4 or other TypeScript modules, including: -- lb-tsc: Use [`tsc`](https://www.typescriptlang.org/docs/handbook/compiler-options.html) to compile typescript files +- lb-tsc: Use + [`tsc`](https://www.typescriptlang.org/docs/handbook/compiler-options.html) to + compile typescript files - lb-tslint: Run [`tslint`](https://github.com/palantir/tslint) - lb-prettier: Run [`prettier`](https://github.com/prettier/prettier) - lb-mocha: Run [`mocha`](https://mochajs.org/) to execute test cases - lb-nyc: Run [`nyc`](https://github.com/istanbuljs/nyc) -- lb-dist: Detect the correct distribution target: `dist` => ES2017, `dist6` => ES2015. - The command is deprecated as `lb-mocha` detects the distribution target now. +- lb-dist: Detect the correct distribution target: `dist` => ES2017, `dist6` => + ES2015. The command is deprecated as `lb-mocha` detects the distribution + target now. -These scripts first try to locate the CLI from target project dependencies and fall back to bundled ones in `@loopback/build`. +These scripts first try to locate the CLI from target project dependencies and +fall back to bundled ones in `@loopback/build`. ## Basic use @@ -54,11 +59,16 @@ Now you run the scripts, such as: - lb-tsc - By default, `lb-tsc` searches your project's root directory for `tsconfig.build.json` then `tsconfig.json`. If neither of them exists, a `tsconfig.json` will be created to extend from `./node_modules/@loopback/build/config/tsconfig.common.json`. + By default, `lb-tsc` searches your project's root directory for + `tsconfig.build.json` then `tsconfig.json`. If neither of them exists, a + `tsconfig.json` will be created to extend from + `./node_modules/@loopback/build/config/tsconfig.common.json`. To customize the configuration: - - Create `tsconfig.build.json` or `tsconfig.json` in your project's root directory + - Create `tsconfig.build.json` or `tsconfig.json` in your project's root + directory + ```json { "$schema": "http://json.schemastore.org/tsconfig", @@ -71,17 +81,22 @@ Now you run the scripts, such as: ``` - Set options explicity for the script - ``` + + ```sh lb-tsc -p tsconfig.json --target es2017 --outDir dist ``` - For more information, see https://www.typescriptlang.org/docs/handbook/compiler-options.html. + For more information, see + . - lb-tslint - By default, `lb-tslint` searches your project's root directory for `tslint.build.json` then `tslint.json`. If neither of them exists, it falls back to `./node_modules/@loopback/build/config/tslint.common.json`. + By default, `lb-tslint` searches your project's root directory for + `tslint.build.json` then `tslint.json`. If neither of them exists, it falls + back to `./node_modules/@loopback/build/config/tslint.common.json`. - `lb-tslint` also depends on `tsconfig.build.json` or `tsconfig.json` to reference the project. + `lb-tslint` also depends on `tsconfig.build.json` or `tsconfig.json` to + reference the project. To customize the configuration: @@ -97,28 +112,30 @@ Now you run the scripts, such as: // See https://github.com/Microsoft/vscode-tslint/issues/70 "rules": { // These rules find errors related to TypeScript features. + ``` +```json + // These rules catch common errors in JS programming or otherwise + // confusing constructs that are prone to producing bugs. + + "await-promise": true, + "no-floating-promises": true, + "no-void-expression": [true, "ignore-arrow-function-shorthand"] + } +} +``` - // These rules catch common errors in JS programming or otherwise - // confusing constructs that are prone to producing bugs. - - "await-promise": true, - "no-floating-promises": true, - "no-void-expression": [true, "ignore-arrow-function-shorthand"] - } - } - ``` +- Set options explicitly for the script - - Set options explicitly for the script - ``` - lb-tslint -c tslint.json -p tsconfig.json - ``` + ```sh + lb-tslint -c tslint.json -p tsconfig.json + ``` - For more information, see https://palantir.github.io/tslint/usage/cli/. + For more information, see . 4. Run builds -``` +```sh npm run build ``` @@ -133,7 +150,8 @@ run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/cli/README.md b/packages/cli/README.md index 4966a6dc19ab..c58be59ff654 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -12,9 +12,9 @@ Run the following command to install the CLI. 1. To scaffold a LoopBack 4 application - `lb4` + `lb4` -``` +```sh Usage: lb4 [options] [] @@ -36,9 +36,9 @@ Arguments: 2. To scaffold a LoopBack 4 extension - `lb4 extension` + `lb4 extension` -``` +```sh Usage: lb4 extension [options] [] @@ -53,7 +53,6 @@ Options: --mocha # Enable mocha --loopbackBuild # Use @loopback/build --componentName # Component name - ``` 3. To scaffold a controller into your application @@ -63,7 +62,7 @@ Options: lb4 controller ``` -``` +```sh Usage: lb4 controller [options] [] @@ -79,9 +78,9 @@ Arguments: 4. To download one of LoopBack example projects - `lb4 example` + `lb4 example` -``` +```sh Usage: lb4 example [options] [] @@ -102,7 +101,8 @@ run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/cli/generators/app/templates/test/README.md b/packages/cli/generators/app/templates/test/README.md index 2243988ea7e1..a88f8a543566 100644 --- a/packages/cli/generators/app/templates/test/README.md +++ b/packages/cli/generators/app/templates/test/README.md @@ -1,4 +1,3 @@ # Tests Please place your tests in this folder. - diff --git a/packages/cli/generators/extension/templates/src/controllers/README.md b/packages/cli/generators/extension/templates/src/controllers/README.md index 30ccae59a75b..83207d1b3d6c 100644 --- a/packages/cli/generators/extension/templates/src/controllers/README.md +++ b/packages/cli/generators/extension/templates/src/controllers/README.md @@ -1,5 +1,6 @@ # Controllers -This directory contains source files for the controllers exported by this extension. +This directory contains source files for the controllers exported by this +extension. -For more information, see http://loopback.io/doc/en/lb4/Controllers.html. +For more information, see . diff --git a/packages/cli/generators/extension/templates/src/decorators/README.md b/packages/cli/generators/extension/templates/src/decorators/README.md index 8e8cf136036a..957e3f9d5718 100644 --- a/packages/cli/generators/extension/templates/src/decorators/README.md +++ b/packages/cli/generators/extension/templates/src/decorators/README.md @@ -2,16 +2,21 @@ ## Overview -Decorators provide annotations for class methods and arguments. Decorators use the form `@decorator` where `decorator` is the name of the function that will be called at runtime. +Decorators provide annotations for class methods and arguments. Decorators use +the form `@decorator` where `decorator` is the name of the function that will be +called at runtime. ## Basic Usage ### txIdFromHeader -This simple decorator allows you to annotate a `Controller` method argument. The decorator will annotate the method argument with the value of the header `X-Transaction-Id` from the request. +This simple decorator allows you to annotate a `Controller` method argument. The +decorator will annotate the method argument with the value of the header +`X-Transaction-Id` from the request. **Example** -``` + +```ts class MyController { @get('/') getHandler(@txIdFromHeader() txId: string) { @@ -22,7 +27,8 @@ class MyController { ## Related Resources -You can check out the following resource to learn more about decorators and how they are used in LoopBack Next. +You can check out the following resource to learn more about decorators and how +they are used in LoopBack Next. - [TypeScript Handbook: Decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) - [Decorators in LoopBack](http://loopback.io/doc/en/lb4/Decorators.html) diff --git a/packages/cli/generators/extension/templates/src/mixins/README.md b/packages/cli/generators/extension/templates/src/mixins/README.md index f4f6e2a7963a..e97bd159fa67 100644 --- a/packages/cli/generators/extension/templates/src/mixins/README.md +++ b/packages/cli/generators/extension/templates/src/mixins/README.md @@ -4,15 +4,21 @@ This directory contains source files for the mixins exported by this extension. ## Overview -Sometimes it's helpful to write partial classes and then combining them together to build more powerful classes. This pattern is called Mixins (mixing in partial classes) and is supported by LoopBack 4. +Sometimes it's helpful to write partial classes and then combining them together +to build more powerful classes. This pattern is called Mixins (mixing in partial +classes) and is supported by LoopBack 4. -LoopBack 4 supports mixins at an `Application` level. Your partial class can then be mixed into the `Application` class. A mixin class can modify or override existing methods of the class or add new ones! It is also possible to mixin multiple classes together as needed. +LoopBack 4 supports mixins at an `Application` level. Your partial class can +then be mixed into the `Application` class. A mixin class can modify or override +existing methods of the class or add new ones! It is also possible to mixin +multiple classes together as needed. ### High level example + ```ts class MyApplication extends MyMixinClass(Application) { // Your code -}; +} // Multiple Classes mixed together class MyApp extends MyMixinClass(MyMixinClass2(Application)) { @@ -22,12 +28,23 @@ class MyApp extends MyMixinClass(MyMixinClass2(Application)) { ## Getting Started -For hello-extensions we write a simple Mixin that allows the `Application` class to bind a `Logger` class from ApplicationOptions, Components, or `.logger()` method that is mixed in. `Logger` instances are bound to the key `loggers.${Logger.name}`. Once a Logger has been bound, the user can retrieve it by using [Dependency Injection](http://loopback.io/doc/en/lb4/Dependency-injection.html) and the key for the `Logger`. +For hello-extensions we write a simple Mixin that allows the `Application` class +to bind a `Logger` class from ApplicationOptions, Components, or `.logger()` +method that is mixed in. `Logger` instances are bound to the key +`loggers.${Logger.name}`. Once a Logger has been bound, the user can retrieve it +by using +[Dependency Injection](http://loopback.io/doc/en/lb4/Dependency-injection.html) +and the key for the `Logger`. ### What is a Logger? -> A Logger class is provides a mechanism for logging messages of varying priority by providing an implementation for `Logger.info()` & `Logger.error()`. An example of a Logger is `console` which has `console.log()` and `console.error()`. + +> A Logger class is provides a mechanism for logging messages of varying +> priority by providing an implementation for `Logger.info()` & +> `Logger.error()`. An example of a Logger is `console` which has +> `console.log()` and `console.error()`. #### An example Logger + ```ts class ColorLogger implements Logger { log(...args: LogArgs) { @@ -42,12 +59,19 @@ class ColorLogger implements Logger { ``` ## LoggerMixin -A complete & functional implementation can be found in `logger.mixin.ts`. *Here are some key things to keep in mind when writing your own Mixin*. + +A complete & functional implementation can be found in `logger.mixin.ts`. _Here +are some key things to keep in mind when writing your own Mixin_. ### constructor() -A Mixin constructor must take an array of any type as it's argument. This would represent `ApplicationOptions` for our base class `Application` as well as any properties we would like for our Mixin. -It is also important for the constructor to call `super(args)` so `Application` continues to work as expected. +A Mixin constructor must take an array of any type as it's argument. This would +represent `ApplicationOptions` for our base class `Application` as well as any +properties we would like for our Mixin. + +It is also important for the constructor to call `super(args)` so `Application` +continues to work as expected. + ```ts constructor(...args: any[]) { super(args); @@ -55,23 +79,32 @@ constructor(...args: any[]) { ``` ### Binding via `ApplicationOptions` -As mentioned earlier, since our `args` represents `ApplicationOptions`, we can make it possible for users to pass in their `Logger` implementations in a `loggers` array on `ApplicationOptions`. We can then read the array and automatically bind these for the user. + +As mentioned earlier, since our `args` represents `ApplicationOptions`, we can +make it possible for users to pass in their `Logger` implementations in a +`loggers` array on `ApplicationOptions`. We can then read the array and +automatically bind these for the user. #### Example user experience + ```ts -class MyApp extends LoggerMixin(Application){ +class MyApp extends LoggerMixin(Application) { constructor(...args: any[]) { super(...args); } -}; +} const app = new MyApp({ - loggers: [ColorLogger] + loggers: [ColorLogger], }); ``` #### Example Implementation -To implement this, we would check `this.options` to see if it has a `loggers` array and if so, bind it by calling the `.logger()` method. (More on that below). + +To implement this, we would check `this.options` to see if it has a `loggers` +array and if so, bind it by calling the `.logger()` method. (More on that +below). + ```ts if (this.options.loggers) { for (const logger of this.options.loggers) { @@ -81,7 +114,12 @@ if (this.options.loggers) { ``` ### Binding via `.logger()` -As mentioned earlier, we can add a new function to our `Application` class called `.logger()` into which a user would pass in their `Logger` implementation so we can bind it to the `loggers.*` key for them. We just add this new method on our partial Mixin class. + +As mentioned earlier, we can add a new function to our `Application` class +called `.logger()` into which a user would pass in their `Logger` implementation +so we can bind it to the `loggers.*` key for them. We just add this new method +on our partial Mixin class. + ```ts logger(logClass: Logger) { const loggerKey = `loggers.${logClass.name}`; @@ -90,7 +128,13 @@ logger(logClass: Logger) { ``` ### Binding a `Logger` from a `Component` -Our base class of `Application` already has a method that binds components. We can modify this method to continue binding a `Component` as usual but also binding any `Logger` instances provided by that `Component`. When modifying behavior of an existing method, we can ensure existing behavior by calling the `super.method()`. In our case the method is `.component()`. + +Our base class of `Application` already has a method that binds components. We +can modify this method to continue binding a `Component` as usual but also +binding any `Logger` instances provided by that `Component`. When modifying +behavior of an existing method, we can ensure existing behavior by calling the +`super.method()`. In our case the method is `.component()`. + ```ts component(component: Constructor) { super.component(component); // ensures existing behavior from Application @@ -98,7 +142,11 @@ component(component: Constructor) { } ``` -We have now modified `.component()` to do it's thing and then call our method `mountComponentLoggers()`. In this method is where we check for `Logger` implementations declared by the component in a `loggers` array by retrieving the instance of the `Component`. Then if `loggers` array exists, we bind the `Logger` instances as normal (by leveraging our `.logger()` method). +We have now modified `.component()` to do it's thing and then call our method +`mountComponentLoggers()`. In this method is where we check for `Logger` +implementations declared by the component in a `loggers` array by retrieving the +instance of the `Component`. Then if `loggers` array exists, we bind the +`Logger` instances as normal (by leveraging our `.logger()` method). ```ts mountComponentLoggers(component: Constructor) { @@ -114,7 +162,12 @@ mountComponentLoggers(component: Constructor) { ``` ## Retrieving the Logger instance -Now that we have bound a Logger to our Application via one of the many ways made possible by `LoggerMixin`, we need to be able to retrieve it so we can use it. Let's say we want to use it in a controller. Here's an example to retrieving it so we can use it. + +Now that we have bound a Logger to our Application via one of the many ways made +possible by `LoggerMixin`, we need to be able to retrieve it so we can use it. +Let's say we want to use it in a controller. Here's an example to retrieving it +so we can use it. + ```ts class MyController { constructor(@inject('loggers.ColorLogger') protected log: Logger) {} @@ -127,7 +180,9 @@ class MyController { ``` ## Examples for using LoggerMixin + ### Using the app's `.logger()` method + ```ts class LoggingApplication extends LoggerMixin(Application) { constructor(...args: any[]) { @@ -138,6 +193,7 @@ class LoggingApplication extends LoggerMixin(Application) { ``` ### Using the app's constructor + ```ts class LoggerApplication extends LoggerMixin(Application) { constructor() { @@ -149,8 +205,9 @@ class LoggerApplication extends LoggerMixin(Application) { ``` ### Binding a Logger provided by a component + ```ts -class LoggingComponent implements Component{ +class LoggingComponent implements Component { loggers: [ColorLogger]; } diff --git a/packages/cli/generators/extension/templates/src/providers/README.md b/packages/cli/generators/extension/templates/src/providers/README.md index 4cf3dcb9d4b2..96a549cc935f 100644 --- a/packages/cli/generators/extension/templates/src/providers/README.md +++ b/packages/cli/generators/extension/templates/src/providers/README.md @@ -1,54 +1,75 @@ # Providers -This directory contains providers contributing additional bindings, for example custom sequence actions. +This directory contains providers contributing additional bindings, for example +custom sequence actions. ## Overview -A [provider](http://loopback.io/doc/en/lb4/Creating-components.html#providers) is a class that provides a `value()` function. This function is called `Context` when another entity requests a value to be injected. +A [provider](http://loopback.io/doc/en/lb4/Creating-components.html#providers) +is a class that provides a `value()` function. This function is called `Context` +when another entity requests a value to be injected. -Here we create a provider for a logging function that can be used as a new action in a custom [sequence](http://loopback.io/doc/en/lb4/Sequence.html). +Here we create a provider for a logging function that can be used as a new +action in a custom [sequence](http://loopback.io/doc/en/lb4/Sequence.html). -The logger will log the URL, the parsed request parameters, and the result. The logger is also capable of timing the sequence if you start a timer at the start of the sequence using `this.logger.startTimer()`. +The logger will log the URL, the parsed request parameters, and the result. The +logger is also capable of timing the sequence if you start a timer at the start +of the sequence using `this.logger.startTimer()`. ## Basic Usage ### TimerProvider -TimerProvider is automatically bound to your Application's [Context](http://loopback.io/doc/en/lb4/Context.html) using the LogComponent which exports this provider with a binding key of `extension-starter.timer`. You can learn more about components in the [related resources section](#related-resources). +TimerProvider is automatically bound to your Application's +[Context](http://loopback.io/doc/en/lb4/Context.html) using the LogComponent +which exports this provider with a binding key of `extension-starter.timer`. You +can learn more about components in the +[related resources section](#related-resources). -This provider makes availble to your application a timer function which given a start time _(given as an array [seconds, nanoseconds])_ can give you a total time elapsed since the start in milliseconds. The timer can also start timing if no start time is given. This is used by LogComponent to allow a user to time a Sequence. +This provider makes availble to your application a timer function which given a +start time _(given as an array [seconds, nanoseconds])_ can give you a total +time elapsed since the start in milliseconds. The timer can also start timing if +no start time is given. This is used by LogComponent to allow a user to time a +Sequence. -*NOTE:* _You can get the start time in the required format by using `this.logger.startTimer()`._ +_NOTE:_ _You can get the start time in the required format by using +`this.logger.startTimer()`._ + +You can provide your own implementation of the elapsed time function by binding +it to the binding key (accessible via `ExtensionStarterBindings`) as follows: -You can provide your own implementation of the elapsed time function by binding it to the binding key (accessible via `ExtensionStarterBindings`) as follows: ```ts app.bind(ExtensionStarterBindings.TIMER).to(timerFn); ``` ### LogProvider -LogProvider can automatically be bound to your Application's Context using the LogComponent which exports the provider with a binding key of `extension-starter.actions.log`. +LogProvider can automatically be bound to your Application's Context using the +LogComponent which exports the provider with a binding key of +`extension-starter.actions.log`. The key can be accessed by importing `ExtensionStarterBindings` as follows: **Example: Binding Keys** + ```ts import {ExtensionStarterBindings} from 'HelloExtensions'; // Key can be accessed as follows now const key = ExtensionStarterBindings.LOG_ACTION; ``` -LogProvider gives us a seuqence action and a `startTimer` function. In order to use the sequence action, you must define your own sequence as shown below. +LogProvider gives us a seuqence action and a `startTimer` function. In order to +use the sequence action, you must define your own sequence as shown below. **Example: Sequence** + ```ts class LogSequence implements SequenceHandler { constructor( @inject(coreSequenceActions.FIND_ROUTE) protected findRoute: FindRoute, @inject(coreSequenceActions.PARSE_PARAMS) protected parseParams: ParseParams, - @inject(coreSequenceActions.INVOKE_METHOD) - protected invoke: InvokeMethod, + @inject(coreSequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, @inject(coreSequenceActions.SEND) protected send: Send, @inject(coreSequenceActions.REJECT) protected reject: Reject, // We get the logger injected by the LogProvider here @@ -80,12 +101,14 @@ class LogSequence implements SequenceHandler { } ``` -Once a sequence has been written, we can just use that in our Application as follows: +Once a sequence has been written, we can just use that in our Application as +follows: **Example: Application** + ```ts const app = new Application({ - sequence: LogSequence + sequence: LogSequence, }); app.component(LogComponent); @@ -94,7 +117,8 @@ app.component(LogComponent); ## Related Resources -You can check out the following resource to learn more about providers, components, sequences, and binding keys. +You can check out the following resource to learn more about providers, +components, sequences, and binding keys. - [Providers](http://loopback.io/doc/en/lb4/Creating-components.html#providers) - [Creating Components](http://loopback.io/doc/en/lb4/Creating-components.html) diff --git a/packages/cli/generators/extension/templates/src/repositories/README.md b/packages/cli/generators/extension/templates/src/repositories/README.md index 9e3d0085c52d..071b2246cfb4 100644 --- a/packages/cli/generators/extension/templates/src/repositories/README.md +++ b/packages/cli/generators/extension/templates/src/repositories/README.md @@ -2,4 +2,4 @@ This directory contains code for repositories provided by this extension. -For more information, see http://loopback.io/doc/en/lb4/Repositories.html. +For more information, see . diff --git a/packages/context/README.md b/packages/context/README.md index e036d590718b..9d346670f100 100644 --- a/packages/context/README.md +++ b/packages/context/README.md @@ -7,8 +7,10 @@ LoopBack framework. ## Overview The `@loopback/context` package exposes TypeScript/JavaScript APIs and -decorators to register artifacts, declare dependencies, and resolve artifacts -by keys. The `Context` also serves as an [IoC container](https://en.wikipedia.org/wiki/Inversion_of_control) to support [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection). +decorators to register artifacts, declare dependencies, and resolve artifacts by +keys. The `Context` also serves as an +[IoC container](https://en.wikipedia.org/wiki/Inversion_of_control) to support +[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection). `Context` and `Binding` are the two core concepts. A context is a registry of bindings and each binding represents a resolvable artifact by the key. @@ -21,12 +23,13 @@ bindings and each binding represents a resolvable artifact by the key. - Provide `@inject` and other variants of decorators to express dependencies. - Support Constructor, property, and method injections. - Allow contexts to form a hierarchy to share or override bindings. -- Track bindings and injections during resolution to detect circular dependencies. +- Track bindings and injections during resolution to detect circular + dependencies. ## Installation -``` -$ npm install --save @loopback/context +```sh +npm install --save @loopback/context ``` ## Basic use @@ -42,6 +45,7 @@ console.log(helloVal); // => 'world' ``` The binding can also be located asynchronously: + ```ts const helloVal = await ctx.get('hello'); console.log(helloVal); // => 'world' @@ -59,8 +63,8 @@ ctx.bind('greeting').to('Hello'); class HelloController { constructor( // injecting the value bound to `greeting` using context - @inject('greeting') private greeting: string) { - } + @inject('greeting') private greeting: string, + ) {} greet(name) { return `${this.greeting}, ${name}`; @@ -80,7 +84,8 @@ async function hello() { hello(); ``` -For additional information, please refer to the [Context page](http://loopback.io/doc/en/lb4/Context.html). +For additional information, please refer to the +[Context page](http://loopback.io/doc/en/lb4/Context.html). ## Contributions @@ -93,7 +98,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/context/test/acceptance/_feature.md b/packages/context/test/acceptance/1-feature.md similarity index 94% rename from packages/context/test/acceptance/_feature.md rename to packages/context/test/acceptance/1-feature.md index 1c207486aad3..0d589917796a 100644 --- a/packages/context/test/acceptance/_feature.md +++ b/packages/context/test/acceptance/1-feature.md @@ -24,7 +24,7 @@ child.get('foo'); // => 'bar' ```ts let ctx = new Context(); -ctx.bind(':name').to('hello world') +ctx.bind(':name').to('hello world'); await ctx.get('foo'); // => hello world await ctx.get('bat'); // => hello world @@ -40,10 +40,10 @@ await ctx.get('bat'); // => hello world let ctx = new Context(); let data = { foo: 'bar', - bat: 'baz' + bat: 'baz', }; -ctx.bind(':name').to((name) => { +ctx.bind(':name').to(name => { return data[name]; }); diff --git a/packages/context/test/acceptance/class-level-bindings.feature.md b/packages/context/test/acceptance/class-level-bindings.feature.md index d9a9d081bd75..67575b5af3ca 100644 --- a/packages/context/test/acceptance/class-level-bindings.feature.md +++ b/packages/context/test/acceptance/class-level-bindings.feature.md @@ -7,53 +7,51 @@ ## Scenario: Inject constructor arguments - - Given a context - - Given class `InfoController` with a constructor - accepting a single argument `appName: string` - - Given `InfoController` ctor argument is decorated - with `@inject('application.name')` - - Given a static binding named `application.name` with value `CodeHub` - - Given a class binding named `controllers.info` bound to class `InfoController` - - When I resolve the binding for `controllers.info` - - Then I get a new instance of `InfoController` - - And the instance was created with `appName` set to `CodeHub` - - ```ts - const ctx = new Context(); - ctx.bind('application.name').to('CodeHub'); - - class InfoController { - constructor(@inject('application.name') public appName: string) { - } - } - ctx.bind('controllers.info').toClass(InfoController); - - const instance = await ctx.get('controllers.info'); - instance.appName; // => CodeHub - ``` +- Given a context +- Given class `InfoController` with a constructor accepting a single argument + `appName: string` +- Given `InfoController` ctor argument is decorated with + `@inject('application.name')` +- Given a static binding named `application.name` with value `CodeHub` +- Given a class binding named `controllers.info` bound to class `InfoController` +- When I resolve the binding for `controllers.info` +- Then I get a new instance of `InfoController` +- And the instance was created with `appName` set to `CodeHub` + +```ts +const ctx = new Context(); +ctx.bind('application.name').to('CodeHub'); + +class InfoController { + constructor(@inject('application.name') public appName: string) {} +} +ctx.bind('controllers.info').toClass(InfoController); + +const instance = await ctx.get('controllers.info'); +instance.appName; // => CodeHub +``` ## Scenario: Inject instance properties - - Given a context - - Given class `InfoController` with a `appName: string` property - - Given `InfoController` with `appName` property decorated - with `@inject('application.name')` - - Given a static binding named `application.name` with value `CodeHub` - - Given a class binding named `controllers.info` bound to class `InfoController` - - When I resolve the binding for `controllers.info` - - Then I get a new instance of `InfoController` - - And the instance was created with `appName` set to `CodeHub` - - ```ts - const ctx = new Context(); - ctx.bind('application.name').to('CodeHub'); - - class InfoController { - @inject('application.name') - appName: string; - } - ctx.bind('controllers.info').toClass(InfoController); - - const instance = await ctx.get('controllers.info'); - instance.appName; // => CodeHub - ``` +- Given a context +- Given class `InfoController` with a `appName: string` property +- Given `InfoController` with `appName` property decorated with + `@inject('application.name')` +- Given a static binding named `application.name` with value `CodeHub` +- Given a class binding named `controllers.info` bound to class `InfoController` +- When I resolve the binding for `controllers.info` +- Then I get a new instance of `InfoController` +- And the instance was created with `appName` set to `CodeHub` + +```ts +const ctx = new Context(); +ctx.bind('application.name').to('CodeHub'); + +class InfoController { + @inject('application.name') appName: string; +} +ctx.bind('controllers.info').toClass(InfoController); + +const instance = await ctx.get('controllers.info'); +instance.appName; // => CodeHub +``` diff --git a/packages/context/test/acceptance/finding-bindings.feature.md b/packages/context/test/acceptance/finding-bindings.feature.md index 193126008c0f..c0862f2f116e 100644 --- a/packages/context/test/acceptance/finding-bindings.feature.md +++ b/packages/context/test/acceptance/finding-bindings.feature.md @@ -41,7 +41,7 @@ console.log(keys); // => ['foo', 'baz'] ```ts // create a container for bindings const ctx = new Context({ - delimiter: '.' // default + delimiter: '.', // default }); // creating three simple bindings @@ -52,7 +52,7 @@ ctx.bind('ur.quux').to('quuz'); // find all bindings const bindings = ctx.find('my.*'); -const keys = bindings.map(binding => binding.key) +const keys = bindings.map(binding => binding.key); console.log(keys); // => ['my.foo', 'my.baz'] ``` @@ -69,13 +69,22 @@ console.log(keys); // => ['my.foo', 'my.baz'] const ctx = new Context(); // bind some animals and tag them as dogs -ctx.bind('spot').to(new Dog()).tag('dog'); -ctx.bind('fido').to(new Dog()).tag('dog'); -ctx.bind('mew').to(new Dog()).tag('cat'); +ctx + .bind('spot') + .to(new Dog()) + .tag('dog'); +ctx + .bind('fido') + .to(new Dog()) + .tag('dog'); +ctx + .bind('mew') + .to(new Dog()) + .tag('cat'); // find by dog tag const bindings = ctx.findByTag('dog'); -const dogs = bindings.map(binding => binding.key) +const dogs = bindings.map(binding => binding.key); console.log(dogs); // => ['spot', 'fido'] ``` diff --git a/packages/context/test/acceptance/locking-bindings.feature.md b/packages/context/test/acceptance/locking-bindings.feature.md index d59e57c11178..61ddb184fc28 100644 --- a/packages/context/test/acceptance/locking-bindings.feature.md +++ b/packages/context/test/acceptance/locking-bindings.feature.md @@ -25,7 +25,7 @@ binding.lock(); // and binding another value with the same key try { ctx.bind('foo'); -} catch(e) { +} catch (e) { // e => Rebind error } ``` diff --git a/packages/context/test/acceptance/method-level-bindings.feature.md b/packages/context/test/acceptance/method-level-bindings.feature.md index 83d5c8d56e2e..be76379897ad 100644 --- a/packages/context/test/acceptance/method-level-bindings.feature.md +++ b/packages/context/test/acceptance/method-level-bindings.feature.md @@ -7,49 +7,46 @@ ## Scenario: Inject method arguments - - Given a context - - Given class `InfoController` - - Given a class binding named `controllers.info` bound to class `InfoController` - - When I resolve the binding for `controllers.info` - - Then I get a new instance of `InfoController` - - When I invoke the `hello` method, the parameter `user` is resolved to the - - value bound to `user` key in the context - - ```ts - class InfoController { - - static say(@inject('user') user: string):string { - const msg = `Hello ${user}`; - debug(msg); - return msg; - } - - hello(@inject('user') user: string):string { - const msg = `Hello ${user}`; - debug(msg); - return msg; - } - - greet(prefix: string, @inject('user') user: string):string { - const msg = `[${prefix}] Hello ${user}`; - debug(msg); - return msg; - } - } - - const ctx = new Context(); - // Mock up user authentication - ctx.bind('user').toDynamicValue(() => Promise.resolve('John')); - ctx.bind('controllers.info').toClass(InfoController); - - const instance = await ctx.get('controllers.info'); - // Invoke the `hello` method => Hello John - const helloMsg = await invokeMethod(instance, 'hello', ctx); - // Invoke the `greet` method with non-injected args => [INFO] Hello John - const greetMsg = await invokeMethod(instance, 'greet', ctx, ['INFO']); - - // Invoke the static `sayHello` method => [INFO] Hello John - const greetMsg = await invokeMethod(InfoController, 'sayHello', ctx); - ``` - - +- Given a context +- Given class `InfoController` +- Given a class binding named `controllers.info` bound to class `InfoController` +- When I resolve the binding for `controllers.info` +- Then I get a new instance of `InfoController` +- When I invoke the `hello` method, the parameter `user` is resolved to the +- value bound to `user` key in the context + +```ts +class InfoController { + static say(@inject('user') user: string): string { + const msg = `Hello ${user}`; + debug(msg); + return msg; + } + + hello(@inject('user') user: string): string { + const msg = `Hello ${user}`; + debug(msg); + return msg; + } + + greet(prefix: string, @inject('user') user: string): string { + const msg = `[${prefix}] Hello ${user}`; + debug(msg); + return msg; + } +} + +const ctx = new Context(); +// Mock up user authentication +ctx.bind('user').toDynamicValue(() => Promise.resolve('John')); +ctx.bind('controllers.info').toClass(InfoController); + +const instance = await ctx.get('controllers.info'); +// Invoke the `hello` method => Hello John +const helloMsg = await invokeMethod(instance, 'hello', ctx); +// Invoke the `greet` method with non-injected args => [INFO] Hello John +const greetMsg = await invokeMethod(instance, 'greet', ctx, ['INFO']); + +// Invoke the static `sayHello` method => [INFO] Hello John +const greetMsg = await invokeMethod(InfoController, 'sayHello', ctx); +``` diff --git a/packages/context/test/acceptance/tagged-bindings.feature.md b/packages/context/test/acceptance/tagged-bindings.feature.md index 8aad018f827a..e0a4d2fccf0c 100644 --- a/packages/context/test/acceptance/tagged-bindings.feature.md +++ b/packages/context/test/acceptance/tagged-bindings.feature.md @@ -17,7 +17,10 @@ let ctx = new Context(); // create a tagged binding -let binding = ctx.bind('foo').to('bar').tag('qux'); +let binding = ctx + .bind('foo') + .to('bar') + .tag('qux'); -console.log(binding.tags) // => Set { 'qux' } +console.log(binding.tags); // => Set { 'qux' } ``` diff --git a/packages/core/README.md b/packages/core/README.md index fd2e46a9149d..48888a357846 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -3,7 +3,7 @@ LoopBack makes it easy to build modern applications that require complex integrations. -# Overview +## Overview - Fast, small, powerful, extensible core - Generate real APIs with a single command @@ -22,14 +22,15 @@ $ npm install --save @loopback/core previous versions, it no longer contains the implementation for listening servers. -For a typical example of how to create a REST server with your application, -see the [@loopback/rest package.](https://github.com/strongloop/loopback-next/tree/master/packages/rest) +For a typical example of how to create a REST server with your application, see +the +[@loopback/rest package.](https://github.com/strongloop/loopback-next/tree/master/packages/rest) ## Advanced Use Since `@loopback/core` is decoupled from the listening server implementation, -LoopBack applications are now able to work with any component that provides -this functionality. +LoopBack applications are now able to work with any component that provides this +functionality. ```ts // index.ts @@ -45,22 +46,25 @@ const app = new Application({ port: 3001, }, }); -app.component(RestComponent) // REST Server -app.component(GrpcComponent) // GRPC Server - -(async function start() { - // Let's retrieve the bound instances of our servers. - const rest = await app.getServer('RestServer'); - const grpc = await app.getServer('GrpcServer'); - - // Define all sorts of bindings here to pass configuration or data - // between your server instances, define controllers and datasources for them, - // etc... - await app.start(); // This automatically spins up all your servers, too! - console.log(`REST server running on port: ${rest.getSync('rest.port')}`); - console.log(`GRPC server running on port: ${grpc.getSync('grpc.port')}`); -})(); +app.component(RestComponent); // REST Server +app.component(GrpcComponent)( + // GRPC Server + + async function start() { + // Let's retrieve the bound instances of our servers. + const rest = await app.getServer('RestServer'); + const grpc = await app.getServer('GrpcServer'); + + // Define all sorts of bindings here to pass configuration or data + // between your server instances, define controllers and datasources for them, + // etc... + await app.start(); // This automatically spins up all your servers, too! + console.log(`REST server running on port: ${rest.getSync('rest.port')}`); + console.log(`GRPC server running on port: ${grpc.getSync('grpc.port')}`); + }, +)(); ``` + In the above example, having a GRPC server mounted on your Application could enable communication with other GRPC-enabled microservices, allowing things like dynamic configuration updates. @@ -76,7 +80,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/example-getting-started/README.md b/packages/example-getting-started/README.md index ee2c65950bc2..b48c2f2e2e92 100644 --- a/packages/example-getting-started/README.md +++ b/packages/example-getting-started/README.md @@ -10,16 +10,18 @@ LoopBack 4. ## Setup You'll need to make sure you have some things installed: + - [Node.js](https://nodejs.org/en/) at v8.x or greater -Additionally, this tutorial assumes that you are comfortable with -certain technologies, languages and concepts. +Additionally, this tutorial assumes that you are comfortable with certain +technologies, languages and concepts. + - JavaScript (ES6) - [REST](http://www.restapitutorial.com/lessons/whatisrest.html) - Lastly, you'll need to install the LoopBack 4 CLI toolkit: -``` + +```sh npm i -g @loopback/cli ``` @@ -31,19 +33,22 @@ To follow this tutorial, begin with the ### Steps 1. [Create your app scaffolding](docs/scaffolding.md) -1. [Adding the legacy juggler](docs/juggler.md) -1. [Add your Todo model](docs/model.md) -1. [Add a datasource](docs/datasource.md) -1. [Add a repository](docs/repository.md) -1. [Add a controller](docs/controller.md) -1. [Putting it all together](docs/putting-it-together.md) +2. [Adding the legacy juggler](docs/juggler.md) +3. [Add your Todo model](docs/model.md) +4. [Add a datasource](docs/datasource.md) +5. [Add a repository](docs/repository.md) +6. [Add a controller](docs/controller.md) +7. [Putting it all together](docs/putting-it-together.md) ## Try it out + If you'd like to see the final results of this tutorial as an example application, follow these steps: -1. Run the `lb4 example` command to select and clone the getting-started repository: -``` +1. Run the `lb4 example` command to select and clone the getting-started + repository: + +```sh $ lb4 example ? What example would you like to clone? (Use arrow keys) ❯ getting-started: An application and tutorial on how to build with LoopBack 4. @@ -53,12 +58,14 @@ $ lb4 example ``` 2. Jump into the directory and then install the required dependencies: -``` -$ cd loopback4-example-getting-started && npm i + +```sh +cd loopback4-example-getting-started && npm i ``` 3. Finally, start the application! -``` + +```sh $ npm start Server is running on port 3000 @@ -69,10 +76,12 @@ works, or if you're still interested in learning how to build it step-by-step, then continue with this tutorial! ### Stuck? + Check out our [Gitter channel](https://gitter.im/strongloop/loopback) and ask for help with this tutorial! ### Bugs/Feedback + Open an issue in [loopback-next](https://github.com/strongloop/loopback-next) and we'll take a look! diff --git a/packages/example-getting-started/docs/controller.md b/packages/example-getting-started/docs/controller.md index eaa3c82b5de4..99aaf26ca7fb 100644 --- a/packages/example-getting-started/docs/controller.md +++ b/packages/example-getting-started/docs/controller.md @@ -1,9 +1,9 @@ ### Controllers In LoopBack 4, controllers handle the request-response lifecycle for your API. -Each function on a controller can be addressed individually to handle -an incoming request (like a POST request to `/todo`), perform business logic -and then return a response. +Each function on a controller can be addressed individually to handle an +incoming request (like a POST request to `/todo`), perform business logic and +then return a response. In this respect, controllers are the regions _in which most of your business logic will live_! @@ -12,17 +12,19 @@ logic will live_! So, let's create a controller to handle our Todo routes. Inside the `src/controllers` directory create the following two files: + - `index.ts` (export helper) - `todo.controller.ts` In addition to creating the handler functions themselves, we'll also be adding -decorators that setup the routing as well as the expected parameters of -incoming requests. +decorators that setup the routing as well as the expected parameters of incoming +requests. First, we need to define our basic controller class as well as plug in our repository, which we'll use to perform our operations against the datasource. #### src/controllers/todo.controller.ts + ```ts import {repository} from '@loopback/repository'; import {TodoRepository} from '../repositories'; @@ -35,21 +37,22 @@ export class TodoController { ``` The `@repository` decorator will retrieve and inject an instance of the -`TodoRepository` whenever an inbound request is being handled. The lifecycle -of controller objects is per-request, which means that a new controller instance -is created for each request. As a result, we want to inject our `TodoRepository` +`TodoRepository` whenever an inbound request is being handled. The lifecycle of +controller objects is per-request, which means that a new controller instance is +created for each request. As a result, we want to inject our `TodoRepository` since the creation of these instances is more complex and expensive than making new controller instances. ->**NOTE**: You can customize the lifecycle of *all* bindings in LoopBack 4! ->Controllers can easily be made to use singleton lifecycles to minimize startup ->costs. For more information, see the ->[Dependency injection](http://loopback.io/doc/en/lb4/Dependency-injection.html) ->section of our docs. +> **NOTE**: You can customize the lifecycle of _all_ bindings in LoopBack 4! +> Controllers can easily be made to use singleton lifecycles to minimize startup +> costs. For more information, see the +> [Dependency injection](http://loopback.io/doc/en/lb4/Dependency-injection.html) +> section of our docs. Now that we have the repository wireup, let's create our first handler function. #### src/controllers/todo.controller.ts + ```ts import {repository} from '@loopback/repository'; import {TodoRepository} from '../repositories'; @@ -74,16 +77,14 @@ export class TodoController { In this example, we're using two new decorators to provide LoopBack with metadata about the route, verb and the format of the incoming request body: -- `@post('/todo')` creates metadata for LoopBack's [RestServer]() so that it can -redirect requests to this function when the path and verb match. -- `@requestBody()` associates the OpenAPI schema for a Todo -with the body of the request so that LoopBack can validate the format of an -incoming request (**Note**: As of this writing, schematic validation is not yet -functional). +- `@post('/todo')` creates metadata for `@loopback/rest` so that it can redirect + requests to this function when the path and verb match. +- `@requestBody()` associates the OpenAPI schema for a Todo with the body of the + request so that LoopBack can validate the format of an incoming request + (**Note**: As of this writing, schematic validation is not yet functional). -We've also added our own validation logic to ensure that a user -will receive an error if they fail to provide a `title` property with their -`POST` request. +We've also added our own validation logic to ensure that a user will receive an +error if they fail to provide a `title` property with their `POST` request. Lastly, we are using the functions provided by our `TodoRepository` instance to perform a create operation against the datasource. @@ -92,6 +93,7 @@ You can use these and other decorators to create a REST API for a full set of verbs: #### src/controllers/todo.controller.ts + ```ts import {repository} from '@loopback/repository'; import {TodoRepository} from '../repositories'; @@ -158,14 +160,15 @@ export class TodoController { ``` Some additional things to note about this example: + - Routes like `@get('/todo/{id}')` can be paired with the `@param.path` -decorators to inject those values at request time into the handler function. + decorators to inject those values at request time into the handler function. - LoopBack's `@param` decorator also contains a namespace full of other -"subdecorators" like `@param.path`, `@param.query`, and `@param.header` that -allow specification of metadata for those parts of a REST request. + "subdecorators" like `@param.path`, `@param.query`, and `@param.header` that + allow specification of metadata for those parts of a REST request. - LoopBack's `@param.path` and `@param.query` also provide subdecorators for -specifying the type of certain value primitives, such as -`@param.path.number('id')`. + specifying the type of certain value primitives, such as + `@param.path.number('id')`. Now that we've wired up the controller, our last step is to tie it all into the [Application](putting-it-together.md)! diff --git a/packages/example-getting-started/docs/datasource.md b/packages/example-getting-started/docs/datasource.md index bbb894f14d47..bcc0b23b5422 100644 --- a/packages/example-getting-started/docs/datasource.md +++ b/packages/example-getting-started/docs/datasource.md @@ -1,12 +1,11 @@ ### Datasources -Datasources are LoopBack's way of connecting to various sources of data, such -as databases, APIs, message queues and more. In LoopBack 4, datasources can -be represented as strongly-typed objects and freely made available for -[injection](http://loopback.io/doc/en/lb4/Dependency-injection.html) -throughout the application. Typically, in LoopBack 4, datasources are used -in conjunction with -[Repositories](http://loopback.io/doc/en/lb4/Repositories.html) to provide +Datasources are LoopBack's way of connecting to various sources of data, such as +databases, APIs, message queues and more. In LoopBack 4, datasources can be +represented as strongly-typed objects and freely made available for +[injection](http://loopback.io/doc/en/lb4/Dependency-injection.html) throughout +the application. Typically, in LoopBack 4, datasources are used in conjunction +with [Repositories](http://loopback.io/doc/en/lb4/Repositories.html) to provide access to data. Since our Todo API will need to persist instances of Todo items, we'll need to @@ -14,11 +13,12 @@ create a datasource definition to make this possible. ### Building a Datasource -Create a new folder in the root directory of the project called `config`, -and then inside that folder, create a `datasources.json` file. For the purposes -of this tutorial, we'll be using the memory connector provided with the Juggler. +Create a new folder in the root directory of the project called `config`, and +then inside that folder, create a `datasources.json` file. For the purposes of +this tutorial, we'll be using the memory connector provided with the Juggler. #### config/datasources.json + ```json { "name": "ds", @@ -34,15 +34,15 @@ which we can consume in our application via injection. ```ts import * as path from 'path'; -import { DataSourceConstructor, juggler } from '@loopback/repository'; +import {DataSourceConstructor, juggler} from '@loopback/repository'; const dsConfigPath = path.resolve('config', 'datasources.json'); const config = require(dsConfigPath); export const db = new DataSourceConstructor(config); ``` -Once you're ready, we'll move onto adding a [repository](repository.md) for -the datasource. +Once you're ready, we'll move onto adding a [repository](repository.md) for the +datasource. ### Navigation diff --git a/packages/example-getting-started/docs/juggler.md b/packages/example-getting-started/docs/juggler.md index 247ebee954da..15a14fb93321 100644 --- a/packages/example-getting-started/docs/juggler.md +++ b/packages/example-getting-started/docs/juggler.md @@ -18,6 +18,7 @@ Next, modify `src/application.ts` to change the base class of your app to use the `RepositoryMixin`: #### src/application.ts + ```ts import {ApplicationConfig} from '@loopback/core'; import {RestApplication, RestServer} from '@loopback/rest'; @@ -69,13 +70,13 @@ export class TodoListApplication extends BootMixin( } ``` -Once you're ready, we'll move on to the [Add your Todo model](model.md) -section. +Once you're ready, we'll move on to the [Add your Todo model](model.md) section. For more information on the Legacy Juggler, check out the [@loopback/repository package](https://github.com/strongloop/loopback-next/tree/master/packages/repository) -or see the [Repositories section](http://loopback.io/doc/en/lb4/Repositories.html) -of our docs. +or see the +[Repositories section](http://loopback.io/doc/en/lb4/Repositories.html) of our +docs. ### Navigation diff --git a/packages/example-getting-started/docs/model.md b/packages/example-getting-started/docs/model.md index 8234d68e4dbb..47e6c54c7bae 100644 --- a/packages/example-getting-started/docs/model.md +++ b/packages/example-getting-started/docs/model.md @@ -3,39 +3,40 @@ Now we can begin working on the representation of our data for use with LoopBack 4. To that end, we're going to create a Todo model that can represent instances of a task for our Todo list. The Todo model will serve both as a -[Data Transfer Object](https://en.wikipedia.org/wiki/Data_transfer_object) -(also known as a DTO) for representing incoming Todo instances on requests, -as well as our data structure for use with the Legacy Juggler. - ->**NOTE:** LoopBack 3 treated models as the "center" of operations; in -LoopBack 4, that is no longer the case. While LoopBack 4 provides many of the -helper methods and decorators that allow you to utilize models in a similar way, -you are no longer _required_ to do so! +[Data Transfer Object](https://en.wikipedia.org/wiki/Data_transfer_object) (also +known as a DTO) for representing incoming Todo instances on requests, as well as +our data structure for use with the Legacy Juggler. +> **NOTE:** LoopBack 3 treated models as the "center" of operations; in LoopBack +> 4, that is no longer the case. While LoopBack 4 provides many of the helper +> methods and decorators that allow you to utilize models in a similar way, you +> are no longer _required_ to do so! ### Building your Todo model -A todo list is all about tracking tasks. For this to be useful, -it will need to let you label tasks so that you can distinguish between them, -add extra information to describe those tasks, and finally, provide a way of -tracking whether or not they're complete. +A todo list is all about tracking tasks. For this to be useful, it will need to +let you label tasks so that you can distinguish between them, add extra +information to describe those tasks, and finally, provide a way of tracking +whether or not they're complete. For our Todo model to represent our Todo instances, it will need: + - a unique id - a title - a description that details what the todo is all about - a boolean flag for whether or not we've completed the task Inside the `src/models` folder, create two files: + - `index.ts` - `todo.model.ts` ->**NOTE:** -The `index.ts` file is an export helper file; this pattern is a huge time-saver -as the number of models in your project grows, because it allows you to point -to the _directory_ when attempting to import types from a file within the target -folder. **We will use this concept throughout the tutorial! For more info, -see TypeScript's [Module Resolution](https://www.typescriptlang.org/docs/handbook/module-resolution.html) docs.** +> **NOTE:** +> The `index.ts` file is an export helper file; this pattern is a huge time-saver +> as the number of models in your project grows, because it allows you to point +> to the _directory_ when attempting to import types from a file within the target +> folder. **We will use this concept throughout the tutorial! For more info, +> see TypeScript's [Module Resolution](https://www.typescriptlang.org/docs/handbook/module-resolution.html) docs.** ```ts // in src/models/index.ts @@ -55,11 +56,12 @@ import {Baz} from './models/baz.model'; // things tidy and succinct! ``` -For the Legacy Juggler to understand how to work with our model class, it -will need to extend the `Entity` type, as well as provide an override for -the `getId` function, so that it can retrieve a Todo model's ID as needed. +For the Legacy Juggler to understand how to work with our model class, it will +need to extend the `Entity` type, as well as provide an override for the `getId` +function, so that it can retrieve a Todo model's ID as needed. #### src/models/todo.model.ts + ```ts import {Entity, property, model} from '@loopback/repository'; @@ -67,23 +69,23 @@ import {Entity, property, model} from '@loopback/repository'; export class Todo extends Entity { @property({ type: 'number', - id: true + id: true, }) id?: number; @property({ type: 'string', - required: true + required: true, }) title: string; @property({ - type: 'string' + type: 'string', }) desc?: string; @property({ - type: 'boolean' + type: 'boolean', }) isComplete: boolean; @@ -93,8 +95,8 @@ export class Todo extends Entity { } ``` -Now that we have our model, it's time to add a -[datasource](datasource.md) so we can perform real CRUD operations! +Now that we have our model, it's time to add a [datasource](datasource.md) so we +can perform real CRUD operations! ### Navigation diff --git a/packages/example-getting-started/docs/putting-it-together.md b/packages/example-getting-started/docs/putting-it-together.md index ed97e06af2d3..04168e1f0075 100644 --- a/packages/example-getting-started/docs/putting-it-together.md +++ b/packages/example-getting-started/docs/putting-it-together.md @@ -7,23 +7,24 @@ system can tie it all together for us! LoopBack's [boot module](https://github.com/strongloop/loopback-next/tree/master/packages/boot) -will automatically discover our controllers, repositories, datasources and -other artifacts and inject them into our application for use. - ->**NOTE**: The boot module will discover and inject artifacts that -> follow our established conventions for artifact directories. ->Here are some examples: ->* Controllers: `./src/controllers` ->* Datasources: `./src/datasources` ->* Models: `./src/models` ->* Repositories: `./src/repositories` +will automatically discover our controllers, repositories, datasources and other +artifacts and inject them into our application for use. + +> **NOTE**: The boot module will discover and inject artifacts that follow our +> established conventions for artifact directories. Here are some examples: +> +> - Controllers: `./src/controllers` +> - Datasources: `./src/datasources` +> - Models: `./src/models` +> - Repositories: `./src/repositories` > ->To find out how to customize this behaviour, see the ->[Booters](http://loopback.io/doc/en/lb4/Booting-an-Application.html#booters) ->section of ->[Booting an Application](http://loopback.io/doc/en/lb4/Booting-an-Application.html). +> To find out how to customize this behaviour, see the +> [Booters](http://loopback.io/doc/en/lb4/Booting-an-Application.html#booters) +> section of +> [Booting an Application](http://loopback.io/doc/en/lb4/Booting-an-Application.html). #### src/application.ts + ```ts import {ApplicationConfig} from '@loopback/core'; import {RestApplication, RestServer} from '@loopback/rest'; @@ -86,14 +87,13 @@ export class TodoListApplication extends BootMixin( console.log(`Try http://127.0.0.1:${port}/ping`); } } - ``` ### Try it out -Let's try out our application! -First, you'll want to start the app. -``` +Let's try out our application! First, you'll want to start the app. + +```sh $ npm start Server is running on port 3000 ``` @@ -102,9 +102,10 @@ Next, you can use the [API Explorer](http://localhost:3000/swagger-ui) to browse your API and make requests! Here are some requests you can try: + - `POST /todo` with a body of `{ "title": "get the milk" }` - `GET /todo/{id}` using the ID you received from your `POST`, and see if you -get your Todo object back. + get your Todo object back. - `PATCH /todo/{id}` with a body of `{ "desc": "need milk for cereal" }` That's it! You've just created your first LoopBack 4 application! diff --git a/packages/example-getting-started/docs/repository.md b/packages/example-getting-started/docs/repository.md index 45027df0a12b..5610c171d551 100644 --- a/packages/example-getting-started/docs/repository.md +++ b/packages/example-getting-started/docs/repository.md @@ -1,28 +1,30 @@ ### Repositories The repository pattern is one of the more fundamental differences between -LoopBack 3 and 4. In LoopBack 3, you would use the model class definitions themselves -to perform CRUD operations. In LoopBack 4, the layer responsible for this has -been separated from the definition of the model itself, into the repository -layer. +LoopBack 3 and 4. In LoopBack 3, you would use the model class definitions +themselves to perform CRUD operations. In LoopBack 4, the layer responsible for +this has been separated from the definition of the model itself, into the +repository layer. ### Create your repository In the `src/repositories` directory, create two files: + - `index.ts` (our export helper) - `todo.repository.ts` Our TodoRepository will extend a small base class that uses the `DefaultCrudRepository` class from [`@loopback/repository`](https://github.com/strongloop/loopback-next/tree/master/packages/repository) -and will define the model type we're working with, as well as its ID type. -This automatically gives us the basic CRUD methods required for performing -operations against our database (or any other kind of datasource). +and will define the model type we're working with, as well as its ID type. This +automatically gives us the basic CRUD methods required for performing operations +against our database (or any other kind of datasource). We'll also inject our datasource so that this repository can connect to it when executing data operations. #### src/repositories/todo.repository.ts + ```ts import {DefaultCrudRepository, DataSourceType} from '@loopback/repository'; import {Todo} from '../models'; diff --git a/packages/example-getting-started/docs/scaffolding.md b/packages/example-getting-started/docs/scaffolding.md index 27af7271b68b..31c897357067 100644 --- a/packages/example-getting-started/docs/scaffolding.md +++ b/packages/example-getting-started/docs/scaffolding.md @@ -1,12 +1,13 @@ ### Create your app scaffolding -The LoopBack 4 CLI toolkit comes with templates that allow you to generate -whole applications, as well as artifacts (ex. controllers, models, repositories) -for existing applications. +The LoopBack 4 CLI toolkit comes with templates that allow you to generate whole +applications, as well as artifacts (ex. controllers, models, repositories) for +existing applications. -To generate your application using the toolkit, run the `lb4 app` command -and fill out the on-screen prompts: -``` +To generate your application using the toolkit, run the `lb4 app` command and +fill out the on-screen prompts: + +```sh $ lb4 app ? Project name: todo-list ? Project description: A todo list API made with LoopBack 4. @@ -26,10 +27,11 @@ whether or not to enable certain project features (loopback's build, tslint, mocha, etc.), leave them all enabled. ### Structure -After your application is generated, you will have a folder structure similar -to this: -``` +After your application is generated, you will have a folder structure similar to +this: + +```text src/ controllers/ README.md @@ -59,27 +61,27 @@ tslint.build.json tslint.json ``` -| File | Purpose | -|------|---------| -| index.ts | Allows importing contents of the `src` folder (for use elsewhere) | -| index.js | Top-level wireup for execution of the application. | -| package.json | Your application's package manifest. See [package.json](https://docs.npmjs.com/files/package.json) for details. | -| tsconfig.json | The TypeScript project configuration. See [tsconfig.json](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html) for details. | -| tslint.json | [TSLint configuration](https://palantir.github.io/tslint/usage/tslint-json/) | -| tslint.build.json | [TSLint configuration (build only)](https://palantir.github.io/tslint/usage/tslint-json/) | -| README.md | The Markdown-based README generated for your application. | -| LICENSE | A copy of the MIT license. If you do not wish to use this license, please delete this file. | -| src/application.ts | The application class, which extends [`RestApplication`](http://apidocs.strongloop.com/@loopback%2frest/#RestApplication) by default. This is the root of your application, and is where your application will be configured. | -| src/index.ts | The starting point of your microservice. This file creates an instance of your application, runs the booter, then attempts to start the [`RestServer`](http://apidocs.strongloop.com/@loopback%2frest/#RestServer) instance bound to the application. | -| src/sequence.ts | An extension of the [Sequence](http://loopback.io/doc/en/lb4/Sequence.html) class used to define the set of actions to take during a REST request/response. | -| src/controllers/README.md | Provides information about the controller directory, how to generate new controllers, and where to find more information. | -| src/controllers/ping.controller.ts | A basic controller that responds to GET requests at `/ping`. | -| src/datasources/README.md | Provides information about the datasources directory, how to generate new datasources, and where to find more information. | -| src/models/README.md | Provides information about the datasources directory, how to generate new datasources, and where to find more information. | -| src/repositories/README.md | Provides information about the repositories directory, how to generate new repositories, and where to find more information. | -| test/README.md | Please place your tests in this folder. | -| test/mocha.opts | [Mocha](https://mochajs.org/) configuration for running your application's tests. | -| test/ping.controller.test.ts | An example test to go with the ping controller in `src/controllers`. | +| File | Purpose | +| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| index.ts | Allows importing contents of the `src` folder (for use elsewhere) | +| index.js | Top-level wireup for execution of the application. | +| package.json | Your application's package manifest. See [package.json](https://docs.npmjs.com/files/package.json) for details. | +| tsconfig.json | The TypeScript project configuration. See [tsconfig.json](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html) for details. | +| tslint.json | [TSLint configuration](https://palantir.github.io/tslint/usage/tslint-json/) | +| tslint.build.json | [TSLint configuration (build only)](https://palantir.github.io/tslint/usage/tslint-json/) | +| README.md | The Markdown-based README generated for your application. | +| LICENSE | A copy of the MIT license. If you do not wish to use this license, please delete this file. | +| src/application.ts | The application class, which extends [`RestApplication`](http://apidocs.strongloop.com/@loopback%2frest/#RestApplication) by default. This is the root of your application, and is where your application will be configured. | +| src/index.ts | The starting point of your microservice. This file creates an instance of your application, runs the booter, then attempts to start the [`RestServer`](http://apidocs.strongloop.com/@loopback%2frest/#RestServer) instance bound to the application. | +| src/sequence.ts | An extension of the [Sequence](http://loopback.io/doc/en/lb4/Sequence.html) class used to define the set of actions to take during a REST request/response. | +| src/controllers/README.md | Provides information about the controller directory, how to generate new controllers, and where to find more information. | +| src/controllers/ping.controller.ts | A basic controller that responds to GET requests at `/ping`. | +| src/datasources/README.md | Provides information about the datasources directory, how to generate new datasources, and where to find more information. | +| src/models/README.md | Provides information about the datasources directory, how to generate new datasources, and where to find more information. | +| src/repositories/README.md | Provides information about the repositories directory, how to generate new repositories, and where to find more information. | +| test/README.md | Please place your tests in this folder. | +| test/mocha.opts | [Mocha](https://mochajs.org/) configuration for running your application's tests. | +| test/ping.controller.test.ts | An example test to go with the ping controller in `src/controllers`. | ### Navigation diff --git a/packages/example-hello-world/README.md b/packages/example-hello-world/README.md index fef22e855b14..2092f4a5ce76 100644 --- a/packages/example-hello-world/README.md +++ b/packages/example-hello-world/README.md @@ -11,10 +11,12 @@ our application to always respond with "Hello World!". ## Prerequisites Before we can begin, you'll need to make sure you have some things installed: + - [Node.js](https://nodejs.org/en/) at v8.x or greater -Additionally, this tutorial assumes that you are comfortable with -certain technologies, languages and concepts. +Additionally, this tutorial assumes that you are comfortable with certain +technologies, languages and concepts. + - JavaScript (ES6) - [npm](https://www.npmjs.com/) - [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) @@ -22,17 +24,20 @@ certain technologies, languages and concepts. ## Installation 1. Install the new loopback CLI toolkit. -``` + +```sh npm i -g @loopback/cli ``` 2. Download the "hello-world" application. -``` + +```sh lb4 example hello-world ``` 3. Switch to the directory and install dependencies. -``` + +```sh cd loopback4-example-hello-world && npm i ``` @@ -40,7 +45,7 @@ cd loopback4-example-hello-world && npm i Start the app: -``` +```sh npm start ``` diff --git a/packages/example-log-extension/README.md b/packages/example-log-extension/README.md index e3aea94401bd..b78348aae170 100644 --- a/packages/example-log-extension/README.md +++ b/packages/example-log-extension/README.md @@ -4,34 +4,33 @@ An example repo showing how to write a complex log extension for LoopBack 4 ## Overview -This repository shows you how to use [@loopback/cli](https://github.com/strongloop/loopback-next/tree/master/packages/cli) -to write a complex logging extension that requires a [Component](http://loopback.io/doc/en/lb4/Using-components.html), -[Decorator](http://loopback.io/doc/en/lb4/Decorators.html), and a [Mixin](http://loopback.io/doc/en/lb4/Mixin.html). +This repository shows you how to use +[@loopback/cli](https://github.com/strongloop/loopback-next/tree/master/packages/cli) +to write a complex logging extension that requires a +[Component](http://loopback.io/doc/en/lb4/Using-components.html), +[Decorator](http://loopback.io/doc/en/lb4/Decorators.html), and a +[Mixin](http://loopback.io/doc/en/lb4/Mixin.html). To use this extension you can add the `LogMixin` to your Application which will -provide you a function to set the Application wide log level as well as automatically -load the `LogComponent`. Only Controller methods configured at or above the -logLevel will be logged. +provide you a function to set the Application wide log level as well as +automatically load the `LogComponent`. Only Controller methods configured at or +above the logLevel will be logged. _You may alternatively load `LogComponent` yourself and set the log level using the appropriate binding keys manually if you don't wish to use the `LogMixin`._ -Possible levels are: DEBUG < INFO < WARN < ERROR < OFF +Possible levels are: DEBUG < INFO < WARN < ERROR < OFF -*Possible levels are represented as numbers but users can use `LOG_LEVEL.${level}` -to specify the value instead of using numbers.* +_Possible levels are represented as numbers but users can use +`LOG_LEVEL.${level}` to specify the value instead of using numbers._ -A decorator enables you to set the log level for Controller methods, at or -above which it should be logged. +A decorator enables you to set the log level for Controller methods, at or above +which it should be logged. ### Example Usage ```ts -import { - LogMixin, - LOG_LEVEL, - log -} from 'loopback4-example-log-extension'; +import {LogMixin, LOG_LEVEL, log} from 'loopback4-example-log-extension'; // Other imports ... class LogApp extends LogMixin(BootMixin(RestApplication)) { @@ -40,7 +39,7 @@ class LogApp extends LogMixin(BootMixin(RestApplication)) { this.projectRoot = __dirname; this.logLevel(LOG_LEVEL.ERROR); - }; + } } class MyController { @@ -53,7 +52,7 @@ class MyController { @log(LOG_LEVEL.ERROR) @get('/name') helloName() { - return 'Hello Name' + return 'Hello Name'; } } ``` @@ -63,7 +62,7 @@ class MyController { You can obtain a local clone of this project (without the rest of our monorepo) using the following command: -``` +```sh lb4 example getting-started ``` @@ -71,18 +70,19 @@ lb4 example getting-started Install `@loopback/cli` by running `npm i -g @loopback/cli`. -Initialize your new extension project as follows: -`lb4 extension` +Initialize your new extension project as follows: `lb4 extension` - Project name: `loopback4-example-log-extension` - Project description: `An example extension project for LoopBack 4` - Project root directory: `(loopback4-example-log-extension)` - Component class name: `LogComponent` -- Select project build settings: `Enable tslint, Enable prettier, Enable mocha, Enable loopbackBuild` +- Select project build settings: + `Enable tslint, Enable prettier, Enable mocha, Enable loopbackBuild` Now you can write the extension as follows: ### `/src/keys.ts` + Define `Binding` keys here for the component as well as any constants for the user (for this extension that'll be the logLevel `enum`). @@ -110,8 +110,8 @@ export enum LOG_LEVEL { } ``` - ### `src/types.ts` + Before we continue, we will need to install a new dependecy as follows: ```shell @@ -161,6 +161,7 @@ export type TimerFn = (start?: HighResTime) => HighResTime; ``` ### `src/decorators/log.decorator.ts` + Extension developers can create decorators to provide "hints" (or metadata) to user artifacts such as controllers and their methods. These "hints" allow the extension to add extra processing accordingly. @@ -216,12 +217,14 @@ export function getLogMetadata( ``` ### `src/mixins/log-level.mixin.ts` + Extension users must set an app wide log level at or above which the decorated controller methods will be logged. A user can do so by binding the level to `example.log.level` but this can be a hassle. A mixin makes it easier for the user to set the application wide log level by -providing it via `ApplicationOptions` or using a helper method `app.logLevel(level: number)`. +providing it via `ApplicationOptions` or using a helper method +`app.logLevel(level: number)`. ```ts import {Constructor} from '@loopback/context'; @@ -248,10 +251,12 @@ export function LogMixin>(superClass: T) { ``` ### Providers + A Providers is a class that returns a `value()` function that can be invoked by LoopBack 4. ### `src/providers/timer.provider.ts` + A timer than can be used to time the function that is being logged. ```ts @@ -270,10 +275,11 @@ export class TimerProvider implements Provider { ``` ### `src/providers/log-action.provider.ts` + This will be the most important provider for the extension as it is responsible for actually logging the request. The extension will retrieve the metadata -stored by the `@log()` decorator using the controller and method name. -Since bindings are resolved at runtime and these values change with each request, +stored by the `@log()` decorator using the controller and method name. Since +bindings are resolved at runtime and these values change with each request, `inject.getter()` must be used to get a function capable of resolving the value when called. The action provider will look as follows: @@ -385,6 +391,7 @@ function logToConsole(msg: string, level: number) { ``` ### `src/index.ts` + Export all the files to ensure a user can import the necessary components. ```ts @@ -398,8 +405,10 @@ export * from './keys'; ``` ### `src/component.ts` + Package the providers in the component to their appropriate `Binding` keys so -they are automatically bound when a user adds the component to their application. +they are automatically bound when a user adds the component to their +application. ```ts import {Component, ProviderMap} from '@loopback/core'; @@ -418,13 +427,13 @@ export class LogComponent implements Component { ## Testing Tests should be written to ensure the behaviour implemented is correct and -future modifications don't break this expected behavior *(unless it's -intentional in which case the tests should be updated as well)*. +future modifications don't break this expected behavior _(unless it's +intentional in which case the tests should be updated as well)_. Take a look at the test folder to see the variety of tests written for this -extension. There are unit tests to test functionality of individual functions -as well as an extension acceptance test which tests the entire extension as a -whole (everything working together). +extension. There are unit tests to test functionality of individual functions as +well as an extension acceptance test which tests the entire extension as a whole +(everything working together). ## Contributions diff --git a/packages/example-rpc-server/README.md b/packages/example-rpc-server/README.md index 178e3880af65..8f738ba564f4 100644 --- a/packages/example-rpc-server/README.md +++ b/packages/example-rpc-server/README.md @@ -1,39 +1,44 @@ # @loopback/example-rpc-server -An example RPC server and application to demonstrate the creation of your -own custom server. +An example RPC server and application to demonstrate the creation of your own +custom server. -[![LoopBack]https://github.com/strongloop/loopback-next/blob/master/docs/site/imgs/branding/Powered-by-LoopBack-Badge-(blue)-%402x.png](http://loopback.io/) +[![powered-by-loopback]](http://loopback.io/) ## Usage 1. Install the new loopback CLI toolkit. -``` + +```sh npm i -g @loopback/cli ``` 2. Download the "rpc-server" application. -``` + +```sh lb4 example rpc-server ``` 3. Switch to the directory and install dependencies. -``` + +```sh cd loopback-example-rpc-server && npm i ``` 4. Start the app! -``` + +```sh npm start ``` -Next, use your favourite REST client to send RPC payloads to the server -(hosted on port 3000). +Next, use your favorite REST client to send RPC payloads to the server (hosted +on port 3000). ## Request Format The request body should contain a controller name, method name and input object. Example: + ```json { "controller": "GreetController", @@ -43,6 +48,7 @@ Example: } } ``` + The router will determine which controller and method will service your request based on the given names in the payload. diff --git a/packages/example-rpc-server/test/README.md b/packages/example-rpc-server/test/README.md index 2243988ea7e1..a88f8a543566 100644 --- a/packages/example-rpc-server/test/README.md +++ b/packages/example-rpc-server/test/README.md @@ -1,4 +1,3 @@ # Tests Please place your tests in this folder. - diff --git a/packages/metadata/README.md b/packages/metadata/README.md index e065716670bd..f0df293d2af2 100644 --- a/packages/metadata/README.md +++ b/packages/metadata/README.md @@ -1,14 +1,15 @@ # @loopback/metadata -This module contains utilities to help developers implement [TypeScript decorators](https://www.typescriptlang.org/docs/handbook/decorators.html), define/merge -metadata, and inspect metadata. +This module contains utilities to help developers implement +[TypeScript decorators](https://www.typescriptlang.org/docs/handbook/decorators.html), +define/merge metadata, and inspect metadata. -* Reflector: Wrapper of +- Reflector: Wrapper of [reflect-metadata](https://github.com/rbuckton/reflect-metadata) -* Decorator factories: A set of factories for class/method/property/parameter +- Decorator factories: A set of factories for class/method/property/parameter decorators to apply metadata to a given class and its static or instance members. -* MetadataInspector: High level APIs to inspect a class and/or its members to +- MetadataInspector: High level APIs to inspect a class and/or its members to get metadata applied by decorators. ## Basic Use @@ -136,7 +137,8 @@ function myParameterDecorator(spec: MyParameterMetadata): ParameterDecorator { } ``` -Now we can use `@myParameterDecorator` to add metadata to a parameter as follows: +Now we can use `@myParameterDecorator` to add metadata to a parameter as +follows: ```ts class MyController { @@ -183,17 +185,14 @@ function myMethodParameterDecorator( } ``` -Now we can use `@myMethodParameterDecorator` to add metadata to a parameter -as follows: +Now we can use `@myMethodParameterDecorator` to add metadata to a parameter as +follows: ```ts class MyController { @myMethodParameterDecorator({name: 'x'}) @myMethodParameterDecorator({name: 'y'}) - myMethod( - x: number, - y: number, - ) {} + myMethod(x: number, y: number) {} } ``` @@ -210,116 +209,117 @@ We recommend that `ParameterDecorator` be used instead. An object of type `DecoratorOptions` can be passed in to create decorator functions. There are two flags for the options: -- allowInheritance: Controls if inherited metadata will be honored. Default to `true`. +- allowInheritance: Controls if inherited metadata will be honored. Default to + `true`. - cloneInputSpec: Controls if the value of `spec` argument will be cloned. -Sometimes we use shared spec for the decoration, but the decorator function -might need to mutate the object. Cloning the input spec makes it safe to use -the same spec (`template`) to decorate different members. Default to `true`. + Sometimes we use shared spec for the decoration, but the decorator function + might need to mutate the object. Cloning the input spec makes it safe to use + the same spec (`template`) to decorate different members. Default to `true`. ### Customize inheritance of metadata By default, the decorator factories allow inheritance with the following rules: -1. If the metadata is an object, we merge the `spec` argument from the decorator - function into the inherited value from base classes. For metadata of array and - other primitive types, the `spec` argument is used if provided. +1. If the metadata is an object, we merge the `spec` argument from the + decorator function into the inherited value from base classes. For metadata + of array and other primitive types, the `spec` argument is used if provided. - - We can override `inherit` method of the decorator factory to customize - how to resolve `spec` against the inherited metadata. For example: + - We can override `inherit` method of the decorator factory to customize how + to resolve `spec` against the inherited metadata. For example: - ```ts - protected inherit(inheritedMetadata: T | undefined | null): T { - // Ignore the inherited metadata - return this.spec; - } - ``` +```ts +protected inherit(inheritedMetadata: T | undefined | null): T { + // Ignore the inherited metadata + return this.spec; +} +``` 2. Method/property/parameter level metadata is applied to the class or its - prototype as a map keyed method/property names. We think this approach is better - than keeping metadata at method/property level as it's not easy to inspect a - class to find static/instance methods and properties with decorations. The - metadata for a class is illustrated below: - - - MyClass (the constructor function itself) - - ```ts - { - // Class level metadata - 'my-class-decorator-key': MyClassMetadata, - // Static method (including the constructor) parameter metadata - 'my-static-parameter-decorator-key': { - '': [MyConstructorParameterMetadata], // Constructor parameter metadata - 'myStaticMethod1': [MyStaticMethodParameterMetadata], - 'myStaticMethod2': [MyStaticMethodParameterMetadata], - }, - // Static method metadata - 'my-static-method-decorator-key': { - 'myStaticMethod1': MyStaticMethodMetadata, - 'myStaticMethod2': MyStaticMethodMetadata, - }, - // Static property metadata - 'my-static-property-decorator-key': { - 'myStaticMethod1': MyStaticPropertyMetadata, - 'myStaticMethod1': MyStaticPropertyMetadata, - } - } - ``` - - - MyClass.prototype - - ```ts - { - // Instance method parameter metadata - 'my-instance-parameter-decorator-key': { - 'myMethod1': [MyMethodParameterMetadata], - 'myMethod2': [MyMethodParameterMetadata], - }, - // Instance method metadata - 'my-instance-method-decorator-key': { - 'myMethod1': MyMethodMetadata, - 'myMethod2': MyMethodMetadata, - }, - // Instance property metadata - 'my-instance-property-decorator-key': { - 'myProperty1': MyPropertyMetadata, - 'myProperty2': MyPropertyMetadata, - } - } - ``` - - The following methods in `DecoratorFactory` allow subclasses to customize how - to merge the `spec` with existing metadata for a class, methods, properties, and - method parameters. Please note `M` is a map for methods/properties/parameters. - - ```ts - protected mergeWithInherited( - inheritedMetadata: M, - target: Object, - member?: string | symbol, - descriptorOrIndex?: TypedPropertyDescriptor | number, - ): M { - // ... - } - - protected mergeWithOwn( - ownMetadata: M, - target: Object, - member?: string | symbol, - descriptorOrIndex?: TypedPropertyDescriptor | number, - ): M { - // ... - } - ``` - -3. The default implementation throws errors if the same decorator function is applied -to a given target member (class/method/property/parameter) more than once. -For example, the following usage will report an error at runtime. - - ```ts - @myClassDecorator({name: 'my-controller'}) - @myClassDecorator({name: 'your-controller'}) - class MyController {} - ``` + prototype as a map keyed method/property names. We think this approach is + better than keeping metadata at method/property level as it's not easy to + inspect a class to find static/instance methods and properties with + decorations. The metadata for a class is illustrated below: + + - MyClass (the constructor function itself) + +```ts +{ + // Class level metadata + 'my-class-decorator-key': MyClassMetadata, + // Static method (including the constructor) parameter metadata + 'my-static-parameter-decorator-key': { + '': [MyConstructorParameterMetadata], // Constructor parameter metadata + 'myStaticMethod1': [MyStaticMethodParameterMetadata], + 'myStaticMethod2': [MyStaticMethodParameterMetadata], + }, + // Static method metadata + 'my-static-method-decorator-key': { + 'myStaticMethod1': MyStaticMethodMetadata, + 'myStaticMethod2': MyStaticMethodMetadata, + }, + // Static property metadata + 'my-static-property-decorator-key': { + 'myStaticMethod1': MyStaticPropertyMetadata, + 'myStaticMethod1': MyStaticPropertyMetadata, + } +} +``` + +- MyClass.prototype + +```ts +{ + // Instance method parameter metadata + 'my-instance-parameter-decorator-key': { + 'myMethod1': [MyMethodParameterMetadata], + 'myMethod2': [MyMethodParameterMetadata], + }, + // Instance method metadata + 'my-instance-method-decorator-key': { + 'myMethod1': MyMethodMetadata, + 'myMethod2': MyMethodMetadata, + }, + // Instance property metadata + 'my-instance-property-decorator-key': { + 'myProperty1': MyPropertyMetadata, + 'myProperty2': MyPropertyMetadata, + } +} +``` + +The following methods in `DecoratorFactory` allow subclasses to customize how to +merge the `spec` with existing metadata for a class, methods, properties, and +method parameters. Please note `M` is a map for methods/properties/parameters. + +```ts +protected mergeWithInherited( + inheritedMetadata: M, + target: Object, + member?: string | symbol, + descriptorOrIndex?: TypedPropertyDescriptor | number, +): M { + // ... +} + +protected mergeWithOwn( + ownMetadata: M, + target: Object, + member?: string | symbol, + descriptorOrIndex?: TypedPropertyDescriptor | number, +): M { + // ... +} +``` + +3. The default implementation throws errors if the same decorator function is + applied to a given target member (class/method/property/parameter) more than + once. For example, the following usage will report an error at runtime. + +```ts +@myClassDecorator({name: 'my-controller'}) +@myClassDecorator({name: 'your-controller'}) +class MyController {} +``` ### Inspect metadata @@ -337,7 +337,7 @@ const meta = MetadataInspector.getClassMetadata( ); ``` -## Inspect own metadata of a class +### Inspect own metadata of a class ```ts import {MetadataInspector} from '@loopback/metadata'; @@ -390,27 +390,26 @@ const myProp = MetadataInspector.getMethodMetaData( ```ts import {MetadataInspector} from '@loopback/metadata'; -const allParamsForMyMethod = - MetadataInspector.getAllParameterMetaData( - 'my-parameter-decorator-key', - MyController.prototype, // Use MyController for static methods, - 'myMethod', - ); +const allParamsForMyMethod = MetadataInspector.getAllParameterMetaData< + MyParameterMetadata +>( + 'my-parameter-decorator-key', + MyController.prototype, // Use MyController for static methods, + 'myMethod', +); -const firstParamForMyMethod = - MetadataInspector.getMyParameterMetaData( - 'my-parameter-decorator-key', - MyController.prototype, // Use MyController for static methods - 'myMethod', - 0, // parameter index - ); +const firstParamForMyMethod = MetadataInspector.getMyParameterMetaData< + MyParameterMetadata +>( + 'my-parameter-decorator-key', + MyController.prototype, // Use MyController for static methods + 'myMethod', + 0, // parameter index +); -const allParamsForConstructor = - MetadataInspector.getAllParameterMetaData( - 'my-parameter-decorator-key', - MyController, - '', - ); +const allParamsForConstructor = MetadataInspector.getAllParameterMetaData< + MyParameterMetadata +>('my-parameter-decorator-key', MyController, ''); ``` ### Inspect design-time metadata of properties/methods @@ -436,8 +435,8 @@ const myMethod = MetadataInspector.getDesignTypeForMethod( ## Installation -``` -$ npm install --save @loopback/metadata +```sh +npm install --save @loopback/metadata ``` ## Contributions diff --git a/packages/openapi-spec-builder/README.md b/packages/openapi-spec-builder/README.md index 87af8d3d0587..4464835b4440 100644 --- a/packages/openapi-spec-builder/README.md +++ b/packages/openapi-spec-builder/README.md @@ -1,32 +1,36 @@ # @loopback/openapi-spec-builder -Make it easy to create OpenAPI (Swagger) specification documents in your -tests using the builder pattern. +Make it easy to create OpenAPI (Swagger) specification documents in your tests +using the builder pattern. ## Overview Creating a full OpenAPI spec document in automated tests is rather cumbersome, -long JSON-like objects pollute the test test code and make it difficult -for readers to distinguish between what's important in the test and what's just +long JSON-like objects pollute the test test code and make it difficult for +readers to distinguish between what's important in the test and what's just shared swagger boilerplate. OpenApiSpecBuilder utilizes -[Test Data Builder pattern](http://www.natpryce.com/articles/000714.html) -to provide a TypeScript/JavaScript API allowing users to create -full OpenAPI Spec documents in few lines of code. +[Test Data Builder pattern](http://www.natpryce.com/articles/000714.html) to +provide a TypeScript/JavaScript API allowing users to create full OpenAPI Spec +documents in few lines of code. ## Installation -```shell -$ npm install --save-dev @loopback/openapi-spec-builder +```sh +npm install --save-dev @loopback/openapi-spec-builder ``` -_This package is typically used in tests, save it to `devDependencies` via `--save-dev`._ +_This package is typically used in tests, save it to `devDependencies` via +`--save-dev`._ ## Basic use ```ts -import {anOpenApiSpec, OpenApiSpecBuilder} from '@loopback/openapi-spec-builder'; +import { + anOpenApiSpec, + OpenApiSpecBuilder, +} from '@loopback/openapi-spec-builder'; const spec = anOpenApiSpec() .withOperationReturningString('get', '/hello', 'greet') @@ -38,7 +42,7 @@ const spec = new OpenApiSpecBuilder() .withOperation('get', '/hello', { 'x-operation-name': 'greet', responses: { - '200': { type: 'string' }, + '200': {type: 'string'}, }, }) .build(); @@ -48,7 +52,7 @@ const spec = new OpenApiSpecBuilder() const spec = { basePath: '/', swagger: '2.0', - info: { title: 'LoopBack Application', version: '1.0.0' }, + info: {title: 'LoopBack Application', version: '1.0.0'}, paths: { '/hello': { get: { @@ -57,19 +61,20 @@ const spec = { '200': { description: 'The string result.', schema: { - type: 'string' - } + type: 'string', }, + }, }, - } - } - } + }, + }, + }, }; ``` ## Related resources -See https://www.openapis.org/ and [version 2.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md) +See and +[version 2.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md) of OpenAPI Specification. ## Contributions @@ -83,7 +88,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/openapi-v3-types/README.md b/packages/openapi-v3-types/README.md index 88c16b116372..30a5f3ab4ea8 100644 --- a/packages/openapi-v3-types/README.md +++ b/packages/openapi-v3-types/README.md @@ -9,8 +9,8 @@ including LoopBack-specific extensions. ## Installation -``` -$ npm install --save @loopback/openapi-v3-types +```sh +npm install --save @loopback/openapi-v3-types ``` ## Basic use @@ -25,14 +25,15 @@ export function validateSpec(spec: OpenApiSpec) { } ``` -IDEs like Visual Studio Code will offer auto-completion for spec properties -when constructing a spec argument value. +IDEs like Visual Studio Code will offer auto-completion for spec properties when +constructing a spec argument value. ## Related resources -See https://www.openapis.org/ and [version 3.0.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) -of OpenAPI Specification. -For the types missing in this package, they are exported from https://www.npmjs.com/package/openapi3-ts +See and +[version 3.0.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) +of OpenAPI Specification. For the types missing in this package, they are +exported from ## Contributions @@ -45,7 +46,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/openapi-v3/README.md b/packages/openapi-v3/README.md index d0d30e709b11..5c532094cde0 100644 --- a/packages/openapi-v3/README.md +++ b/packages/openapi-v3/README.md @@ -1,30 +1,33 @@ -@loopback/openapi-v3 +# @loopback/openapi-v3 This package contains: -* Decorators that describe LoopBack artifacts as OpenAPI 3.0.0 metadata. -* Utilities that transfer LoopBack metadata to OpenAPI 3.0.0 specifications. +- Decorators that describe LoopBack artifacts as OpenAPI 3.0.0 metadata. +- Utilities that transfer LoopBack metadata to OpenAPI 3.0.0 specifications. ## Overview The package has functions described above for LoopBack controller classes. -Decorators apply REST api mapping metadata to controller classes and their members. And utilities that inspect controller classes to build OpenAPI 3.0.0 specifications from REST API mapping metadata. +Decorators apply REST api mapping metadata to controller classes and their +members. And utilities that inspect controller classes to build OpenAPI 3.0.0 +specifications from REST API mapping metadata. Functions for more artifacts will be added when we need. ## Installation -``` -$ npm install --save @loopback/openapi-v3 +```sh +npm install --save @loopback/openapi-v3 ``` ## Basic use -Currently this package only has spec generator for controllers. -It generates OpenAPI specifications for a given decorated controller class, including +Currently this package only has spec generator for controllers. It generates +OpenAPI specifications for a given decorated controller class, including `paths`, `components.schemas`, and `servers`. -Here is an example of calling function `getControllerSpec` to generate the OpenAPI spec: +Here is an example of calling function `getControllerSpec` to generate the +OpenAPI spec: ```js import {api, getControllerSpec} from '@loopback/openapi-v3'; @@ -53,11 +56,13 @@ then the `myControllerSpec` will be: } ``` -For details of how to apply controller decorators, please check http://loopback.io/doc/en/lb4/Decorators.html#route-decorators +For details of how to apply controller decorators, please check + ## Related resources -See https://www.openapis.org/ and [version 3.0.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) +See and +[version 3.0.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) of OpenAPI Specification. ## Contributions @@ -71,7 +76,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/repository-json-schema/README.md b/packages/repository-json-schema/README.md index a89c1c77d63d..07280715ac7c 100644 --- a/packages/repository-json-schema/README.md +++ b/packages/repository-json-schema/README.md @@ -1,10 +1,12 @@ # @loopback/repository-json-schema -Convert a TypeScript class/model to a JSON Schema for users, leveraging LoopBack4's decorators, metadata, and reflection system. +Convert a TypeScript class/model to a JSON Schema for users, leveraging +LoopBack4's decorators, metadata, and reflection system. ## Overview -This package provides modules to easily convert LoopBack4 models that have been decorated with `@model` and `@property` to a matching JSON Schema Definition. +This package provides modules to easily convert LoopBack4 models that have been +decorated with `@model` and `@property` to a matching JSON Schema Definition. ## Installation @@ -50,7 +52,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/repository/README.md b/packages/repository/README.md index 578e6366764b..cc96db186e9a 100644 --- a/packages/repository/README.md +++ b/packages/repository/README.md @@ -14,8 +14,8 @@ well as the constructs for modeling and accessing those data. ## Installation -``` -$ npm install --save @loopback/repository +```sh +npm install --save @loopback/repository ``` ## Basic use @@ -176,6 +176,7 @@ interface CustomerRepository extends Repository { ``` See more examples at: + - [Repository/CrudRepository/EntityRepository](src/repository.ts) - [KVRepository](src/kv-repository.ts) @@ -198,6 +199,7 @@ There are two subtly different types of models for domain objects: equality is based on the structural value. For example, `Address` can be modeled as `Value Object` as two US addresses are equal if they have the same street number, street name, city, and zip code values. For example: + ```json { "name": "Address", @@ -255,11 +257,10 @@ specific backend system, such as a database, a REST service, a SOAP Web Service, or a gRPC micro-service. It abstracts such interactions as a list of operations in the form of Node.js methods. -Typically, a connector translates LoopBack query -and mutation requests into native api calls supported by the underlying Node.js -driver for the given backend. For example, a connector for `MySQL` will map -`create` method to SQL INSERT statement, which can be executed through MySQL -driver for Node.js. +Typically, a connector translates LoopBack query and mutation requests into +native api calls supported by the underlying Node.js driver for the given +backend. For example, a connector for `MySQL` will map `create` method to SQL +INSERT statement, which can be executed through MySQL driver for Node.js. ### Mixin @@ -309,9 +310,9 @@ coercion. The following types are supported out of box. ## Related resources -- https://martinfowler.com/eaaCatalog/repository.html -- https://msdn.microsoft.com/en-us/library/ff649690.aspx -- http://docs.spring.io/spring-data/data-commons/docs/2.0.0.M3/reference/html/#repositories +- +- +- ## Contributions @@ -324,7 +325,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/rest/README.md b/packages/rest/README.md index 18df1d34a406..80865227a11f 100644 --- a/packages/rest/README.md +++ b/packages/rest/README.md @@ -1,20 +1,25 @@ # @loopback/rest -The REST API package for [loopback-next](https://github.com/strongloop/loopback-next). +The REST API package for +[loopback-next](https://github.com/strongloop/loopback-next). ## STATUS: Developer Preview #1 + This package is not yet ready for production use. Additionally, this document is also a work-in-progress and some sections may not have corresponding code! ## Overview + This component provides a REST server for your application instances, complete with: + - new custom routing engine (special thanks to @bajtos)! - tools for defining your application routes - OpenAPI 3.0 spec (`openapi.json`/`openapi.yaml`) generation using `@loopback/openapi-v3` - a default sequence implementation to manage the request and response lifecycle ## Installation + To use this package, you'll need to install `@loopback/rest`. ```sh @@ -22,42 +27,44 @@ npm i @loopback/rest ``` ## Basic Use + Here's a basic "Hello World" application using `@loopback/rest`: - ```ts - import {RestApplication, RestServer} from '@loopback/rest'; +```ts +import {RestApplication, RestServer} from '@loopback/rest'; - const app = new RestApplication(); - app.handler((sequence, request, response) => { - sequence.send(response, 'hello world'); - }); +const app = new RestApplication(); +app.handler((sequence, request, response) => { + sequence.send(response, 'hello world'); +}); - (async function start() { - await app.start(); +(async function start() { + await app.start(); - const rest = await app.getServer(RestServer); - const port = await server.get('rest.port'); - console.log(`Server is running at http://127.0.0.1:${port}`); - })(); - ``` + const rest = await app.getServer(RestServer); + const port = await server.get('rest.port'); + console.log(`Server is running at http://127.0.0.1:${port}`); +})(); +``` ## Configuration + The rest package is configured by passing a `rest` property inside of your Application options. ```ts - const app = new RestApplication({ - rest: { - port: 3001 - } - }); +const app = new RestApplication({ + rest: { + port: 3001, + }, +}); ``` ### `rest` options -| Property | Type | Purpose | -|----------|------|---------| -| port | number | Specify the port on which the RestServer will listen for traffic. | +| Property | Type | Purpose | +| -------- | --------------- | --------------------------------------------------------------------------------------------------------- | +| port | number | Specify the port on which the RestServer will listen for traffic. | | sequence | SequenceHandler | Use a custom SequenceHandler to change the behavior of the RestServer for the request-response lifecycle. | ## Contributions @@ -71,7 +78,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License diff --git a/packages/rest/test/acceptance/module-exporting/feature.md b/packages/rest/test/acceptance/module-exporting/feature.md index d631ae9adc18..9ecaa5d442d2 100644 --- a/packages/rest/test/acceptance/module-exporting/feature.md +++ b/packages/rest/test/acceptance/module-exporting/feature.md @@ -9,7 +9,8 @@ - Given a Node.js project - And LoopBack.next installed as a project dependency -- When I import LoopBack.next's Application construct via Node.js's require syntax +- When I import LoopBack.next's Application construct via Node.js's require + syntax - Then I get an instantiable Application object ```js diff --git a/packages/rest/test/acceptance/routing/feature.md b/packages/rest/test/acceptance/routing/feature.md index a0d308681179..c532450fabac 100644 --- a/packages/rest/test/acceptance/routing/feature.md +++ b/packages/rest/test/acceptance/routing/feature.md @@ -65,11 +65,10 @@ app.bind('definitions.controllers').to({ foo: { path: './foo.controller.ts', name: 'FooController', - baseUrl: '/foo' - } + baseUrl: '/foo', + }, }); - app .bind(':type.:name') .toDynamicValue((type, name) => { @@ -100,7 +99,6 @@ app.bind('currentMethod').toDynamicValue(() => { return remoteMethod; }); - server.on('request', async (req, res) => { let ctx = new Context(); ctx.bind('url').to(req.url); @@ -114,7 +112,8 @@ server.on('request', async (req, res) => { // allow apps to create / customize bindings controller.bind(); - ctx.bind('result') + ctx + .bind('result') .toPromise(async (reject, resolve, ctx) => { let method = await ctx.get('currentMethod'); let methodArgs = await ctx.get('currentArgs'); diff --git a/packages/testlab/README.md b/packages/testlab/README.md index bfcb39894acf..87a8410c8c93 100644 --- a/packages/testlab/README.md +++ b/packages/testlab/README.md @@ -5,22 +5,26 @@ A collection of test utilities we use to write LoopBack tests. ## Overview Test utilities to help writing LoopBack 4 tests: + - `expect` - behavior-driven development (BDD) style assertions - `sinon` - - test spies: functions recording arguments and other information for all of their calls - - stubs: functions (spies) with pre-programmed behavior - - mocks: fake methods (like spies) with pre-programmed behavior (like stubs) as well as pre-programmed expectations + - test spies: functions recording arguments and other information for all of + their calls + - stubs: functions (spies) with pre-programmed behavior + - mocks: fake methods (like spies) with pre-programmed behavior (like stubs) + as well as pre-programmed expectations - Helpers for creating `supertest` clients for LoopBack applications - HTTP request/response stubs for writing tests without a listening HTTP server - Swagger/OpenAPI spec validation ## Installation -``` -$ npm install --save-dev @loopback/testlab +```sh +npm install --save-dev @loopback/testlab ``` -_This package is typically used in tests, save it to `devDependencies` via `--save-dev`._ +_This package is typically used in tests, save it to `devDependencies` via +`--save-dev`._ ## Basic use @@ -44,7 +48,7 @@ describe('Basic assertions', => { ### `sinon` -Spies, mocks and stubs. Learn more at [http://sinonjs.org/](http://sinonjs.org/). +Spies, mocks and stubs. Learn more at . ### `shot` @@ -63,13 +67,14 @@ describe('MyApp', () => { const app = new MyApp(); const server = await app.getServer(RestServer); await validateApiSpec(server.getApiSpec()); - }) + }); }); ``` ## Related resources -For more info about `supertest`, please refer to [supertest](https://www.npmjs.com/package/supertest) +For more info about `supertest`, please refer to +[supertest](https://www.npmjs.com/package/supertest) ## Contributions @@ -82,7 +87,8 @@ Run `npm test` from the root folder. ## Contributors -See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). ## License