Skip to content

From model definition to REST API with no custom repository/controller classes #2036

Closed

Description

@bajtos

In LoopBack 3, it was very easy to get a fully-featured CRUD REST API with very little code: a model definition describing model properties + a model configuration specifying which datasource to use.

Let's provide the same simplicity to LB4 users too.

  • User creates a model class and uses decorators to define model properties. (No change here.)
  • User declaratively defines what kind of data-access patterns to provide (CRUD, KeyValue, etc.) and what datasource to use under the hood.
  • @loopback/boot processes this configuration and registers appropriate repositories & controllers with the app.

For example, to create a Product model, attach it to a db datasources and expose a CRUD REST API, all that the developer needs is to add the following two files to their project (plus any npm packages that implement this functionality):

models/product.ts:

@model()
export class Product extends Entity {
  @property()
  name: string;
}

public-models/product.json:

{
  "model": "Product",
  "pattern": "Crud",
  "dataSource": "db",
}

Note: having config for multiple models in a single JSON file, as we do in LB 3.x, can quickly become a maintenance nightmare - see strongloop/loopback#1316. It will be better to have one config file per model.

An example file for a model using KeyValue pattern (not CRUD) - public-models/cache-entry.json:

{
  "model": "CacheEntry",
  "pattern": "KeyValue",
  "dataSource": "redis"
}

See also

Acceptance criteria

Out of scope

Activity

  1. changed the title [-]From model definition to REST API without custom repository/controller classes[/-] [+]From model definition to REST API with no repository/controller classes[/+] on Nov 16, 2018
  2. bajtos commented on Dec 4, 2018

    @bajtos
    MemberAuthor

    Why generate a model first? I think it's better if we can put an OpenAPI YAML or JSON file on some folder, and from the definition, generate the model, controller, and repository.

    Sure, that's an option too!

    However, if the model is defined in a YAML/JSON file, then there are no type definitions available and as a result, the compiler cannot check types in code using the generated model & repository - typically acceptance/integration tests using JS/TS API to setup and clean the database between the tests.

  3. raymondfeng commented on Dec 7, 2018

    @raymondfeng
    Contributor

    @gczobel-f5 We already have lb4 openapi command that generates corresponding artifacts from OpenAPI specs.

    To allow a set of OpenAPI specs in a folder to be discovered, we probably need to do the following:

    1. Add a booter to discover/load/code-gen for open api specs.
    2. Make it possible to invoke app.boot (up to certain stage) at build time to perform code generation.
    3. Integrate code-gen with npm run build:codegen.
  4. bajtos commented on Feb 12, 2019

    @bajtos
    MemberAuthor

    Cross-posting #1889 (comment)

    For example, why do not provide a set of extensions in order to keep the simplicity of LB3, without the needed to create repository, controller for default CRUD actions (only if we need custom actions) ?

    Yes please! It's our design goal to keep LoopBack 4+ extensible and allow the community to experiment with different styles of project layout. We don't have bandwidth to implement and maintain all such extensions ourselves, but we are happy to help anybody willing to step up and write such extension themselves.

    As for repositories, it's already possible to use DefaultCrudRepository without creating per-model repository classes.

    In #740, @raymondfeng has proposed an extension providing REST API via a base controller class CrudController that provides usual querying and CRUD operations, similar to what the controllers scaffolded by lb4 controller do (but without the scaffolding step).

    With these two buildings blocks, I imagine one can write a small piece of code accepting a model class an exposing it via REST API, see the mock-up implementation below. It's just an illustration to show what I mean, it will probably not work out of the box and may require cleanup.

    // usage - this can be automated via a custom Booter
    import {Product, Category} from './models';
    
    export class MyApplication extends RepositoryMixin(RestApplication) {
     constructor() {
        super();  
        exposeModelViaRest(app, Product);
        exposeModelViaRest(app, Category;
      }
    }
    
    // implementation
    function exposeModelViaRest<T extends Entity>(
      app: ApplicationWithRepositories,
      dataSourceName: string, 
      modelCtor: Class<T>
    ) {
      const REPOSITORY_KEY = `repositories.${modelCtor.modelName}Repository`;
      app.bind(REPOSITORY_KEY).toDynamicValue(() => {
        const ds = await ctx.get(`datasources.${dataSourceName}`);
        return new DefaultCrudRepository(modelCtor, ds);
      });
    
      app.controller(createCrudControllerForModel(modelCtor, REPOSITORY_KEY));
    }
    
    function createCrudControllerForModel<T extends Entity>(
      modelCtor: class<T>,
      repositoryKey: string
    ) {
      // It is not possible to access closure variables like "BaseController"
      // from a dynamically defined function. The solution is to
      // create a dynamically defined factory function that accepts
      // closure variables as arguments.
      const name = modelCtor.modelName + 'Controller';
      const factory = new Function('BaseController', 'repositoryName', `
        class ${name} extends BaseController {
          constructor(repository) {
            super(repository);
          }
        }`);
    
      const controllerCtor = factory(CrudController, repositoryName);
      // apply @inject() decorator on the first constructor parameter
      inject(repositoryKey)(controllerCtor, undefined, 0);
      return controllerCtor;
    }        
  5. changed the title [-]From model definition to REST API with no repository/controller classes[/-] [+]From model definition to REST API with no 'visible' repository/controller classes[/+] on Mar 19, 2019
  6. changed the title [-]From model definition to REST API with no 'visible' repository/controller classes[/-] [+]From model definition to REST API with no repository/controller classes[/+] on Mar 19, 2019
  7. emonddr commented on Mar 19, 2019

    @emonddr
    Contributor

    @bajtos , we need some clarification please. The title mentions the need for "no repository or controller" classes, yet controller classes are mention a few times.

    @loopback/boot processes this configuration and registers appropriate repositories & controllers with the app.
    ... providing CrudController class and ... providing KeyValueController class.

  8. 9 remaining items

  9. bajtos commented on May 13, 2019

    @bajtos
    MemberAuthor

    Moved this Epic from Q2 to Q3 because most of the stories (see my previous comment) are already assigned to Q3.

  10. changed the title [-]From model definition to REST API with no custom repository/controller classes[/-] [+][EPIC] From model definition to REST API with no custom repository/controller classes[/+] on Sep 12, 2019
  11. changed the title [-][EPIC] From model definition to REST API with no custom repository/controller classes[/-] [+]From model definition to REST API with no custom repository/controller classes[/+] on Sep 13, 2019
  12. dhmlau commented on Sep 17, 2019

    @dhmlau
    Member

    The tasks we've targeted for 2019Q3 is done. Moving this epic to 2019Q4 for the remaining items.

  13. nabdelgadir commented on Apr 14, 2020

    @nabdelgadir
    Contributor

    Blog post is ready to publish; closing this EPIC as done 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions