Loopback-next: [API Explorer] - Add static file serving capabilities to RestServer

Created on 31 Oct 2017  路  16Comments  路  Source: strongloop/loopback-next

Story

As a LoopBack developer, I would like to have my RestServer provide static file hosting so that I can retrieve front-end content (HTML/CSS/JS).

(Additional note: This functionality would be required for an API Explorer Component)

Acceptance Criteria

  • [x] Add a new API to both RestServer and RestApplication to allow app developers to mount static middleware, for example app.static(path, options). Leverage Express middleware server-static to implement the actual file serving.

    • Should allow a user to specify a directory and tweak serve-static options

    • Should set the Content-Type header of the response based on the file's MIME type.

  • [x] Add a short section/guide to our documentation explaining users how to use this new API.

Stretch goals:

  • [ ] Should return a nicely formatted "404" error if the content could not be found.
    _Comment by @bajtos: Ideally, a LB4 app should have only one error handler responsible for handling all errors regardless of their source (controller methods, static middleware, input argument validation, etc.) To get there, we need to rethink and improve the error handling story in general, see #1434. Unless we can find a way how to get nice 404 errors for static content with very little effort, I am proposing to defer this item and make it a part of #1434._

Removed from scope by @bajtos on 2018-06-25:

  • Should default to /src/public
  • Should recursively map target directory's file and folder paths in memory (NOT THE CONTENT)
API Explorer p2

Most helpful comment

Having saying that, what is the right way to delivery html from the loopback app?

We don't have a proper solution for this yet, that's what this github issue is tracking. See https://github.com/strongloop/loopback-next/pull/1611 for the current work in progress.

Beside that, how can I prefix all rest calls with /api/ and router all urls not starting with /api/ to the function that deliveries the html responses?

In the future, we would like to introduce basePath option allowing developers to specify path prefix, see https://github.com/strongloop/loopback-next/issues/918 and https://github.com/strongloop/loopback-next/issues/914


A possible workaround solution addressing both problems you are facing: mount LB4 app as a middleware on top of your "main" express app.

const mainApp = express();
const apiApp = new MyLb4Application();

mainApp.use('/api', apiApp.requestHandler);
mainApp.use(express.static('./assets'));

mainApp.listen(3000);

All 16 comments

We are targeting API server developers for MVP release. Serving static files (e.g. single-page applications) is out of scope of that. We should revisit this after MVP and decide what different kinds of servers we want to support in LoopBack 4+

Might be able to resolve in https://github.com/strongloop/loopback-next/issues/1038.
cc @raymondfeng

I am taking this up. This will pave the way for the development of our middleware interface.

Don't we get this as a freebie when we switch to Express since it supports static file serving. We can just set it up for a default folder in basic app scaffolding (CLI) once the switch to Express has been made.

From my understanding, yes, we should get it for free after the switch to Express, or at least very minimal effort.
Since this is marked as non-DP3, we should evaluate what needs to be done (hopefully none). If there's work, we'll then defer it since it's not a DP3 item to begin with.

Yes, it comes baked in with Express. I was thinking more along the middleware interface we'll expose. With access to the Express instance and the app, users can enable this on their own. So, let's not touch this for now, we can create another story for the middleware interface.

Cross-posting https://github.com/strongloop/loopback-next/issues/559#issuecomment-399077339

I am proposing to enhance RestServer/RestApplication with an API allowing consumers to mount static files, for example app.static(path, options). This API should leverage Express middleware server-static as an implementation detail.

With this new API in place, we can either write an API Explorer component bundling swagger-ui and calling app.static(pathToSwaggerUI), or preferably write a short guide explaining LB4 users how to add swagger-ui to their project themselves.

Let's re-estimate this story based on the fact that Express is already a part of RestServer implementation.

I am using angular as my front-end and I use the angular universal, that allows me to render my angular app on server side. With a few lines I can write a express server to render the angular app and send static files like assets and JavaScript files. Now I am looking for a back-end and I find out Loopback 4 is awesome.
I can easily have my express app doing what I am doing above and have the loopback api listening in a different port in a different app with a nginx server proxying the api/* calls to loopback and the other calls to the express app. But it would be great if I can have all that in a single app.
When I render the angular app on the server, most time I have to call the api doing http calls. With a single server app, I can use loopback repositories directly in the angular app instead making http calls.
Having saying that, what is the right way to delivery html from the loopback app? Beside that, how can I prefix all rest calls with /api/ and router all urls not starting with /api/ to the function that deliveries the html responses?
By saying "prefix all rest calls with /api/" I mean that if I use a decorator in a controller

@get('persons/{id}')
getPerson( ... ) {
    ...
}

then loopback routes /api/persons/{id} instead /persons/{id} to that method.

For now I am doing trying to do the following

  async handle(context: RequestContext) {
    try {
      const {request, response} = context;
      if (!request.path.startsWith('/api/')) {
        response.contentType('text/html');
        response.send('<h1>Hello world</h1>');
        return;
      }
      // the line below doesn't work because request.url has only getter
      // request.url = request.url.substr(4);

      const args = await this.parseParams(request, route);
      const result = await this.invoke(route, args);
      this.send(response, result);
    } catch (err) {
      this.reject(context, err);
    }
  }

I solved the problem in this way
image

Having saying that, what is the right way to delivery html from the loopback app?

We don't have a proper solution for this yet, that's what this github issue is tracking. See https://github.com/strongloop/loopback-next/pull/1611 for the current work in progress.

Beside that, how can I prefix all rest calls with /api/ and router all urls not starting with /api/ to the function that deliveries the html responses?

In the future, we would like to introduce basePath option allowing developers to specify path prefix, see https://github.com/strongloop/loopback-next/issues/918 and https://github.com/strongloop/loopback-next/issues/914


A possible workaround solution addressing both problems you are facing: mount LB4 app as a middleware on top of your "main" express app.

const mainApp = express();
const apiApp = new MyLb4Application();

mainApp.use('/api', apiApp.requestHandler);
mainApp.use(express.static('./assets'));

mainApp.listen(3000);
const mainApp = express();
const apiApp = new MyLb4Application();

mainApp.use('/api', apiApp.requestHandler);
mainApp.use(express.static('./assets'));

mainApp.listen(3000);

@bajtos Amazing. With that solution I can continue using my existing express app and start using loopback straight away without any refactoring. Thanks.

I suggest to put that use case in documentation. I see loopback as a api only. Using loopback like in your suggestion I can have my main app doing other things and loopback focusing in api only. As I didn't know that I was able to do that, I started using apollo server, that has a express middleware that allow me to do exactly what you suggested.

@csbenjamin I am glad my solution works for you 馃憤

I suggest to put that use case in documentation

That's a great idea. Could you please contribute that change yourself?

I am not sure what would be the best place where to add this content. To keep things easy for you, I think it will be best to add a new question & answer to our FAQ by editing the following file: docs/site/FAQ.md. We can move the content around later, if/when we find a better home of it.

It may be worth adding a cross-reference to Why express behind the scene too, what do you think?

See the following page for guides explaining how to contribute documentation: https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md#documentation

We agreed with @dhmlau and @raymondfeng to create follow-up narrowly-focused stories for the features that are missing and close this story once they are created. I'll create the stories next week, I am thinking about:

  • Unified error handler for both REST APIs and static content.
  • Performance - REST router must be invoked before static content handler(s). This will allow - static content to be mounted on /.

Follow-up stories to address missing pieces:

  • Error handling for non-API routes #1784
  • Invoke serve-static after API router #1785
Was this page helpful?
0 / 5 - 0 ratings