-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Extension: CrudRestController #3582
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
Conversation
a664e45 to
bec7199
Compare
|
Added docs and implemented the missing operations. The pull request is ready for review. /cc @raymondfeng @strongloop/loopback-maintainers |
| IdType, | ||
| IdName extends keyof T, | ||
| Relations extends object = {} | ||
| >( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it feasible to define a CrudRestController from a Repository (instance or class) directly? Ideally, if a developer adds a repository using lb4 repository to connect a model to a data source, we should allows him/her to expose the repository as CRUD REST APIs out of box.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an interesting idea, we can explore it later if you like. This new module is in 0.x version, so even if we need to make breaking changes to support your idea, then that's ok.
Our current plan is to allow developers to go from lb4 model to REST API with no custom repository class needed - see #2036
The idea is that the user will tell us that let's say Product model should be exposed via CRUD REST API (as opposed to e.g. KeyValue REST API) using datasource db, and the framework will take care of the rest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation requires a repository class to be defined. I added a few comments inline. I'm open to incremental improvements.
| /** | ||
| * The base path where to "mount" the controller. | ||
| */ | ||
| basePath: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to make this optional and use a default basePath if one isn't provided?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would require us to implement some logic to convert a model name (e.g. Product) to a path (e.g. /products). This gets quickly tricky - do we want /people or /persons for Person model?
I'd prefer to keep basePath required for this initial implementation, we can make it optional later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps the purpose of this field is not clear enough and we should improve the api docs? Can you suggest a better wording? Would an example help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would require us to implement some logic to convert a model name (e.g.
Product) to a path (e.g./products). This gets quickly tricky - do we want/peopleor/personsforPersonmodel?
I was thinking we could use something similar to how the controller generator does it which in this scenario would make it /people.
I'd prefer to keep
basePathrequired for this initial implementation, we can make it optional later.
That's fine with me 👍
Perhaps the purpose of this field is not clear enough and we should improve the api docs? Can you suggest a better wording? Would an example help?
It's clear to me. My suggestion was so that we could make options optional and be able to exclude it from the function call:
const CrudRestController = defineCrudRestController<
Product,
typeof Product.prototype.id,
'id'
>(Product, {basePath: '/products'});vs.
const CrudRestController = defineCrudRestController<
Product,
typeof Product.prototype.id,
'id'
>(Product);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking we could use something similar to how the controller generator does it which in this scenario would make it
/people.
Makes sense! I had mixed experience with auto-generated base path for CRUD endpoints in LB3. Some people found the pluralization rules confusing ("people" vs. "persons"), some wanted different mechanism for converting pascal-cased model names (e.g. "ProductReview") to HTTP paths (e.g. "/ProductReviews", "/product-reviews", etc.).
Let's keep basePath optional for now and wait until we learn more about this area while working on #2036.
|
|
||
| 1. Define your model class, e.g. using `lb4 model` tool. | ||
|
|
||
| 2. Create a Repository class, e.g. using `lb4 repository` tool. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it required? IIUC, you want to skip Repository class so that a model can be used to expose REST CRUD APIs with a datasource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A custom repository class is not required, this is just to make the documentation easier to follow.
This new component is meant to be a low-level building block to support #2036, I am expecting that most people most people will use #2036 and don't leverage the current API directly.
For the initial docs, I choose a scenario that's easy to understand (because it's using existing concepts) and that's matching the scenario used for testing.
Depending on the outcome of the next tasks (especially the spike #2738), we may add more "meat" to this component and update or even rework the README along the way.
|
|
||
| ```ts | ||
| class ProductController extends CrudRestController { | ||
| constructor(@repository(ProductRepository) repo: ProductRepository) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we skip the repository, can we inject a datasource instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I believe injecting a datasource should be a viable option too. The actual implementation does not prescribe any IoC setup, the base controller class is expecting to receive a repository instance. It's up to the code extending the controller class to decide how to obtain the repository.
I believe the following solution should work as an alternative approach too:
class ProductController extends CrudRestController {
constructor(
@inject('datasources.db') dataSource: juggler.DataSource,
) {
const repo = new DefaultCrudRepository<
Product,
typeof Product.prototype.id
>(Product, db);
super(repo);
}
}@raymondfeng Would you like me to open a new issue to add a test & documentation for this approach?
raymondfeng
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few more comments, which can be addressed in follow-up tasks.
jannyHou
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 LGTM, a few comments about the status code.
Introduce a new package `@loopback/rest-crud` providing a controller class implementing default CRUD operations for a given Entity.
|
@raymondfeng @nabdelgadir @jannyHou thank you for the review and thoughtful comments. I am going to land this pull request as it is now. Please let me know if you would like me to improve any parts of this initial implementation as part of the story #2736. |
This pull request adds
CrudRestControllerextension as described in #2736. See the top-level EPIC describing the big picture & plan: #2036 From model definition to REST API with no custom repository/controller classes.Example use of the new controller:
Create a base REST CRUD controller class for your model.
Create a new subclass of the base controller class to configure repository
injection.
Register the controller with your application.
Checklist
👉 Read and sign the CLA (Contributor License Agreement) 👈
npm testpasses on your machinepackages/cliwere updatedexamples/*were updated👉 Check out how to submit a PR 👈