From a446f436925f2d7a5c04d985991f8bfbe02f2f23 Mon Sep 17 00:00:00 2001 From: Yaapa Hage Date: Mon, 1 Jun 2020 20:25:49 +0530 Subject: [PATCH] docs: dynamic datasource, model, repository, and controller Describe how to create datasources, models, repositories, and controllers at runtime. --- docs/site/Controllers.md | 50 +++++++++++++++++++++++++++ docs/site/DataSources.md | 29 ++++++++++++++++ docs/site/Model.md | 46 ++++++++++++++++++++++++ docs/site/Repositories.md | 73 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+) diff --git a/docs/site/Controllers.md b/docs/site/Controllers.md index 693e83061c9a..2d95552fd826 100644 --- a/docs/site/Controllers.md +++ b/docs/site/Controllers.md @@ -196,6 +196,25 @@ export class HelloController { - `@param.query.number` specifies in the spec being generated that the route takes a parameter via query which will be a number. +## Class factory to allow parameterized decorations + +Since decorations applied on a top-level class cannot have references to +variables, you can create a class factory that allows parameterized decorations +as shown in the example below. + +```ts +function createControllerClass(version: string, basePath: string) { + @api({basePath: `${basePath}`}) + class Controller { + @get(`/${version}`) find() {} + } +} +``` + +For a complete example, see +[parameterized-decoration.ts](https://github.com/strongloop/loopback-next/blob/master/examples/context/src/parameterized-decoration.ts) +. + ## Handling Errors in Controllers In order to specify errors for controller methods to throw, the class @@ -269,3 +288,34 @@ export class HelloController { } } ``` + +## Creating Controllers at Runtime + +A controller can be created for a model at runtime using the +`defineCrudRestController` helper function from the `@loopback/rest-crud` +package. It accepts a Model class and a `CrudRestControllerOptions` object. +Dependency injection for the controller has to be configured by applying the +`inject` decorator manually as shown in the example below. + +```ts +const basePath = '/' + bookDef.name; +const BookController = defineCrudRestController(BookModel, {basePath}); +inject(repoBinding.key)(BookController, undefined, 0); +``` + +The controller is then attached to the app by calling the `app.controller()` +method. + +```ts +app.controller(BookController); +``` + +The new CRUD REST endpoints for the model will be available on the app now. + +If you want a customized controller, you can create a copy of +`defineCrudRestController`'s +[implementation](https://github.com/strongloop/loopback-next/blob/00917f5a06ea8a51e1f452f228a6b0b7314809be/packages/rest-crud/src/crud-rest.controller.ts#L129-L269) +and modify it according to your requirements. + +For details about `defineCrudRestController` and `CrudRestControllerOptions`, +refer to the [@loopback/rest-crud API documentation](./apidocs/rest-crud.html). diff --git a/docs/site/DataSources.md b/docs/site/DataSources.md index 788310a1389a..1f233e63a7bb 100644 --- a/docs/site/DataSources.md +++ b/docs/site/DataSources.md @@ -52,3 +52,32 @@ export class DbDataSource extends juggler.DataSource { } } ``` + +### Creating a DataSource at Runtime + +A datasource can be created at runtime by creating an instance of +`juggler.DataSource`. It requires a name for the datasource, the connector, and +the connection details. + +```ts +import {juggler} from '@loopback/repository'; +const dsName = 'bookstore-ds'; +const bookDs = new juggler.DataSource({ + name: dsName, + connector: require('loopback-connector-mongodb'), + url: 'mongodb://sysop:moon@localhost', +}); +await bookDs.connect(); +app.dataSource(bookDs, dsName); +``` + +For details about datasource options, refer to the [DataSource +documentation])(https://apidocs.strongloop.com/loopback-datasource-juggler/#datasource) +. + +Attach the newly created datasource to the app by calling `app.dataSource()`. + +{% include note.html content=" +The `app.datasource()` method is available only on application classes +with `RepositoryMixin` applied. +" %} diff --git a/docs/site/Model.md b/docs/site/Model.md index b0d66e0c7a7e..58a46ff87f94 100644 --- a/docs/site/Model.md +++ b/docs/site/Model.md @@ -112,6 +112,52 @@ export class Customer { } ``` +## Defining a Model at runtime + +Models can be created at runtime using the `defineModelClass()` helper function +from the `@loopback/repository` class. It expects a base model to extend +(typically `Model` or `Entity`), followed by a `ModelDefinition` object as shown +in the example below. + +```ts +const bookDef = new ModelDefinition('Book') + .addProperty('id', {type: 'number', id: true}) + .addProperty('title', {type: 'string'}); +const BookModel = defineModelClass( + Entity, // Base model + bookDef, // ModelDefinition +); +``` + +You will notice that we are specifying generic parameters for the +`defineModelClass()` function. The first parameter is the base model, the +second one is an interface providing the TypeScript description for the +properties of the model we are defining. If the interface is not specified, the +generated class will have only members inherited from the base model class, +which typically means no properties. + +In case you need to use an existing Model as the base class, specify the Model +as the base class instead of `Entity`. + +```ts +// Assuming User is a pre-existing Model class in the app +import {User} from './user.model'; +import DynamicModelCtor from '@loopback/repository'; +const StudentModel = defineModelClass< + typeof User, + // id being provided by the base class User + {university?: string} +>(User, studentDef); +``` + +If you want make this new Model available from other parts of the app, you can +call `app.model(StudentModel)` to create a binding for it. + +{% include note.html content=" +The `app.model()` method is available only on application classes with +`RepositoryMixin` applied. +" %} + ## Model Discovery LoopBack can automatically create model definitions by discovering the schema of diff --git a/docs/site/Repositories.md b/docs/site/Repositories.md index 6f6d0c868e3a..38e9f9f1b154 100644 --- a/docs/site/Repositories.md +++ b/docs/site/Repositories.md @@ -300,6 +300,79 @@ Please See [Testing Your Application](Testing-your-application.md) section in order to set up and write unit, acceptance, and integration tests for your application. +## Creating Repositories at Runtime + +Repositories can be created at runtime using the `defineCrudRepositoryClass` +helper function from the `@loopback/rest-crud` package. It creates +`DefaultCrudRepository`-based repository classes by default. + +```ts +const BookRepository = defineCrudRepositoryClass< + Book, + typeof Book.prototype.id, + BookRelations +>(BookModel); +``` + +In case you want to use a non-`DefaultCrudRepository` repository class or you +want to create a custom repository, use the `defineRepositoryClass()` helper +function instead. Pass a second parameter to this function as the base class for +the new repository. + +There are two options for doing this: + +#### 1. Using a base repository class + +Create a base repository with your custom implementation, and then specify this +repository as the base class. + +```ts +class MyRepoBase< + E extends Entity, + IdType, + Relations extends object +> extends DefaultCrudRepository { + // Custom implementation +} + +const BookRepositoryClass = defineRepositoryClass< + typeof BookModel, + MyRepoBase +>(BookModel, MyRepoBase); +``` + +#### 2. Using a Repository mixin + +Create a repository mixin with your customization as shown in the +[Defining A Repository Mixin Class Factory Function](https://loopback.io/doc/en/lb4/migration-models-mixins.html#defining-a-repository-mixin-class-factory-function) +example, apply the mixin on the base repository class (e.g. +`DefaultCrudRepository`) then specify this combined repository as the base class +to be used. + +```ts +const BookRepositoryClass = defineRepositoryClass< + typeof BookModel, + DefaultCrudRepository< + BookModel, + typeof BookModel.prototype.id, + BookRelations + > & + FindByTitle +>(BookModel, FindByTitleRepositoryMixin(DefaultCrudRepository)); +``` + +Dependency injection has to be configured for the datasource as shown below. + +```ts +inject(`datasources.${dsName.name}`)(BookRepository, undefined, 0); +const repoBinding = app.repository(BookRepository); +``` + +{% include note.html content=" +The `app.repository()` method is available only on application classes +with `RepositoryMixin` applied. +" %} + ## Access KeyValue Stores We can now access key-value stores such as [Redis](https://redis.io/) using the