diff --git a/docs/site/sidebars/lb4_sidebar.yml b/docs/site/sidebars/lb4_sidebar.yml index 0a271c941dda..a1ec58a0a754 100644 --- a/docs/site/sidebars/lb4_sidebar.yml +++ b/docs/site/sidebars/lb4_sidebar.yml @@ -354,6 +354,11 @@ children: - title: 'Add Model Relations' url: todo-list-tutorial-relations.html output: 'web, pdf' + children: + + - title: 'Add a HasOne Relation' + url: todo-list-tutorial-has-one-relation.html + output: 'web, pdf' - title: 'Add TodoList Controller' url: todo-list-tutorial-controller.html diff --git a/docs/site/tutorials/todo-list/todo-list-tutorial-controller.md b/docs/site/tutorials/todo-list/todo-list-tutorial-controller.md index ef98e5b2570b..c28d5b8dfab1 100644 --- a/docs/site/tutorials/todo-list/todo-list-tutorial-controller.md +++ b/docs/site/tutorials/todo-list/todo-list-tutorial-controller.md @@ -12,8 +12,9 @@ summary: ### Controllers with related models Defining business logic to handle requests to related models isn't too different -from handling requests for standalone models. We'll create controllers to handle -requests for todo-lists and todo items under a todo-list. +from handling requests for standalone models. We'll create +[controllers](../../Controllers.md) to handle requests for todo-lists and todo +items under a todo-list. ### Create TodoList controller @@ -30,6 +31,7 @@ Controller TodoList will be created in src/controllers/todo-list.controller.ts ? What is the name of your CRUD repository? TodoListRepository ? What is the name of ID property? id ? What is the type of your ID? number +? Is the id omitted when creating a new instance? Yes ? What is the base HTTP path name of the CRUD operations? /todo-lists create src/controllers/todo-list.controller.ts update src/controllers/index.ts @@ -37,6 +39,9 @@ Controller TodoList will be created in src/controllers/todo-list.controller.ts Controller TodoList was created in src/controllers/ ``` +To view the completed file, see the +[`TodoList` example](https://github.com/strongloop/loopback-next/blob/master/examples/todo-list/src/controllers/todo-list.controller.ts). + And voilà! We now have a set of basic APIs for todo-lists, just like that! #### Inclusion of Related Models @@ -122,193 +127,52 @@ async findTodos(/*...*/) {/*...*/} async findTodoById(/*...*/) {/*...*/} ``` -### Create TodoList's Todo controller - -For the controller handling `Todos` of a `TodoList`, we'll start with an empty -controller: - -```sh -$ lb4 controller -? Controller class name: TodoListTodo -Controller TodoListTodo will be created in src/controllers/todo-list-todo.controller.ts - -? What kind of controller would you like to generate? Empty Controller - create src/controllers/todo-list-todo.controller.ts - update src/controllers/index.ts - -Controller TodoListTodo was created in src/controllers/ -``` - -Let's add in an injection for our `TodoListRepository`: - -{% include code-caption.html content="src/controllers/todo-list-todo.controller.ts" %} +#### Relation Controllers -```ts -import {repository} from '@loopback/repository'; -import {TodoListRepository} from '../repositories'; - -export class TodoListTodoController { - constructor( - @repository(TodoListRepository) protected todoListRepo: TodoListRepository, - ) {} -} -``` +Earlier when we used `lb4 relation` to create the two relations between `Todo` +and `TodoList`, you may have noticed +`src/controllers/todo-todo-list.controller.ts` and +`src/controllers/todo-list-todo.controller.ts` were created. These files contain +a set of API for the relations. -We're now ready to add in some routes for our todo requests. To call the CRUD -methods on a todo-list's todo items, we'll first need to create a constrained -`TodoRepository`. We can achieve this by using our repository instance's `todos` -factory function that we defined earlier in `TodoListRepository`. +Relation controllers act in a similar manner to normal controllers, except they +modify the relational property. For example, in the +`src/controllers/todo-list-todo.controller.ts` file, we can do requests to the +endpoint `/todo-lists/{id}/todos`, which we'll see in the +[Try it out](#try-it-out) section. -The `POST` request from `/todo-lists/{id}/todos` should look similar to the -following request: +As `src/controllers/todo-todo-list.controller.ts` only contains one method, we +can move it to the `Todo` controller and delete that file: -{% include code-caption.html content="src/controllers/todo-list-todo.controller.ts" %} +{% include code-caption.html content="src/models/todo.controller.ts" %} ```ts -import {repository} from '@loopback/repository'; -import {TodoListRepository} from '../repositories'; -import {post, param, requestBody} from '@loopback/rest'; -import {Todo} from '../models'; - -export class TodoListTodoController { - constructor( - @repository(TodoListRepository) protected todoListRepo: TodoListRepository, - ) {} - - @post('/todo-lists/{id}/todos') - async create( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Todo, {title: 'NewTodo', exclude: ['id']}), - }, - }, - }) - todo: Omit, - ) { - return this.todoListRepo.todos(id).create(todo); - } -} -``` +export class TodoController { + constructor() // ... + {} -Using our constraining factory as we did with the `POST` request, we'll define -the controller methods for the rest of the HTTP verbs for the route. The -completed controller should look as follows: + // other controller methods -{% include code-caption.html content="src/controllers/todo-list-todo.controller.ts" %} - -```ts -import { - Count, - CountSchema, - Filter, - repository, - Where, -} from '@loopback/repository'; -import { - del, - get, - getModelSchemaRef, - getWhereSchemaFor, - param, - patch, - post, - requestBody, -} from '@loopback/rest'; -import {Todo} from '../models'; -import {TodoListRepository} from '../repositories'; - -export class TodoListTodoController { - constructor( - @repository(TodoListRepository) protected todoListRepo: TodoListRepository, - ) {} - - @post('/todo-lists/{id}/todos', { + @get('/todos/{id}/todo-list', { responses: { '200': { - description: 'TodoList.Todo model instance', - content: {'application/json': {schema: getModelSchemaRef(Todo)}}, - }, - }, - }) - async create( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Todo, {title: 'NewTodo', exclude: ['id']}), - }, - }, - }) - todo: Omit, - ): Promise { - return this.todoListRepo.todos(id).create(todo); - } - - @get('/todo-lists/{id}/todos', { - responses: { - '200': { - description: "Array of Todo's belonging to TodoList", + description: 'TodoList belonging to Todo', content: { 'application/json': { - schema: {type: 'array', items: getModelSchemaRef(Todo)}, + schema: {type: 'array', items: getModelSchemaRef(TodoList)}, }, }, }, }, }) - async find( - @param.path.number('id') id: number, - @param.query.object('filter') filter?: Filter, - ): Promise { - return this.todoListRepo.todos(id).find(filter); - } - - @patch('/todo-lists/{id}/todos', { - responses: { - '200': { - description: 'TodoList.Todo PATCH success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async patch( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Todo, {partial: true}), - }, - }, - }) - todo: Partial - @param.query.object('where', getWhereSchemaFor(Todo)) where?: Where, - ): Promise { - return this.todoListRepo.todos(id).patch(todo, where); - } - - @del('/todo-lists/{id}/todos', { - responses: { - '200': { - description: 'TodoList.Todo DELETE success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async delete( - @param.path.number('id') id: number, - @param.query.object('where', getWhereSchemaFor(Todo)) where?: Where, - ): Promise { - return this.todoListRepo.todos(id).delete(where); + async getTodoList( + @param.path.number('id') id: typeof Todo.prototype.id, + ): Promise { + return this.todoRepository.todoList(id); } } ``` -Check out our `TodoList` example to see the full source code generated for the -`TodoListTodo` controller: -[src/controllers/todo-list-todo.controller.ts](https://github.com/strongloop/loopback-next/blob/master/examples/todo-list/src/controllers/todo-list-todo.controller.ts) - ### Try it out With the controllers complete, your application is ready to start up again! @@ -324,13 +188,22 @@ Here are some new requests you can try out: - `POST /todo-lists` with a body of `{ "title": "grocery list" }`. - `POST /todo-lists/{id}/todos` using the ID you got back from the previous - `POST` request and a body for a todo. Notice that response body you get back - contains property `todoListId` with the ID from before. -- `GET /todo-lists/{id}/todos` and see if you get the todo you created from - before. + `POST` request and this body: `{ "title": "get eggs", "isComplete": false}`. + Notice that response body you get back contains property `todoListId` with the + ID from before. +- `GET /todo-lists/{id}/todos` with the ID from before, and see if you get the + todo you created from before. +- `GET /todo-lists/{id}` with the ID from before, with the following filter + `{include: [{relation: 'todos'}]}`, and see if you get a `todos` property with + the todo created before. **Note**: this filter won't work through the API + explorer (See this + [GitHub issue](https://github.com/strongloop/loopback-next/issues/2208) for + details). Use the following url to test this endpoint (remember to replace + `{id}` with the ID from before): + http://localhost:3000/todo-lists/{id}?filter[include][][relation]=todos And there you have it! You now have the power to define APIs for related models! ### Navigation -Previous step: [Add TodoList repository](todo-list-tutorial-repository.md) +Previous step: [Add Model Relations](todo-list-tutorial-relations.md) diff --git a/docs/site/tutorials/todo-list/todo-list-tutorial-has-one-relation.md b/docs/site/tutorials/todo-list/todo-list-tutorial-has-one-relation.md new file mode 100644 index 000000000000..fc333af577ef --- /dev/null +++ b/docs/site/tutorials/todo-list/todo-list-tutorial-has-one-relation.md @@ -0,0 +1,284 @@ +--- +lang: en +title: 'Add TodoListImage Relation' +keywords: LoopBack 4.0, LoopBack 4 +sidebar: lb4_sidebar +permalink: /doc/en/lb4/todo-list-tutorial-has-one-relation.html +summary: LoopBack 4 TodoList Application Tutorial - Add TodoListImage Relation +--- + +We have that a `Todo` [`belongsTo`](../../BelongsTo-relation.md) a `TodoList` +and a `TodoList` [`hasMany`](../../HasMany-relation.md) `Todo`s. Another type of +relation we can add is [`hasOne`](../../hasOne-relation.md). To do so, let's add +`TodoListImage` such that each `TodoList` `hasOne` image. In parallel, a +`TodoListImage` will belong to a `TodoList`, similar to how a `Todo` belongs to +`TodoList`. + +### Create the Model + +Similar to how we created the model for +[`TodoList`](todo-list-tutorial-model.md), using `lb4 model`: + +```sh +lb4 model +? Model class name: TodoListImage +? Please select the model base class Entity (A persisted model with an ID) +? Allow additional (free-form) properties? No +Model TodoListImage will be created in src/models/todo-list-image.model.ts + +Let's add a property to TodoListImage +Enter an empty property name when done + +? Enter the property name: id +? Property type: number +? Is id the ID property? Yes +? Is id generated automatically? No +? Is it required?: No +? Default value [leave blank for none]: + +Let's add another property to TodoListImage +Enter an empty property name when done + +? Enter the property name: value +? Property type: string +? Is it required?: Yes +? Default value [leave blank for none]: + +Let's add another property to TodoListImage +Enter an empty property name when done + +? Enter the property name: + create src/models/todo-list-image.model.ts + update src/models/index.ts + +Model TodoListImage was created in src/models/ +``` + +### Create the Repository + +Using `lb4 repository`, let's create the repository: + +```sh +lb4 repository +? Please select the datasource DbDatasource +? Select the model(s) you want to generate a repository TodoListImage +? Please select the repository base class DefaultCrudRepository (Legacy juggler bridge) + create src/repositories/todo-list-image.repository.ts + update src/repositories/index.ts + +Repository TodoListImageRepository was created in src/repositories/ +``` + +### Add the Relation + +{% include note.html content=" +We are working on adding `hasOne` to the CLI command `lb4 relation`. See [issue #2980](https://github.com/strongloop/loopback-next/issues/2980). +" %} + +Adding a [`hasOne` relation](../../hasOne-relation.md) is simple. First, let's +add the relation to the model classes. + +In the `TodoListImage` model class, we'll start by adding a `todoListId` +property to reference the `TodoList` this image belongs to: + +{% include code-caption.html content="src/models/todo-list-image.model.ts" %} + +```ts +import {belongsTo} from '@loopback/repository'; +import {TodoList, TodoListWithRelations} from './todo-list.model'; + +@model() +export class TodoListImage extends Entity { + // ... other properties + + @belongsTo(() => TodoList) + todoListId: number; + + // ... +} + +export interface TodoListImageRelations { + todoList?: TodoListWithRelations; +} +``` + +{% include note.html content=" +A `hasOne` relation from model A to model B does not need a `belongsTo` relation to exist from model B to model A. +" %} + +In the `TodoList` model class, we'll add an `image` property to represent the +`TodoListImage` this `TodoList` has one of: + +{% include code-caption.html content="src/models/todo-list.model.ts" %} + +```ts +import {hasOne} from '@loopback/repository'; +import { + TodoListImage, + TodoListImageWithRelations, +} from './todo-list-image.model'; + +@model() +export class TodoList extends Entity { + // ... other properties + + @hasOne(() => TodoListImage) + image: TodoListImage; + + // ... +} + +export interface TodoListRelations { + todos?: TodoWithRelations[]; + + // Add the following line + image?: TodoListImageWithRelations; +} +``` + +{% include note.html content=" +See the [`@hasOne`](../../hasOne-relation.md#relation-metadata) and [`@belongsTo`](../../BelongsTo-relation.md#relation-metadata) documentation for more information on how to customize the decorators. +" %} + +Next, let's add the relation to the repository classes: + +{% include code-caption.html content="src/repositories/todo-list.repository.ts" %} + +```ts +// Add the following imports +import {HasOneRepositoryFactory} from '@loopback/repository'; +import {TodoListImage} from '../models'; +import {TodoListImageRepository} from './todo-list-image.repository'; + +export class TodoListRepository extends DefaultCrudRepository< + TodoList, + typeof TodoList.prototype.id, + TodoListRelations +> { + // other code + + // Add the following + public readonly image: HasOneRepositoryFactory< + TodoListImage, + typeof TodoList.prototype.id + >; + + constructor( + // other code + + // Add the following + @repository.getter('TodoListImageRepository') + protected todoListImageRepositoryGetter: Getter, + ) { + // other code + + // Add the following + this.image = this.createHasOneRepositoryFactoryFor( + 'image', + todoListImageRepositoryGetter, + ); + + this.registerInclusionResolver('image', this.image.inclusionResolver); + } +} +``` + +```ts +import {BelongsToAccessor} from '@loopback/repository'; +import {TodoList} from '../models'; +import {TodoListRepository} from './todo-list.repository'; + +export class TodoListImageRepository extends DefaultCrudRepository< + TodoListImage, + typeof TodoListImage.prototype.id, + TodoListImageRelations +> { + // Add the following + public readonly todoList: BelongsToAccessor< + TodoList, + typeof TodoListImage.prototype.id + >; + constructor( + // other code + + // Add the following line + protected todoListRepositoryGetter: Getter, + ) { + // other code + + // Add the following + this.todoList = this.createBelongsToAccessorFor( + 'todoList', + todoListRepositoryGetter, + ); + + this.registerInclusionResolver('todoList', this.todoList.inclusionResolver); + } +} +``` + +{% include note.html content=" +We use **default** foreign key and source property names in this case. +If you'd like to customize them, please check [`Relation Metadata`]( +../../hasOne-relation.md#relation-metadata). +" %} + +### Create the Controller + +Create a new file `src/controllers/todo-list-image.controller.ts`. We only want +to access a `TodoListImage` through a `TodoList`, so we'll create `GET` and +`POST` methods that allow for that as follows: + +{% include code-caption.html content="src/controllers/todo-list-image.controller.ts" %} + +```ts +import {repository} from '@loopback/repository'; +import {get, getModelSchemaRef, param, post, requestBody} from '@loopback/rest'; +import {TodoListImage} from '../models'; +import {TodoListRepository} from '../repositories'; + +export class TodoListImageController { + constructor( + @repository(TodoListRepository) protected todoListRepo: TodoListRepository, + ) {} + + @post('/todo-lists/{id}/image', { + responses: { + '200': { + description: 'create todoListImage model instance', + content: { + 'application/json': {schema: getModelSchemaRef(TodoListImage)}, + }, + }, + }, + }) + async create( + @param.path.number('id') id: number, + @requestBody() image: TodoListImage, + ): Promise { + return this.todoListRepo.image(id).create(image); + } + + @get('/todo-lists/{id}/image', { + responses: { + '200': { + description: 'The image belonging to the TodoList', + content: { + 'application/json': { + schema: getModelSchemaRef(TodoListImage, {includeRelations: true}), + }, + }, + }, + }, + }) + async find(@param.path.number('id') id: number): Promise { + return this.todoListRepo.image(id).get(); + } +} +``` + +### Navigation + +Previous step: [Add TodoList Relations](todo-list-tutorial-relations.md) + +Last step: [Add TodoList Controller](todo-list-tutorial-controller.md) diff --git a/docs/site/tutorials/todo-list/todo-list-tutorial-model.md b/docs/site/tutorials/todo-list/todo-list-tutorial-model.md index 939de4a1763f..e637c6f1bc4a 100644 --- a/docs/site/tutorials/todo-list/todo-list-tutorial-model.md +++ b/docs/site/tutorials/todo-list/todo-list-tutorial-model.md @@ -31,7 +31,7 @@ for us as follows: ```sh $ lb4 model ? Model class name: TodoList -? Please select the model base class Entity +? Please select the model base class Entity (A persisted model with an ID) ? Allow additional (free-form) properties? No Model TodoList will be created in src/models/todo-list.model.ts @@ -41,14 +41,16 @@ Enter an empty property name when done ? Enter the property name: id ? Property type: number ? Is id the ID property? Yes -? Is id generated automatically? Yes +? Is id generated automatically? No +? Is it required?: No +? Default value [leave blank for none]: Let's add another property to TodoList Enter an empty property name when done ? Enter the property name: title ? Property type: string -? Required?: Yes +? Is it required?: Yes ? Default value [leave blank for none]: Let's add another property to TodoList @@ -56,7 +58,7 @@ Enter an empty property name when done ? Enter the property name: color ? Property type: string -? Required?: No +? Is it required?: No ? Default value [leave blank for none]: Let's add another property to TodoList @@ -69,6 +71,9 @@ Enter an empty property name when done Model TodoList was created in src/models/ ``` +To view the completed file, see the +[`TodoList` example](https://github.com/strongloop/loopback-next/blob/master/examples/todo-list/src/models/todo-list.model.ts). + Once the models have been completely configured, it's time to move on to adding a [repository](todo-list-tutorial-repository.md) for `TodoList`. diff --git a/docs/site/tutorials/todo-list/todo-list-tutorial-relations.md b/docs/site/tutorials/todo-list/todo-list-tutorial-relations.md index 4525d5b4a464..3b5616d7c00f 100644 --- a/docs/site/tutorials/todo-list/todo-list-tutorial-relations.md +++ b/docs/site/tutorials/todo-list/todo-list-tutorial-relations.md @@ -11,7 +11,7 @@ summary: LoopBack 4 TodoList Application Tutorial - Add TodoList Repository We are going to add the model relation to indicate the relation that `TodoList` _hasMany_ `Todo` using the -[`lb4 relation` command](https://loopback.io/doc/en/lb4/Relation-generator.html). +[`lb4 relation` command](../../Relation-generator.md). ```sh $ lb4 relation @@ -19,7 +19,7 @@ $ lb4 relation ? Please select source model TodoList ? Please select target model Todo ? Foreign key name to define on the target model todoListId -? Source property name for the relation getter todos +? Source property name for the relation getter (will be the relation name) todos ? Allow TodoList queries to include data from related Todo instances? Yes create src/controllers/todo-list-todo.controller.ts @@ -34,7 +34,8 @@ $ lb4 relation ? Please select the relation type belongsTo ? Please select source model Todo ? Please select target model TodoList -? Source property name for the relation getter todoListId +? Foreign key name to define on the source model todoListId +? Relation name todoList ? Allow Todo queries to include data from related TodoList instances? Yes create src/controllers/todo-todo-list.controller.ts @@ -42,13 +43,65 @@ Relation BelongsTo was created in src/ ``` {% include note.html content=" -we use **default** foreign key and source property names in this case. -If you'd like to customize them, please check `Relation Metadata` -https://loopback.io/doc/en/lb4/HasMany-relation.html#relation-metadata and other +We use **default** foreign key and source property names in this case. +If you'd like to customize them, please check [Relation Metadata]( +../../HasMany-relation.md#relation-metadata) and other relations as well. " %} -### Behind the scene +### Update Sample Data + +Now that we have the relations between the `Todo` and `TodoList` models, we can +update the data we have in `data/db.json` to reflect this relation. + +First let's add two sample `TodoList`s: + +```json +{ + "ids": { + "Todo": 5, + "TodoList": 3 + }, + "models": { + "Todo": { + "1": "{\"title\":\"Take over the galaxy\",\"desc\":\"MWAHAHAHAHAHAHAHAHAHAHAHAHAMWAHAHAHAHAHAHAHAHAHAHAHAHA\",\"id\":1}", + "2": "{\"title\":\"destroy alderaan\",\"desc\":\"Make sure there are no survivors left!\",\"id\":2}", + "3": "{\"title\":\"play space invaders\",\"desc\":\"Become the very best!\",\"id\":3}", + "4": "{\"title\":\"crush rebel scum\",\"desc\":\"Every.Last.One.\",\"id\":4}" + }, + "TodoList": { + "1": "{\"title\":\"Sith lord's check list\",\"lastModified\":\"a long time ago\",\"id\":1}", + "2": "{\"title\":\"My daily chores\",\"lastModified\":\"2018-07-13\",\"id\":2}" + } + } +} +``` + +Next, let's add a `todoListId` property to the `Todo`s with the `id`s of the new +`TodoList`s we added: + +```json +{ + "ids": { + "Todo": 5, + "TodoList": 3 + }, + "models": { + "Todo": { + "1": "{\"title\":\"Take over the galaxy\",\"desc\":\"MWAHAHAHAHAHAHAHAHAHAHAHAHAMWAHAHAHAHAHAHAHAHAHAHAHAHA\",\"todoListId\":1,\"id\":1}", + "2": "{\"title\":\"destroy alderaan\",\"desc\":\"Make sure there are no survivors left!\",\"todoListId\":1,\"id\":2}", + "3": "{\"title\":\"play space invaders\",\"desc\":\"Become the very best!\",\"todoListId\":2,\"id\":3}", + "4": "{\"title\":\"crush rebel scum\",\"desc\":\"Every.Last.One.\",\"todoListId\":1,\"id\":4}" + }, + "TodoList": { + "1": "{\"title\":\"Sith lord's check list\",\"lastModified\":\"a long time ago\",\"id\":1}", + "2": "{\"title\":\"My daily chores\",\"lastModified\":\"2018-07-13\",\"id\":2}" + } + } +} +``` + +### Behind the scenes If you want to understand the code changes introduced from the relation generator command, read on the details in this section; otherwise, you are ready @@ -66,7 +119,7 @@ export class TodoList extends Entity { // ...properties defined by the CLI... @hasMany(() => Todo) - todos?: Todo[]; + todos: Todo[]; // ...constructor def... } @@ -87,11 +140,57 @@ export class Todo extends Entity { #### Inclusion of Related Models -When we ran the `lb4 relation` command, we accepted the default of `Yes` to the -prompt: +You'll notice there's a `TodoRelations` interface in the `Todo` model class file +as well as a `TodoWithRelations` type defined. + +{% include code-caption.html content="src/models/todo.model.ts" %} + +```ts +export interface TodoRelations { + // describe navigational properties here +} + +export type TodoWithRelations = Todo & TodoRelations; +``` + +In the `TodoRelations` interface, we want to describe a `todoList` as a +navigational property. We can do that as follows: + +{% include code-caption.html content="src/models/todo.model.ts" %} + +```ts +// add TodoListWithRelations to the following import +import {TodoList, TodoListWithRelations} from './todo-list.model'; +``` + +```ts +export interface TodoRelations { + // add the following line + todoList?: TodoListWithRelations; +} +``` + +Let's add a `todo` navigational property to the `TodoList` model as well: + +{% include code-caption.html content="src/models/todo-list.model.ts" %} + +```ts +// add TodoWithRelations to the following import +import {Todo, TodoWithRelations} from './todo.model'; +``` + +```ts +export interface TodoRelations { + // add the following line + todos?: TodoWithRelations[]; +} +``` + +Further, when we ran the `lb4 relation` command, we accepted the default of +`Yes` to the prompt: ```sh -? Allow Order queries to include data from related Customer instances? (Y/n) +? Allow Todo queries to include data from related TodoList instances? (Y/n) ``` This registers the `inclusionResolver` for the relation(s) you were working with @@ -148,6 +247,12 @@ this.todoList = this.createBelongsToAccessorFor( this.registerInclusionResolver('todoList', this.todoList.inclusionResolver); ``` +There's an additional relation type LoopBack 4 supports, which is +[`hasOne`](../../hasOne-relation.md). If you're interested in trying it out, see +[Add TodoListImage Relation](todo-list-tutorial-has-one-relation.md). This is +not required for the application, so if you'd like to skip it, see the +[navigation](#navigation) for the last step. + ### Navigation Previous step: [Add TodoList Repository](todo-list-tutorial-repository.md) diff --git a/docs/site/tutorials/todo-list/todo-list-tutorial-repository.md b/docs/site/tutorials/todo-list/todo-list-tutorial-repository.md index 42ea9f477fb5..45a114ef7202 100644 --- a/docs/site/tutorials/todo-list/todo-list-tutorial-repository.md +++ b/docs/site/tutorials/todo-list/todo-list-tutorial-repository.md @@ -29,14 +29,38 @@ datasources. lb4 repository ? Please select the datasource DbDatasource ? Select the model(s) you want to generate a repository TodoList - create src/repositories/todo-list.repository.ts - update src/repositories/index.ts ? Please select the repository base class DefaultCrudRepository (Legacy juggler bridge) + create src/repositories/todo-list.repository.ts + update src/repositories/index.ts Repository TodoListRepository was created in src/repositories/ ``` +#### Custom Methods + +A custom method can be added to the repository class. For example, if we want to +find a `TodoList` with a specific `title` from the repository level, the +following method can be added: + +```ts +export class TodoListRepository extends DefaultCrudRepository< + TodoList, + typeof TodoList.prototype.id, + TodoListRelations +> { + // other code + + // Add the following function + public findByTitle(title: string) { + return this.findOne({where: {title}}); + } +} +``` + +To view the completed file, see the +[`TodoList` example](https://github.com/strongloop/loopback-next/blob/master/examples/todo-list/src/repositories/todo-list.repository.ts). + ### Navigation Previous step: [Add TodoList model](todo-list-tutorial-model.md) diff --git a/docs/site/tutorials/todo/todo-tutorial-controller.md b/docs/site/tutorials/todo/todo-tutorial-controller.md index f3c17b6585f9..67706524430b 100644 --- a/docs/site/tutorials/todo/todo-tutorial-controller.md +++ b/docs/site/tutorials/todo/todo-tutorial-controller.md @@ -9,10 +9,10 @@ summary: LoopBack 4 Todo Application Tutorial - Add a Controller ### 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 `/todos`), to perform business logic, -and to return a response. +In LoopBack 4, [controllers](../../Controllers.md) 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 `/todos`), to +perform business logic, and to return a response. `Controller` is a class that implements operations defined by application's API. It implements an application's business logic and acts as a bridge between the @@ -76,6 +76,9 @@ Some additional things to note about this example: specifying the type of certain value primitives, such as `@param.path.number('id')`. +To view the completed file, see the +[`Todo` example](https://github.com/strongloop/loopback-next/blob/master/examples/todo/src/controllers/todo.controller.ts). + Now that we've wired up the controller, our last step is to tie it all into the [Application](todo-tutorial-putting-it-together.md)! diff --git a/docs/site/tutorials/todo/todo-tutorial-datasource.md b/docs/site/tutorials/todo/todo-tutorial-datasource.md index 7cb57247cc8c..7f0c63681750 100644 --- a/docs/site/tutorials/todo/todo-tutorial-datasource.md +++ b/docs/site/tutorials/todo/todo-tutorial-datasource.md @@ -21,7 +21,7 @@ the application. Typically, in LoopBack 4, datasources are used in conjunction with [Repositories](../../Repositories.md) to provide access to data. For more information about datasources in LoopBack, see -[DataSources](https://loopback.io/doc/en/lb4/DataSources.html). +[DataSources](../../DataSources.md). Since our Todo API will need to persist instances of Todo items, we'll need to create a datasource definition to make this possible. @@ -46,6 +46,9 @@ lb4 datasource Datasource Db was created in src/datasources/ ``` +To view the completed files, see the +[`Todo` example](https://github.com/strongloop/loopback-next/tree/master/examples/todo/src/datasources). + Create a `data` folder in the applications root and add a new file called `db.json` containing an example database. @@ -69,7 +72,7 @@ Create a `data` folder in the applications root and add a new file called {% include note.html content="If you are using a relational database as the datasource, don't forget to create the corresponding table or follow the -[Database migration instruction](https://loopback.io/doc/en/lb4/Database-migrations.html) to get it created programmatically. +[Database migration instructions](https://loopback.io/doc/en/lb4/Database-migrations.html) to get it created programmatically. " %} Once you're ready, we'll move onto adding a diff --git a/docs/site/tutorials/todo/todo-tutorial-geocoding-service.md b/docs/site/tutorials/todo/todo-tutorial-geocoding-service.md index f2f48d71012f..c7c738a8bb95 100644 --- a/docs/site/tutorials/todo/todo-tutorial-geocoding-service.md +++ b/docs/site/tutorials/todo/todo-tutorial-geocoding-service.md @@ -11,9 +11,10 @@ summary: ### Services To call other APIs and web services from LoopBack applications, we recommend to -use Service Proxies as a design pattern for encapsulating low-level -implementation details of communication with 3rd-party services and providing -JavaScript/TypeScript API that's easy to consume e.g. from Controllers. See +use [Service Proxies](../../Services.md) as a design pattern for encapsulating +low-level implementation details of communication with 3rd-party services and +providing JavaScript/TypeScript API that's easy to consume e.g. from +Controllers. See [Calling other APIs and web services](../../Calling-other-APIs-and-Web-Services.md) for more details. @@ -63,6 +64,7 @@ docs here: [REST connector](/doc/en/lb3/REST-connector.html). ```json { + "name": "geocoder", "connector": "rest", "options": { "headers": { @@ -92,20 +94,31 @@ docs here: [REST connector](/doc/en/lb3/REST-connector.html). ### Implement a service provider -Create a new directory `src/services` and add the following two new files: +Use the `lb4 service` command and the following inputs to create a geocoder +service: -- `src/services/geocoder.service.ts` defining TypeScript interfaces for Geocoder - service and implementing a service proxy provider. -- `src/services/index.ts` providing a conventient access to all services via a - single `import` statement. +```sh +lb4 service +? Service type: Remote service proxy backed by a data source +? Please select the datasource GeocoderDatasource +? Service name: geocoder + create src/services/geocoder.service.ts + update src/services/index.ts + +Service Geocoder was created in src/services/ +``` + +In the `src/services/geocoder.service.ts`, we'll add a `GeoPoint` interface and +a `geocode` function to the `Geocoder` interface as follows: {% include code-caption.html content="src/services/geocoder.service.ts" %} ```ts -import {getService, juggler} from '@loopback/service-proxy'; import {inject, Provider} from '@loopback/core'; -import {GeocoderDataSource} from '../datasources/geocoder.datasource'; +import {getService} from '@loopback/service-proxy'; +import {GeocoderDataSource} from '../datasources'; +// Add the following interface export interface GeoPoint { /** * latitude @@ -118,28 +131,24 @@ export interface GeoPoint { x: number; } -export interface GeocoderService { +export interface Geocoder { + // Add the following property geocode(address: string): Promise; } -export class GeocoderServiceProvider implements Provider { +export class GeocoderProvider implements Provider { constructor( + // geocoder must match the name property in the datasource json file @inject('datasources.geocoder') - protected dataSource: juggler.DataSource = new GeocoderDataSource(), + protected dataSource: GeocoderDataSource = new GeocoderDataSource(), ) {} - value(): Promise { + value(): Promise { return getService(this.dataSource); } } ``` -{% include code-caption.html content="src/services/index.ts" %} - -```ts -export * from './geocoder.service'; -``` - ### Enhance Todo model with location data Add two new properties to our Todo model: `remindAtAddress` and `remindAtGeo`. @@ -168,26 +177,27 @@ export class Todo extends Entity { Finally, modify `TodoController` to look up the address and convert it to GPS coordinates when a new Todo item is created. -Import `GeocodeService` interface into the `TodoController` and then modify the -Controller constructor to receive `GeocodeService` as a new dependency. +Import `Geocoder` interface into the `TodoController` and then modify the +Controller constructor to receive `Geocoder` as a new dependency. {% include code-caption.html content="src/controllers/todo.controller.ts" %} ```ts import {inject} from '@loopback/core'; -import {GeocoderService} from '../services'; +import {Geocoder} from '../services'; export class TodoController { constructor( - @repository(TodoRepository) protected todoRepo: TodoRepository, - @inject('services.GeocoderService') protected geoService: GeocoderService, + @repository(TodoRepository) + public todoRepository: TodoRepository, + @inject('services.Geocoder') protected geoService: Geocoder, ) {} // etc. } ``` -Modify `create` method to look up the address provided in `remindAtAddress` +Modify the `create` method to look up the address provided in `remindAtAddress` property and convert it to GPS coordinates stored in `remindAtGeo`. {% include code-caption.html content="src/controllers/todo.controller.ts" %} @@ -222,13 +232,17 @@ export class TodoController { // https://gis.stackexchange.com/q/7379 todo.remindAtGeo = `${geo[0].y},${geo[0].x}`; } - return this.todoRepo.create(todo); + return this.todoRepository.create(todo); } // other endpoints remain unchanged } ``` +{% include warning.html content=" +Some addresses may not be found and the request will be rejected. +" %} + Congratulations! Now your Todo API makes it easy to enter an address for a reminder and have the client application show the reminder when the device reaches close proximity of that address based on GPS location. diff --git a/docs/site/tutorials/todo/todo-tutorial-model.md b/docs/site/tutorials/todo/todo-tutorial-model.md index 083171295ea1..76a4c4db4435 100644 --- a/docs/site/tutorials/todo/todo-tutorial-model.md +++ b/docs/site/tutorials/todo/todo-tutorial-model.md @@ -22,7 +22,7 @@ name, type, and other constraints. Models are used for data exchange on the wire or between different systems. For more information about Models and how they are used in LoopBack, see -[Models](https://loopback.io/doc/en/lb4/Model.html). +[Models](../../Model.md). {% include note.html content="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! " %} @@ -48,7 +48,7 @@ these steps: ```sh lb4 model ? Model class name: todo -? Please select the model base class: Entity +? Please select the model base class Entity (A persisted model with an ID) ? Allow additional (free-form) properties? No Model Todo will be created in src/models/todo.model.ts @@ -58,8 +58,8 @@ Enter an empty property name when done ? Enter the property name: id ? Property type: number ? Is id the ID property? Yes -? Is it required?: No ? Is id generated automatically? No +? Is it required?: No ? Default value [leave blank for none]: Let's add another property to Todo @@ -97,6 +97,9 @@ Enter an empty property name when done Model Todo was created in src/models/ ``` +To view the completed file, see the +[`Todo` example](https://github.com/strongloop/loopback-next/blob/master/examples/todo/src/models/todo.model.ts). + Now that we have our model, it's time to add a [datasource](todo-tutorial-datasource.md) so we can perform real CRUD operations! diff --git a/docs/site/tutorials/todo/todo-tutorial-putting-it-together.md b/docs/site/tutorials/todo/todo-tutorial-putting-it-together.md index 73470e1f56c0..6b10d80de840 100644 --- a/docs/site/tutorials/todo/todo-tutorial-putting-it-together.md +++ b/docs/site/tutorials/todo/todo-tutorial-putting-it-together.md @@ -52,6 +52,8 @@ Here are some requests you can try: That's it! You've just created your first LoopBack 4 application! +_Note: Use **CTRL+C** to stop the application_ + ### Where to go from here There are still a ton of features you can use to build on top of the diff --git a/docs/site/tutorials/todo/todo-tutorial-repository.md b/docs/site/tutorials/todo/todo-tutorial-repository.md index 214eb3d89989..9e54fe02426d 100644 --- a/docs/site/tutorials/todo/todo-tutorial-repository.md +++ b/docs/site/tutorials/todo/todo-tutorial-repository.md @@ -20,7 +20,7 @@ strong-typed data access (for example, CRUD) operations of a domain model against the underlying database or service. For more information about Repositories, see -[Repositories](https://loopback.io/doc/en/lb4/Repositories.html). +[Repositories](../../Repositories.md). ### Create your repository @@ -33,11 +33,12 @@ list of available datasources. lb4 repository ? Please select the datasource DbDatasource ? Select the model(s) you want to generate a repository Todo - create src/repositories/todo.repository.ts - update src/repositories/index.ts ? Please select the repository base class DefaultCrudRepository (Legacy juggler bridge) + create src/repositories/todo.repository.ts + update src/repositories/index.ts + Repository TodoRepository was created in src/repositories/ ``` @@ -50,6 +51,9 @@ model definition and 'db' datasource configuration and retrieves the datasource using [Dependency Injection](https://loopback.io/doc/en/lb4/Dependency-injection.html). +To view the completed file, see the +[`Todo` example](https://github.com/strongloop/loopback-next/blob/master/examples/todo/src/repositories/todo.repository.ts). + Now we can expose the `Todo` API through the [controller](todo-tutorial-controller.md). diff --git a/docs/site/tutorials/todo/todo-tutorial-scaffolding.md b/docs/site/tutorials/todo/todo-tutorial-scaffolding.md index 43b6c08af9e7..ff52698a2910 100644 --- a/docs/site/tutorials/todo/todo-tutorial-scaffolding.md +++ b/docs/site/tutorials/todo/todo-tutorial-scaffolding.md @@ -28,7 +28,7 @@ $ lb4 app ◉ Enable mocha: install mocha to run tests ◉ Enable loopbackBuild: use @loopback/build helpers (e.g. lb-eslint) ◉ Enable vscode: add VSCode config files -❯◯ Enable docker: include Dockerfile and .dockerignore + ◉ Enable docker: include Dockerfile and .dockerignore ◉ Enable repositories: include repository imports and RepositoryMixin ◉ Enable services: include service-proxy imports and ServiceMixin # npm will install dependencies now @@ -36,8 +36,7 @@ $ lb4 app ``` For this tutorial, when prompted with the options for enabling certain project -features (LoopBack's build, eslint, mocha, etc.), leave them all enabled except -for `docker`. +features (LoopBack's build, eslint, mocha, etc.), leave them all enabled. ### Structure @@ -81,6 +80,8 @@ tsconfig.json .mocharc.json ``` +Note that there might be extra files not listed here. + | File | Purpose | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `index.ts` | Allows importing contents of the `src` folder (for use elsewhere) | diff --git a/examples/todo-list/.dockerignore b/examples/todo-list/.dockerignore new file mode 100644 index 000000000000..7aecc7e3dda5 --- /dev/null +++ b/examples/todo-list/.dockerignore @@ -0,0 +1,5 @@ +node_modules +npm-debug.log +/dist +# Cache used by TypeScript's incremental build +*.tsbuildinfo diff --git a/examples/todo-list/Dockerfile b/examples/todo-list/Dockerfile new file mode 100644 index 000000000000..bca90e201a53 --- /dev/null +++ b/examples/todo-list/Dockerfile @@ -0,0 +1,28 @@ +# Check out https://hub.docker.com/_/node to select a new base image +FROM node:10-slim + +# Set to a non-root built-in user `node` +USER node + +# Create app directory (with user `node`) +RUN mkdir -p /home/node/app + +WORKDIR /home/node/app + +# Install app dependencies +# A wildcard is used to ensure both package.json AND package-lock.json are copied +# where available (npm@5+) +COPY --chown=node package*.json ./ + +RUN npm install + +# Bundle app source code +COPY --chown=node . . + +RUN npm run build + +# Bind to all network interfaces so that it can be mapped to the host OS +ENV HOST=0.0.0.0 PORT=3000 + +EXPOSE ${PORT} +CMD [ "node", "." ] diff --git a/examples/todo-list/src/controllers/todo.controller.ts b/examples/todo-list/src/controllers/todo.controller.ts index 8a81c7600498..517307993987 100644 --- a/examples/todo-list/src/controllers/todo.controller.ts +++ b/examples/todo-list/src/controllers/todo.controller.ts @@ -19,7 +19,9 @@ import {Todo, TodoList} from '../models'; import {TodoRepository} from '../repositories'; export class TodoController { - constructor(@repository(TodoRepository) protected todoRepo: TodoRepository) {} + constructor( + @repository(TodoRepository) protected todoRepository: TodoRepository, + ) {} @post('/todos', { responses: { @@ -39,7 +41,7 @@ export class TodoController { }) todo: Omit, ): Promise { - return this.todoRepo.create(todo); + return this.todoRepository.create(todo); } @get('/todos/{id}', { @@ -59,7 +61,7 @@ export class TodoController { @param.query.object('filter', getFilterSchemaFor(Todo)) filter?: Filter, ): Promise { - return this.todoRepo.findById(id, filter); + return this.todoRepository.findById(id, filter); } @get('/todos', { @@ -81,7 +83,7 @@ export class TodoController { @param.query.object('filter', getFilterSchemaFor(Todo)) filter?: Filter, ): Promise { - return this.todoRepo.find(filter); + return this.todoRepository.find(filter); } @put('/todos/{id}', { @@ -95,7 +97,7 @@ export class TodoController { @param.path.number('id') id: number, @requestBody() todo: Todo, ): Promise { - await this.todoRepo.replaceById(id, todo); + await this.todoRepository.replaceById(id, todo); } @patch('/todos/{id}', { @@ -116,7 +118,7 @@ export class TodoController { }) todo: Partial, ): Promise { - await this.todoRepo.updateById(id, todo); + await this.todoRepository.updateById(id, todo); } @del('/todos/{id}', { @@ -127,7 +129,7 @@ export class TodoController { }, }) async deleteTodo(@param.path.number('id') id: number): Promise { - await this.todoRepo.deleteById(id); + await this.todoRepository.deleteById(id); } @get('/todos/{id}/todo-list', { @@ -139,6 +141,6 @@ export class TodoController { }, }) async findOwningList(@param.path.number('id') id: number): Promise { - return this.todoRepo.todoList(id); + return this.todoRepository.todoList(id); } } diff --git a/examples/todo-list/src/models/todo-list-image.model.ts b/examples/todo-list/src/models/todo-list-image.model.ts index 37e9cc6e12a8..fd4d7c477b5c 100644 --- a/examples/todo-list/src/models/todo-list-image.model.ts +++ b/examples/todo-list/src/models/todo-list-image.model.ts @@ -11,6 +11,7 @@ export class TodoListImage extends Entity { @property({ type: 'number', id: true, + generated: false, }) id: number; diff --git a/examples/todo-list/src/models/todo-list.model.ts b/examples/todo-list/src/models/todo-list.model.ts index 8b23ffc2d868..8e4bebd63102 100644 --- a/examples/todo-list/src/models/todo-list.model.ts +++ b/examples/todo-list/src/models/todo-list.model.ts @@ -15,6 +15,7 @@ export class TodoList extends Entity { @property({ type: 'number', id: true, + generated: false, }) id: number; @@ -30,10 +31,10 @@ export class TodoList extends Entity { color?: string; @hasMany(() => Todo) - todos?: Todo[]; + todos: Todo[]; @hasOne(() => TodoListImage) - image?: TodoListImage; + image: TodoListImage; constructor(data?: Partial) { super(data); diff --git a/examples/todo-list/src/models/todo.model.ts b/examples/todo-list/src/models/todo.model.ts index 64ceddafa179..b06295dc3ab7 100644 --- a/examples/todo-list/src/models/todo.model.ts +++ b/examples/todo-list/src/models/todo.model.ts @@ -11,6 +11,7 @@ export class Todo extends Entity { @property({ type: 'number', id: true, + generated: false, }) id: number; diff --git a/examples/todo/.dockerignore b/examples/todo/.dockerignore new file mode 100644 index 000000000000..7aecc7e3dda5 --- /dev/null +++ b/examples/todo/.dockerignore @@ -0,0 +1,5 @@ +node_modules +npm-debug.log +/dist +# Cache used by TypeScript's incremental build +*.tsbuildinfo diff --git a/examples/todo/Dockerfile b/examples/todo/Dockerfile new file mode 100644 index 000000000000..bca90e201a53 --- /dev/null +++ b/examples/todo/Dockerfile @@ -0,0 +1,28 @@ +# Check out https://hub.docker.com/_/node to select a new base image +FROM node:10-slim + +# Set to a non-root built-in user `node` +USER node + +# Create app directory (with user `node`) +RUN mkdir -p /home/node/app + +WORKDIR /home/node/app + +# Install app dependencies +# A wildcard is used to ensure both package.json AND package-lock.json are copied +# where available (npm@5+) +COPY --chown=node package*.json ./ + +RUN npm install + +# Bundle app source code +COPY --chown=node . . + +RUN npm run build + +# Bind to all network interfaces so that it can be mapped to the host OS +ENV HOST=0.0.0.0 PORT=3000 + +EXPOSE ${PORT} +CMD [ "node", "." ] diff --git a/examples/todo/src/__tests__/acceptance/todo.acceptance.ts b/examples/todo/src/__tests__/acceptance/todo.acceptance.ts index 0e6d616d3d8f..9f2a11daf616 100644 --- a/examples/todo/src/__tests__/acceptance/todo.acceptance.ts +++ b/examples/todo/src/__tests__/acceptance/todo.acceptance.ts @@ -14,7 +14,7 @@ import { import {TodoListApplication} from '../../application'; import {Todo} from '../../models/'; import {TodoRepository} from '../../repositories/'; -import {GeocoderService} from '../../services'; +import {Geocoder} from '../../services'; import { aLocation, getProxiedGeoCoderConfig, @@ -40,7 +40,7 @@ describe('TodoApplication', () => { before(async function() { // eslint-disable-next-line no-invalid-this this.timeout(30 * 1000); - const service = await app.get('services.GeocoderService'); + const service = await app.get('services.Geocoder'); available = await isGeoCoderServiceAvailable(service); }); diff --git a/examples/todo/src/__tests__/helpers.ts b/examples/todo/src/__tests__/helpers.ts index 72aa7026de3e..2608aabf7957 100644 --- a/examples/todo/src/__tests__/helpers.ts +++ b/examples/todo/src/__tests__/helpers.ts @@ -8,7 +8,7 @@ import {merge} from 'lodash'; import path from 'path'; import GEO_CODER_CONFIG from '../datasources/geocoder.datasource.config.json'; import {Todo} from '../models/index'; -import {GeocoderService, GeoPoint} from '../services/geocoder.service'; +import {Geocoder, GeoPoint} from '../services/geocoder.service'; /* ============================================================================== @@ -74,7 +74,7 @@ export async function givenCachingProxy() { return proxy; } -export async function isGeoCoderServiceAvailable(service: GeocoderService) { +export async function isGeoCoderServiceAvailable(service: Geocoder) { try { await service.geocode(aLocation.address); return true; diff --git a/examples/todo/src/__tests__/integration/services/geocoder.service.integration.ts b/examples/todo/src/__tests__/integration/services/geocoder.service.integration.ts index 745f7e67ad9b..176dd8a7cb99 100644 --- a/examples/todo/src/__tests__/integration/services/geocoder.service.integration.ts +++ b/examples/todo/src/__tests__/integration/services/geocoder.service.integration.ts @@ -5,7 +5,7 @@ import {expect} from '@loopback/testlab'; import {GeocoderDataSource} from '../../../datasources/geocoder.datasource'; -import {GeocoderService, GeocoderServiceProvider} from '../../../services'; +import {Geocoder, GeocoderProvider} from '../../../services'; import { aLocation, getProxiedGeoCoderConfig, @@ -22,7 +22,7 @@ describe('GeoLookupService', function() { before(async () => (cachingProxy = await givenCachingProxy())); after(() => cachingProxy.stop()); - let service: GeocoderService; + let service: Geocoder; before(givenGeoService); let available = true; @@ -42,6 +42,6 @@ describe('GeoLookupService', function() { async function givenGeoService() { const config = getProxiedGeoCoderConfig(cachingProxy); const dataSource = new GeocoderDataSource(config); - service = await new GeocoderServiceProvider(dataSource).value(); + service = await new GeocoderProvider(dataSource).value(); } }); diff --git a/examples/todo/src/__tests__/unit/controllers/todo.controller.unit.ts b/examples/todo/src/__tests__/unit/controllers/todo.controller.unit.ts index 50f575291b06..fba6760124ee 100644 --- a/examples/todo/src/__tests__/unit/controllers/todo.controller.unit.ts +++ b/examples/todo/src/__tests__/unit/controllers/todo.controller.unit.ts @@ -13,12 +13,12 @@ import { import {TodoController} from '../../../controllers'; import {Todo} from '../../../models/index'; import {TodoRepository} from '../../../repositories'; -import {GeocoderService} from '../../../services'; +import {Geocoder} from '../../../services'; import {aLocation, givenTodo} from '../../helpers'; describe('TodoController', () => { let todoRepo: StubbedInstanceWithSinonAccessor; - let geoService: GeocoderService; + let geoService: Geocoder; let geocode: sinon.SinonStub; diff --git a/examples/todo/src/controllers/todo.controller.ts b/examples/todo/src/controllers/todo.controller.ts index 32ac22f34fbb..1acbcd2e21ac 100644 --- a/examples/todo/src/controllers/todo.controller.ts +++ b/examples/todo/src/controllers/todo.controller.ts @@ -18,12 +18,12 @@ import { } from '@loopback/rest'; import {Todo} from '../models'; import {TodoRepository} from '../repositories'; -import {GeocoderService} from '../services'; +import {Geocoder} from '../services'; export class TodoController { constructor( - @repository(TodoRepository) protected todoRepo: TodoRepository, - @inject('services.GeocoderService') protected geoService: GeocoderService, + @repository(TodoRepository) protected todoRepository: TodoRepository, + @inject('services.Geocoder') protected geoService: Geocoder, ) {} @post('/todos', { @@ -52,7 +52,7 @@ export class TodoController { // https://gis.stackexchange.com/q/7379 todo.remindAtGeo = `${geo[0].y},${geo[0].x}`; } - return this.todoRepo.create(todo); + return this.todoRepository.create(todo); } @get('/todos/{id}', { @@ -67,7 +67,7 @@ export class TodoController { @param.path.number('id') id: number, @param.query.boolean('items') items?: boolean, ): Promise { - return this.todoRepo.findById(id); + return this.todoRepository.findById(id); } @get('/todos', { @@ -86,7 +86,7 @@ export class TodoController { @param.query.object('filter', getFilterSchemaFor(Todo)) filter?: Filter, ): Promise { - return this.todoRepo.find(filter); + return this.todoRepository.find(filter); } @put('/todos/{id}', { @@ -100,7 +100,7 @@ export class TodoController { @param.path.number('id') id: number, @requestBody() todo: Todo, ): Promise { - await this.todoRepo.replaceById(id, todo); + await this.todoRepository.replaceById(id, todo); } @patch('/todos/{id}', { @@ -121,7 +121,7 @@ export class TodoController { }) todo: Partial, ): Promise { - await this.todoRepo.updateById(id, todo); + await this.todoRepository.updateById(id, todo); } @del('/todos/{id}', { @@ -132,6 +132,6 @@ export class TodoController { }, }) async deleteTodo(@param.path.number('id') id: number): Promise { - await this.todoRepo.deleteById(id); + await this.todoRepository.deleteById(id); } } diff --git a/examples/todo/src/models/todo.model.ts b/examples/todo/src/models/todo.model.ts index 93ed89c900a6..fc0461434390 100644 --- a/examples/todo/src/models/todo.model.ts +++ b/examples/todo/src/models/todo.model.ts @@ -10,6 +10,7 @@ export class Todo extends Entity { @property({ type: 'number', id: true, + generated: false, }) id?: number; diff --git a/examples/todo/src/services/geocoder.service.ts b/examples/todo/src/services/geocoder.service.ts index fe1c6113ba9f..b9da8d7f97ea 100644 --- a/examples/todo/src/services/geocoder.service.ts +++ b/examples/todo/src/services/geocoder.service.ts @@ -1,11 +1,11 @@ -// Copyright IBM Corp. 2018. All Rights Reserved. +// Copyright IBM Corp. 2018,2020. All Rights Reserved. // Node module: @loopback/example-todo // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {getService, juggler} from '@loopback/service-proxy'; import {inject, Provider} from '@loopback/core'; -import {GeocoderDataSource} from '../datasources/geocoder.datasource'; +import {getService} from '@loopback/service-proxy'; +import {GeocoderDataSource} from '../datasources'; export interface GeoPoint { /** @@ -19,17 +19,18 @@ export interface GeoPoint { x: number; } -export interface GeocoderService { +export interface Geocoder { geocode(address: string): Promise; } -export class GeocoderServiceProvider implements Provider { +export class GeocoderProvider implements Provider { constructor( + // geocoder must match the name property in the datasource json file @inject('datasources.geocoder') - protected dataSource: juggler.DataSource = new GeocoderDataSource(), + protected dataSource: GeocoderDataSource = new GeocoderDataSource(), ) {} - value(): Promise { + value(): Promise { return getService(this.dataSource); } }