Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: dynamic datasource, model, repository, and controller #5630

Merged
merged 1 commit into from
Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/site/Controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should mention Class factory to allow parameterized decorations per https://loopback.io/doc/en/lb4/core-tutorial-part10.html (Class factory to allow parameterized decorations).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really convinced. If I am understanding correctly, the content at the link describes a general programming pattern, not something specific to Controllers. If that's the case, we should avoid adding non-specific content to avoid noise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class factory is part of the programming model pattern and it's being asked by our users. It gives our users more control. The change is applying to Controllers.md and I don't think that's noise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is applying to Controllers.md

Got the context, I thought it was about runtime controllers.

Added Class factory to allow parameterized decorations at an appropriate location.

29 changes: 29 additions & 0 deletions docs/site/DataSources.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
" %}
46 changes: 46 additions & 0 deletions docs/site/Model.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain what is the second type parameter for?


```ts
const bookDef = new ModelDefinition('Book')
.addProperty('id', {type: 'number', id: true})
.addProperty('title', {type: 'string'});
const BookModel = defineModelClass<typeof Entity, {id: number; title?: string}>(
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
bajtos marked this conversation as resolved.
Show resolved Hide resolved
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);
```

raymondfeng marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
73 changes: 73 additions & 0 deletions docs/site/Repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<E, IdType, Relations> {
// Custom implementation
}

const BookRepositoryClass = defineRepositoryClass<
typeof BookModel,
MyRepoBase<BookModel, typeof BookModel.prototype.id, BookRelations>
>(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>
>(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
Expand Down