From c6366b99ee9935f8e881a3709ee20612ba40f514 Mon Sep 17 00:00:00 2001 From: Kevin Delisle Date: Mon, 26 Feb 2018 14:41:31 -0500 Subject: [PATCH] docs(example-getting-started): tutorial cleanup --- packages/example-getting-started/README.md | 77 ++++++-- .../docs/1-prerequisites-and-setup.md | 34 ---- .../docs/2-scaffold-app.md | 19 -- .../docs/3-add-legacy-juggler.md | 59 ------ .../docs/5-datasource.md | 40 ----- .../docs/6-repository.md | 33 ---- .../docs/7-controller.md | 71 -------- .../docs/8-putting-it-together.md | 66 ------- .../docs/controller.md | 169 ++++++++++++++++++ .../docs/datasource.md | 53 ++++++ .../example-getting-started/docs/juggler.md | 67 +++++++ .../docs/{4-todo-model.md => model.md} | 73 +++++--- .../docs/putting-it-together.md | 103 +++++++++++ .../docs/repository.md | 50 ++++++ .../docs/scaffolding.md | 86 +++++++++ 15 files changed, 646 insertions(+), 354 deletions(-) delete mode 100644 packages/example-getting-started/docs/1-prerequisites-and-setup.md delete mode 100644 packages/example-getting-started/docs/2-scaffold-app.md delete mode 100644 packages/example-getting-started/docs/3-add-legacy-juggler.md delete mode 100644 packages/example-getting-started/docs/5-datasource.md delete mode 100644 packages/example-getting-started/docs/6-repository.md delete mode 100644 packages/example-getting-started/docs/7-controller.md delete mode 100644 packages/example-getting-started/docs/8-putting-it-together.md create mode 100644 packages/example-getting-started/docs/controller.md create mode 100644 packages/example-getting-started/docs/datasource.md create mode 100644 packages/example-getting-started/docs/juggler.md rename packages/example-getting-started/docs/{4-todo-model.md => model.md} (53%) create mode 100644 packages/example-getting-started/docs/putting-it-together.md create mode 100644 packages/example-getting-started/docs/repository.md create mode 100644 packages/example-getting-started/docs/scaffolding.md diff --git a/packages/example-getting-started/README.md b/packages/example-getting-started/README.md index 177887461981..3d99125f6b97 100644 --- a/packages/example-getting-started/README.md +++ b/packages/example-getting-started/README.md @@ -2,23 +2,76 @@ This is the basic tutorial for getting started with Loopback 4! -To get started, jump into the -[Prerequisites and setup](docs/1-prerequisites-and-setup.md) section. +## Overview -## Tutorial Steps +This tutorial demonstrates how to create a basic API for a todo list using +LoopBack 4. -1. [Prerequisites and setup](docs/1-prerequisites-and-setup.md) -1. [Scaffolding your application](docs/2-scaffold-app.md) -1. [Adding the legacy juggler](docs/3-add-legacy-juggler.md) -1. [Add your Todo model](docs/4-todo-model.md) -1. [Add a datasource](docs/5-datasource.md) -1. [Add a repository](docs/6-repository.md) -1. [Add a controller](docs/7-controller.md) -1. [Putting it all together](docs/8-putting-it-together.md) +## 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. +- JavaScript (ES6) +- [REST](http://www.restapitutorial.com/lessons/whatisrest.html) + + +Lastly, you'll need to install the LoopBack 4 CLI toolkit: +``` +npm i -g @loopback/cli +``` + +## Tutorial + +To follow this tutorial, begin with the +[Create your app scaffolding](docs/scaffolding.md) section. + +### 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) + +## 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: +``` +$ 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 + log-extension: An example extension project for LoopBack 4 + rpc-server: A basic RPC server using a made-up protocol. +``` + +2. Jump into the directory and then install the required dependencies: +``` +$ cd loopback4-example-getting-started && npm i +``` + +3. Finally, start the application! +``` +$ npm start + +Server is running on port 3000 +``` + +Feel free to look around in the application's code to get a feel for how it +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 this repository **OR** on [loopback-next](https://github.com/strongloop/loopback-next) and we'll take a look! +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/1-prerequisites-and-setup.md b/packages/example-getting-started/docs/1-prerequisites-and-setup.md deleted file mode 100644 index 713e5c2a6e2c..000000000000 --- a/packages/example-getting-started/docs/1-prerequisites-and-setup.md +++ /dev/null @@ -1,34 +0,0 @@ -## Prerequisites - -Before we can begin, you'll need to make sure you have some things installed: -- [Node.js](https://nodejs.org/en/) at v6.x or greater - -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) - -## Setup -1. Install the new loopback CLI toolkit. -``` -npm i -g @loopback/cli -``` -2. Download the "getting-started" application. -``` -lb4 example getting-started -``` - -3. Switch to the directory and install dependencies. -``` -cd loopback-example-getting-started && npm i -``` - -4. Start the app! -``` -npm start -``` - -### Navigation - -Next step: [Scaffolding your application](2-scaffold-app.md) diff --git a/packages/example-getting-started/docs/2-scaffold-app.md b/packages/example-getting-started/docs/2-scaffold-app.md deleted file mode 100644 index eefbd82b8013..000000000000 --- a/packages/example-getting-started/docs/2-scaffold-app.md +++ /dev/null @@ -1,19 +0,0 @@ -### Create your app scaffolding - -Install the `@loopback/cli` package. This will give you the command-line -toolkit that can generate a basic REST app for you. -`npm i -g @loopback/cli` - -Next, navigate to whichever directory you'd like to create your new project -and run `lb4`. Follow the prompts to generate your application. For this -tutorial, when prompted with the options for selecting things like whether or -not to enable certain project features (loopback's build, tslint, mocha, etc.), -leave them all enabled. - - - -### Navigation - -Previous step: [Prerequisites and setup](1-prerequisites-and-setup.md) - -Next step: [Adding the legacy juggler](3-add-legacy-juggler.md) diff --git a/packages/example-getting-started/docs/3-add-legacy-juggler.md b/packages/example-getting-started/docs/3-add-legacy-juggler.md deleted file mode 100644 index d11d7717b592..000000000000 --- a/packages/example-getting-started/docs/3-add-legacy-juggler.md +++ /dev/null @@ -1,59 +0,0 @@ -### Adding the Legacy Juggler - -Jump into the directory for your new application. You'll see a folder structure -similar to this: -``` -dist\ -node_modules\ -src\ - controllers\ - ping.controller.ts - README.md - repositories\ - README.md - application.ts - index.ts -test\ - mocha.opts - ping.controller.test.ts - README.md -index.js -index.d.ts -index.ts -``` - -The application template comes with a controller, and some default wireup in -`src/application.ts` that handles the basic configuration for your application. -For this tutorial, we won't need `ping.controller.ts` or its corresponding test, -but you can leave them in for now. - -Now that you have your setup, it's time to modify it to add in -`@loopback/repository`. Install this dependency by running -`npm i --save @loopback/repository`. - -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} from '@loopback/rest'; -import {PingController} from './controllers/ping-controller'; -import {Class, Repository, RepositoryMixin} from '@loopback/repository'; - -export class TodoApplication extends RepositoryMixin(RestApplication) { - constructor(options?: ApplicationConfig) { - super(options); - this.setupControllers(); - } - - setupControllers() { - this.controller(PingController); - } -} -``` -### Navigation - -Previous step: [Scaffolding your application](2-scaffold-app.md) - -Next step: [Add your Todo model](4-todo-model.md) diff --git a/packages/example-getting-started/docs/5-datasource.md b/packages/example-getting-started/docs/5-datasource.md deleted file mode 100644 index 958259971413..000000000000 --- a/packages/example-getting-started/docs/5-datasource.md +++ /dev/null @@ -1,40 +0,0 @@ -### Building a Datasource - -Before we can begin constructing controllers and repositories for our -application, we need to define our 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 now, we'll -be using the memory connector provided with the Juggler. - -#### config/datasources.json -```json -{ - "name": "ds", - "connector": "memory" -} -``` - -Create another folder called `datasources` in the `src` directory, and inside -that folder, create a new file called `db.datasource.ts`. - -#### src/datasources/db.datasource.ts - -```ts -import * as path from 'path'; -import * as fs from 'fs'; -import { DataSourceConstructor, juggler } from '@loopback/repository'; - -const dsConfigPath = path.resolve('config', 'datasources.json'); -const config = require(dsConfigPath); -export const db = new DataSourceConstructor(config); -``` - -This will give us a strongly-typed datasource export that we can work with to -construct our TodoRepository definition. - -### Navigation - -Previous step: [Add your Todo model](4-todo-model.md) - -Next step: [Add a repository](6-repository.md) diff --git a/packages/example-getting-started/docs/6-repository.md b/packages/example-getting-started/docs/6-repository.md deleted file mode 100644 index 95569abc0935..000000000000 --- a/packages/example-getting-started/docs/6-repository.md +++ /dev/null @@ -1,33 +0,0 @@ -### Create your repository - -Create another folder in `src` called `repositories` and inside of that folder, -create two files: -- `index.ts` (our export helper) -- `todo.repository.ts` - -Our TodoRepository will contain a small base class that uses the -`DefaultCrudRepository` class from `@loopback/repository` and will define the -model type we're working with, as well as its ID type. 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'; -import { inject } from '@loopback/core'; - -export class TodoRepository extends DefaultCrudRepository< - Todo, - typeof Todo.prototype.id -> { - constructor(@inject('datasource') protected datasource: DataSourceType) { - super(Todo, datasource); - } -} -``` -### Navigation - -Previous step: [Add a datasource](5-datasource.md) - -Next step: [Add a controller](7-controller.md) diff --git a/packages/example-getting-started/docs/7-controller.md b/packages/example-getting-started/docs/7-controller.md deleted file mode 100644 index 96d16354e8aa..000000000000 --- a/packages/example-getting-started/docs/7-controller.md +++ /dev/null @@ -1,71 +0,0 @@ -### Create your controller - -Now, we'll create a controller to handle our Todo routes. Create the -`src/controllers` directory and two files inside: -- `index.ts` (export helper) -- `todo.controller.ts` - -In addition to creating the CRUD methods themselves, we'll also be adding -decorators that setup the routing as well as the expected parameters of -incoming requests. - -#### src/controllers/todo.controller.ts -```ts -import {post, param, get, put, patch, del} from '@loopback/openapi-v2'; -import {HttpErrors} from '@loopback/rest'; -import {TodoSchema, Todo} from '../models'; -import {repository} from '@loopback/repository'; -import {TodoRepository} from '../repositories/index'; - -export class TodoController { - constructor( - @repository(TodoRepository.name) protected todoRepo: TodoRepository, - ) {} - @post('/todo') - @param.body('todo', TodoSchema) - async createTodo(todo: Todo) { - if (!todo.title) { - return Promise.reject(new HttpErrors.BadRequest('title is required')); - } - return await this.todoRepo.create(todo); - } - - @get('/todo/{id}') - @param.path.number('id') - @param.query.boolean('items') - async findTodoById(id: number, items?: boolean): Promise { - return await this.todoRepo.findById(id); - } - - @get('/todo') - async findTodos(): Promise { - return await this.todoRepo.find(); - } - - @put('/todo/{id}') - @param.path.number('id') - @param.body('todo', TodoSchema) - async replaceTodo(id: number, todo: Todo): Promise { - return await this.todoRepo.replaceById(id, todo); - } - - @patch('/todo/{id}') - @param.path.number('id') - @param.body('todo', TodoSchema) - async updateTodo(id: number, todo: Todo): Promise { - return await this.todoRepo.updateById(id, todo); - } - - @del('/todo/{id}') - @param.path.number('id') - async deleteTodo(id: number): Promise { - return await this.todoRepo.deleteById(id); - } -} -``` - -### Navigation - -Previous step: [Add a repository](6-repository.md) - -Final step: [Putting it all together](8-putting-it-together.md) diff --git a/packages/example-getting-started/docs/8-putting-it-together.md b/packages/example-getting-started/docs/8-putting-it-together.md deleted file mode 100644 index 579af3919815..000000000000 --- a/packages/example-getting-started/docs/8-putting-it-together.md +++ /dev/null @@ -1,66 +0,0 @@ -### Putting it all together - -Now that we've got all of our artifacts made, let's set them up in our -application! - -We'll define a new helper function for setting up the repositories, as well -as adding in our new controller binding. - -#### src/application.ts -```ts -import {ApplicationConfig} from '@loopback/core'; -import {RestApplication} from '@loopback/rest'; -import {TodoController, PingController} from './controllers'; -import { - Class, - Repository, - RepositoryMixin, - DataSourceConstructor, -} from '@loopback/repository'; -import {db} from './datasources/db.datasource'; -import {TodoRepository} from './repositories'; - -export class TodoApplication extends RepositoryMixin(RestApplication) { - constructor(options?: ApplicationConfig) { - super(options); - this.setupControllers(); - this.setupRepositories(); - } - - setupControllers() { - this.controller(TodoController); - this.controller(PingController); - } - - setupRepositories() { - // This will allow you to test your application without needing to - // use the "real" datasource! - const datasource = - this.options && this.options.datasource - ? new DataSourceConstructor(this.options.datasource) - : db; - this.bind('datasource').to(datasource); - this.repository(TodoRepository); - } -} -``` - -### Try it out - -Now that your app is ready to go, try it out with your favourite REST client! -Start the app (`npm start`) and then make some REST requests: -- `POST /todo` with a body of `{ "title": "get the milk" }` -- `GET /todo/1` and see if you get your Todo object back. -- `PATCH /todo/1` with a body of `{ "desc": "need milk for cereal" }` - -### Navigation - -Previous step: [Add a controller](7-controller.md) - -### More examples and tutorials - -Eager to continue learning about LoopBack 4? Check out our -[examples and tutorials](https://loopback.io/doc/en/lb4/Examples-and-tutorials.html) -section to find examples for creating your own custom components, sequences and -more! - diff --git a/packages/example-getting-started/docs/controller.md b/packages/example-getting-started/docs/controller.md new file mode 100644 index 000000000000..d9807690e1fe --- /dev/null +++ b/packages/example-getting-started/docs/controller.md @@ -0,0 +1,169 @@ +### 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. + +In this respect, controllers are the regions _in which most of your business +logic will live_! + +### Create your controller + +So, let's create a controller to handle our Todo routes. Create the +`src/controllers` directory and two files inside: +- `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. + +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/index'; + +export class TodoController { + constructor( + @repository(TodoRepository.name) protected todoRepo: TodoRepository, + ) {} +} +``` + +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` +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. + +Now that we have the repository wireup, let's create our first handler function. + +#### src/controllers/todo.controller.ts +```ts +import {post, param} from '@loopback/openapi-v2'; +import {HttpErrors} from '@loopback/rest'; +import {TodoSchema, Todo} from '../models'; +import {repository} from '@loopback/repository'; +import {TodoRepository} from '../repositories/index'; + +export class TodoController { + constructor( + @repository(TodoRepository.name) protected todoRepo: TodoRepository, + ) {} + + @post('/todo') + @param.body('todo', TodoSchema) + async createTodo(todo: Todo) { + if (!todo.title) { + return Promise.reject(new HttpErrors.BadRequest('title is required')); + } + return await this.todoRepo.create(todo); + } +} +``` + +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. +- `@param.body('todo', TodoSchema)` 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. + +Lastly, we are using the functions provided by our `TodoRepository` instance to +perform a create operation against the datasource. + +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 {post, param, get, put, patch, del} from '@loopback/openapi-v2'; +import {HttpErrors} from '@loopback/rest'; +import {TodoSchema, Todo} from '../models'; +import {repository} from '@loopback/repository'; +import {TodoRepository} from '../repositories/index'; + +export class TodoController { + constructor( + @repository(TodoRepository.name) protected todoRepo: TodoRepository, + ) {} + + @post('/todo') + @param.body('todo', TodoSchema) + async createTodo(todo: Todo) { + if (!todo.title) { + return Promise.reject(new HttpErrors.BadRequest('title is required')); + } + return await this.todoRepo.create(todo); + } + + @get('/todo/{id}') + @param.path.number('id') + @param.query.boolean('items') + async findTodoById(id: number, items?: boolean): Promise { + return await this.todoRepo.findById(id); + } + + @get('/todo') + async findTodos(): Promise { + return await this.todoRepo.find(); + } + + @put('/todo/{id}') + @param.path.number('id') + @param.body('todo', TodoSchema) + async replaceTodo(id: number, todo: Todo): Promise { + return await this.todoRepo.replaceById(id, todo); + } + + @patch('/todo/{id}') + @param.path.number('id') + @param.body('todo', TodoSchema) + async updateTodo(id: number, todo: Todo): Promise { + return await this.todoRepo.updateById(id, todo); + } + + @del('/todo/{id}') + @param.path.number('id') + async deleteTodo(id: number): Promise { + return await this.todoRepo.deleteById(id); + } +} +``` + +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. +- LoopBack's `@param` decorator also contains a namespace full of other +"subdecorators" like `@param.path`, `@param.query`, and `@param.body` 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')`. + +Now that we've wired up the controller, our last step is to tie it all into the +[Application](putting-it-together.md)! + +### Navigation + +Previous step: [Add a repository](repository.md) + +Final step: [Putting it all together](putting-it-together.md) diff --git a/packages/example-getting-started/docs/datasource.md b/packages/example-getting-started/docs/datasource.md new file mode 100644 index 000000000000..fe3c5e7d3902 --- /dev/null +++ b/packages/example-getting-started/docs/datasource.md @@ -0,0 +1,53 @@ +### 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 +access to data. + +Since our Todo API will need to persist instances of Todo items, we'll need to +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. + +#### config/datasources.json +```json +{ + "name": "ds", + "connector": "memory" +} +``` + +Create another folder called `datasources` in the `src` directory, and inside +that folder, create a new file called `db.datasource.ts`. This file will create +a strongly-typed export of our datasource using the `DataSourceConstructor`, +which we can consume in our application via injection. + +#### src/datasources/db.datasource.ts + +```ts +import * as path from 'path'; +import * as fs from 'fs'; +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. + +### Navigation + +Previous step: [Add your Todo model](model.md) + +Next step: [Add a repository](repository.md) diff --git a/packages/example-getting-started/docs/juggler.md b/packages/example-getting-started/docs/juggler.md new file mode 100644 index 000000000000..dada9c7a5b9c --- /dev/null +++ b/packages/example-getting-started/docs/juggler.md @@ -0,0 +1,67 @@ +### Adding the Legacy Juggler + +The Legacy Juggler is a "bridge" between the existing +[loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler) +and the new LoopBack 4 architecture. It provides the capabilities required to +access persistence layers/APIs, and perform +[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) operations +on those sources of data. + +It also provides many of the functions and interfaces we'll require for setting +up our new LoopBack application, which is why we're starting here. + +Now that you have your setup, it's time to modify it to add in +`@loopback/repository`. Install this dependency by running +`npm i --save @loopback/repository`. + +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} from '@loopback/rest'; + +/* tslint:disable:no-unused-variable */ +// Do not remove! +// Class and Repository imports required to infer types in consuming code! +// Binding and Booter imports are required to infer types for BootMixin! +import {BootMixin, Booter, Binding} from '@loopback/boot'; +import { + Class, + Repository, + RepositoryMixin, +} from '@loopback/repository'; +/* tslint:enable:no-unused-variable */ +export class TodoApplication extends BootMixin( + RepositoryMixin(RestApplication), +) { + constructor(options?: ApplicationConfig) { + super(options); + this.projectRoot = __dirname; + // Customize @loopback/boot Booter Conventions here + this.bootOptions = { + controllers: { + // Customize ControllerBooter Conventions here + dirs: ['controllers'], + extensions: ['.controller.js'], + nested: true, + }, + }; + } +} +``` + +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. + +### Navigation + +Previous step: [Scaffolding your application](scaffolding.md) + +Next step: [Add your Todo model](model.md) diff --git a/packages/example-getting-started/docs/4-todo-model.md b/packages/example-getting-started/docs/model.md similarity index 53% rename from packages/example-getting-started/docs/4-todo-model.md rename to packages/example-getting-started/docs/model.md index 168312ef218d..f2630f6ba711 100644 --- a/packages/example-getting-started/docs/4-todo-model.md +++ b/packages/example-getting-started/docs/model.md @@ -1,18 +1,43 @@ -### Building the Todo model +### Models -The Todo model will be the object we use both as a Data Transfer Object (DTO) on -the controller, and as a LoopBack model for the Legacy Juggler implementation. +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. -Create another folder in `src` called `repositories` and inside of that folder, +>**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. + +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 + +Create another folder in `src` called `models` and inside of that folder, create two files: - `index.ts` -- `todo.repository.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! +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 export * from './foo.model'; @@ -27,24 +52,14 @@ import {Foo, Bar, Baz} from './models'; import {Foo} from './models/foo.model'; import {Bar} from './models/bar.model'; import {Baz} from './models/baz.model'; +// Using an index.ts in your artifact folders really helps keep +// things tidy and succinct! ``` -In our Todo model, we'll create a basic representation of what would go in -a Todo list. Our model will include: -- 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. - 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. -Additionally, we'll define a `SchemaObject` that represents our Todo model -as an [OpenAPI Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schema-object). -This will give the OpenAPI spec builder the information it needs to describe the -Todo model on your app's OpenAPI endpoints. - #### src/models/todo.model.ts ```ts import {Entity, property, model} from '@loopback/repository'; @@ -78,7 +93,21 @@ export class Todo extends Entity { return this.id; } } +``` + + +Additionally, we'll define a `SchemaObject` that represents our Todo model +as an [OpenAPI Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schema-object). +This will give the OpenAPI spec builder the information it needs to describe the +Todo model on your app's OpenAPI endpoints. +#### src/models/todo.model.ts +```ts +// Note: This is in the same file as the Todo model class. +// This should come *after* the model class definition! export const TodoSchema: SchemaObject = { title: 'todoItem', properties: { @@ -102,8 +131,12 @@ export const TodoSchema: SchemaObject = { required: ['title'], }; ``` + +Now that we have our model and schema, it's time to add a +[datasource](datasource.md) so we can perform real CRUD operations! + ### Navigation -Previous step: [Adding the Legacy Juggler](3-add-legacy-juggler.md) +Previous step: [Adding the Legacy Juggler](juggler.md) -Next step: [Add a datasource](5-datasource.md) +Next step: [Add a datasource](datasource.md) diff --git a/packages/example-getting-started/docs/putting-it-together.md b/packages/example-getting-started/docs/putting-it-together.md new file mode 100644 index 000000000000..86ec8b03f18e --- /dev/null +++ b/packages/example-getting-started/docs/putting-it-together.md @@ -0,0 +1,103 @@ +### Putting it all together + +We've got all of our artifacts now, and all that's left is to bind them to our +[Application](http://loopback.io/doc/en/lb4/Application.html) so that LoopBack's +[Dependency injection](http://loopback.io/doc/en/lb4/Dependency-injection.html) +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` +> +>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} from '@loopback/rest'; +import {db} from './datasources/db.datasource'; + +/* tslint:disable:no-unused-variable */ +// Do not remove! +// Class and Repository imports required to infer types in consuming code! +// Binding and Booter imports are required to infer types for BootMixin! +import {BootMixin, Booter, Binding} from '@loopback/boot'; +import { + Class, + Repository, + RepositoryMixin, +} from '@loopback/repository'; +/* tslint:enable:no-unused-variable */ + +export class TodoApplication extends BootMixin( + RepositoryMixin(RestApplication), +) { + constructor(options?: ApplicationConfig) { + super(options); + this.projectRoot = __dirname; + // Customize @loopback/boot Booter Conventions here + this.bootOptions = { + controllers: { + // Customize ControllerBooter Conventions here + dirs: ['controllers'], + extensions: ['.controller.js'], + nested: true, + }, + }; + this.setupDatasources(); + } + + setupDatasources() { + // This will allow you to test your application without needing to + // use the "real" datasource! + const datasource = + this.options && this.options.datasource + ? new DataSourceConstructor(this.options.datasource) + : db; + this.bind('datasource').to(datasource); + } +}} +``` + +### Try it out + +Let's try out our application! +First, you'll want to start the app. +``` +$ npm start +Server is running on port 3000 +``` + +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. +- `PATCH /todo/{id}` with a body of `{ "desc": "need milk for cereal" }` + +That's it! You've just created your first LoopBack 4 application! + +### More examples and tutorials + +Eager to continue learning about LoopBack 4? Check out our +[examples and tutorials](https://loopback.io/doc/en/lb4/Examples-and-tutorials.html) +section to find examples for creating your own custom components, sequences and +more! + +### Navigation + +Previous step: [Add a controller](controller.md) diff --git a/packages/example-getting-started/docs/repository.md b/packages/example-getting-started/docs/repository.md new file mode 100644 index 000000000000..51a6eb1a735f --- /dev/null +++ b/packages/example-getting-started/docs/repository.md @@ -0,0 +1,50 @@ +### 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. + +### Create your repository + +Create another folder in `src` called `repositories` and inside of that folder, +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). + +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'; +import {inject} from '@loopback/core'; + +export class TodoRepository extends DefaultCrudRepository< + Todo, + typeof Todo.prototype.id +> { + constructor(@inject('datasource') protected datasource: DataSourceType) { + super(Todo, datasource); + } +} +``` + +Now we have everything we need to perform CRUD operations for our Todo list, +we'll need to build the [Controller](controller.md) to handle our incoming +requests. + +### Navigation + +Previous step: [Add a datasource](datasource.md) + +Next step: [Add a controller](controller.md) diff --git a/packages/example-getting-started/docs/scaffolding.md b/packages/example-getting-started/docs/scaffolding.md new file mode 100644 index 000000000000..69ff20cd58f1 --- /dev/null +++ b/packages/example-getting-started/docs/scaffolding.md @@ -0,0 +1,86 @@ +### 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. + +To generate your application using the toolkit, run the `lb4 app` command +and fill out the on-screen prompts: +``` +$ lb4 app +? Project name: (todo-list) +? Project description: A todo list API made with LoopBack 4. +? Project root directory: (todo-list) +? Application class name: (TodoListApplication) +? Select project build settings: (Press to select, to toggle all, to inverse selection) +❯◉ Enable tslint + ◉ Enable prettier + ◉ Enable mocha + ◉ Enable loopbackBuild + # npm will install dependencies now + Application todo-list is now created in todo-list. +``` + +For this tutorial, when prompted with the options for selecting things like +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: + +``` +src/ + controllers/ + README.md + ping.controller.ts + datasources/ + README.md + models/ + README.md + repositories/ + README.md + application.ts + index.ts + sequence.ts +test/ + README.md + mocha.opts + ping.controller.test.ts +node_modules/ + *** +LICENSE +README.md +index.js +index.ts +package.json +tsconfig.json +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`. | + +### Navigation + +Next step: [Adding the legacy juggler](juggler.md)