-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[WIP][RFC] Add local swagger ui for LoopBack 4 API explorer #1664
Conversation
11d970e
to
2fb8a59
Compare
80c4a4a
to
4b58087
Compare
8e7097c
to
dfda1d8
Compare
dfda1d8
to
e526609
Compare
I am not comfortable adding
Could you please look for other options on how to add local swagger-ui? Ideally, I'd like import {ExplorerComponent} from '@loopback/explorer';
class MyApp extends RestApplication {
constructor() {
app.component(ExplorerComponent);
}
} Implementation wise, we need the server to provide the following two features IIUC:
|
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.
Let's discuss the high-level design first, see my comment above.
Yes, |
@bajtos @hacksparrow I agree that it should be an extension to I'm just taking baby steps to get there. :-). |
e526609
to
7291ead
Compare
828295d
to
154f434
Compare
7291ead
to
cba9e21
Compare
I took a step to create There seems to be few gaps to have |
// FIXME: How do we configure the API Explorer UI? | ||
server.bind(ExplorerBindings.CONFIG).to({}); | ||
|
||
// FIXME: Should the ExplorerComponent contribute a handler or route? |
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 think it might be better to contribute a route. Do we even support multiple handlers within an application?
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.
How do we contribute a route?
- call APIs on RestServer such as
route(...)
? This will create a dependency fromexplorer
to `rest. - bind the Explorer route to
rest.server.routes
? The tricky thing is that we can have multiple REST server instances. Do we useinject.setter
to bind it to the server context?
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 guess this is a missing piece that needs to be figured out. I think ideally a component should be allowed to contribute a route ... similar to how it can contribute a component and then rest will figure out the mapping of the files to the route.
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.
At the moment, extensions can contribute controller (classes). So instead of contributing a single route, Explorer can contribute controller class with a single method (HTTP endpoint). That should work OOTB right now, BUT this new endpoint will be included in OpenAPI spec documenting application's API. I think we should add an option for hiding certain endpoints from the documentation, e.g. @undocumented
or @private
.
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.
call APIs on RestServer such as route(...)? This will create a dependency from explorer to `rest.
I am not really concerned about this. IMO, the API explorer cannot function with the REST component, because it's tightly coupled with OpenAPI.
If you are concerned about coupling at the level of Node.js dependencies, then I am proposing to define a minimal RestApplication
interface inside the Explorer extension, this interface can describe the route
API we need.
154f434
to
d2572f3
Compare
Btw, what do we want the url to be? |
packages/rest/src/rest.server.ts
Outdated
// E.g. if the app implements access/audit logs, I don't want | ||
// this endpoint to trigger a log entry. If the server implements | ||
// content-negotiation to support XML clients, I don't want the OpenAPI | ||
// spec to be converted into an XML response. |
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.
Please preserve this comment.
packages/rest/src/rest.server.ts
Outdated
// Serving OpenAPI spec | ||
for (const p in mapping) { | ||
this._expressApp.use(p, (req, res) => | ||
this._serveOpenApiSpec(req, res, mapping[p]), |
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.
First of all, please move this new setup code to a new method, similar to _setupRouterForStaticAssets
.
Secondly, I think this change makes sense on its own and should have been made as part of #1637. Could you please move it to #1637 or perhaps open a new PR after #1637 is landed? I'd like to get this cleanup landed quickly and fear that the Explorer pull request will require longer to get into a mergeable state.
// FIXME: How to register routes to the rest server by phase or order | ||
// FIXME: Should REST server automatically mounts the route based on registration | ||
// via bindings such as `rest.server.routes`? | ||
server.staticRouter.use('/explorer', handler); |
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 find this very hacky.
Just the fact that staticRouter
is a public property is alarming to me, as it opens door to all kinds of hacks that people can start making with our internal implementation details of app.static()
. Let's fix that please and make server.staticRouter
a private property.
Secondly, I thought the entire purpose of app.static
was to allow the explorer component to serve swagger-ui static assets. I don't fully understand why you don't want to use it?
IMO, we should focus on RestApplication
scenario in the first iteration because multi-server apps are not officially supported yet, and call app.static()
in component constructor.
Thoughts?
packages/rest/src/rest.server.ts
Outdated
@@ -561,6 +555,10 @@ export class RestServer extends Context implements Server, HttpServerLike { | |||
this._routerForStaticAssets.use(path, express.static(rootDir, options)); | |||
} | |||
|
|||
get staticRouter() { |
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.
Please don't, see my comment above.
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 agree. I'm playing with various options here to surface the problems.
async function givenAServer(options?: {rest: RestServerConfig}) { | ||
const app = new Application(options); | ||
app.component(RestComponent); | ||
// FIXME: Can we mount the ExplorerComponent to a given server? |
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 think this is a missing extension point?
At the moment, components can customize only the entire Application
, they don't have a good access to individual Server
instances.
However, for 4.0 GA, we support only single-server applications using RestApplication
, so I would not worry about this too much. I am proposing to create a follow-up issue for post-GA and move on.
I'd like to propose an entirely different approach. IMO, LoopBack is primarily intended for REST API servers, it's not a universal HTTP server good for serving single-page applications. API Explorer is a single page application. What if we leverage vanilla express to build API Explorer and serve the explorer on a different port?Or perhaps use middleware composition to add LoopBack application as a middleware in the top-level express app? const apiApp = new TodoApplication();
const container = express();
container.use('/api', apiApp.requestHandler);
container.use('/explorer-config', (req, res) => {
// serve dynamic configuration needed by swagger-ui
// see loopback-component-explorer for details
});
container.use('/explorer', express.static('path-to-swagger-ui')); Thoughts? |
I like that approach - less disruptive and simpler to implement. Things to consider:
|
b56fb37
to
eca79a4
Compare
cba9e21
to
d5e9db2
Compare
@raymondfeng Can you please rebase this PR? Something seems off as I'm seeing 28 commits & 105 file changes and can't do a review at this time as a result. |
2068c9b
to
b2ca7dc
Compare
I am not happy at all about allowing third party components and user applications to access the internal Express router.
In one of my earlier comments, I was proposing to use
I am not entirely against using a different approach, but need to hear an explanation why we cannot use Considering that an explorer hosted at For example:
|
Please note we allow customization of the home page and render it as an EJS view while keeping relative paths to js assets from
Sure, |
In the latest code, I have removed the exposure of the internal Express router. Instead, we allow extensions to bind an Express middleware to the extension point.
Yes and No. Yes - We want to have a better handler/chain design. No - One of the major reason of re-adopting Express as the http container is to allow the pluggability of existing Express middleware modules as-is.
Again, we don't expose Router directly. Instead, we'll offer an API such as We should open a new issue to discuss how to position Express with LoopBack 4 and what's the ideal programming model to get the best of both breeds. |
@@ -272,13 +282,29 @@ export class RestServer extends Context implements Server, HttpServerLike { | |||
return this.httpHandler.handleRequest(request, response); | |||
} | |||
|
|||
protected _registerMiddleware() { |
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.
👍
credentials: true, | ||
}; | ||
// Set up CORS | ||
this._expressApp.use(cors(corsOptions)); |
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.
Can we register this using this._registerMiddlerware()
to serve as an example.
bb71f55
to
2e9730e
Compare
2e9730e
to
c1d710b
Compare
I opened a new PR inspired by this work, see #2014 |
Depends on #1611
Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated