diff --git a/packages/rest/src/__tests__/integration/express.integration.ts b/packages/rest/src/__tests__/integration/express.integration.ts new file mode 100644 index 000000000000..1c8ae25d0a5f --- /dev/null +++ b/packages/rest/src/__tests__/integration/express.integration.ts @@ -0,0 +1,89 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/rest +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Application} from '@loopback/core'; +import {get} from '@loopback/openapi-v3'; +import { + Client, + createClientForHandler, + expect, + givenHttpServerConfig, +} from '@loopback/testlab'; +import * as express from 'express'; +import {RestComponent} from '../../rest.component'; +import { + HttpRequestListener, + RestServer, + RestServerConfig, +} from '../../rest.server'; + +describe('HttpHandler mounted as an express router', () => { + let client: Client; + beforeEach(givenLoopBackApp); + beforeEach(givenExpressApp); + beforeEach(givenClient); + + context('with a simple HelloWorld controller', () => { + beforeEach(function setupHelloController() { + class HelloController { + @get('/hello') + public async greet(): Promise { + return 'Hello world!'; + } + } + + givenControllerClass(HelloController); + }); + + it('handles simple "GET /hello" requests', () => { + return client + .get('/api/hello') + .expect(200) + .expect('content-type', 'text/plain') + .expect('Hello world!'); + }); + + it('handles openapi.json', async () => { + const res = await client + .get('/api/openapi.json') + .set('Accept', 'application/json') + .expect(200); + expect(res.body.servers[0]).to.eql({url: '/api'}); + }); + }); + + let expressApp: express.Application; + let server: RestServer; + let handler: HttpRequestListener; + + function givenControllerClass( + // tslint:disable-next-line:no-any + ctor: new (...args: any[]) => Object, + ) { + server.controller(ctor); + } + + async function givenLoopBackApp( + options: {rest: RestServerConfig} = {rest: {port: 0}}, + ) { + options.rest = givenHttpServerConfig(options.rest); + const app = new Application(options); + app.component(RestComponent); + server = await app.getServer(RestServer); + handler = server.requestHandler; + } + + /** + * Create an express app that mounts the LoopBack routes to `/api` + */ + function givenExpressApp() { + expressApp = express(); + expressApp.use('/api', handler); + } + + function givenClient() { + client = createClientForHandler(expressApp); + } +}); diff --git a/packages/rest/src/rest.server.ts b/packages/rest/src/rest.server.ts index 564cdf9d6ce4..3ed4dd1b4177 100644 --- a/packages/rest/src/rest.server.ts +++ b/packages/rest/src/rest.server.ts @@ -249,7 +249,7 @@ export class RestServer extends Context implements Server, HttpServerLike { const mapping = this.config.openApiSpec!.endpointMapping!; // Serving OpenAPI spec for (const p in mapping) { - this._expressApp.use(p, (req, res) => + this._expressApp.get(p, (req, res) => this._serveOpenApiSpec(req, res, mapping[p]), ); } @@ -376,11 +376,12 @@ export class RestServer extends Context implements Server, HttpServerLike { specObj.servers = [{url: this._getUrlForClient(request)}]; } - if (specObj.servers && this._basePath) { + const basePath = this.getBasePathFor(request); + if (specObj.servers && basePath) { for (const s of specObj.servers) { // Update the default server url to honor `basePath` if (s.url === '/') { - s.url = this._basePath; + s.url = basePath; } } } @@ -453,7 +454,21 @@ export class RestServer extends Context implements Server, HttpServerLike { // add port number of present host += port !== '' ? ':' + port : ''; - return protocol + '://' + host + this._basePath; + return protocol + '://' + host + this.getBasePathFor(request); + } + + /** + * Get the base for the request. It honors `baseUrl` sets by express if the + * application is mounted to an express app, such as: + * expressApp.use('/api', app.requestHandler); + * @param request Http request + */ + private getBasePathFor(request: Request) { + let basePath = this._basePath; + if (request.baseUrl && request.baseUrl !== '/') { + basePath = request.baseUrl + basePath; + } + return basePath; } private async _redirectToSwaggerUI(