diff --git a/packages/rest/src/rest.server.ts b/packages/rest/src/rest.server.ts index 8c389c5aa3df..501d672227df 100644 --- a/packages/rest/src/rest.server.ts +++ b/packages/rest/src/rest.server.ts @@ -127,6 +127,7 @@ export class RestServer extends Context implements Server, HttpServerLike { public requestHandler: HttpRequestListener; public readonly config: RestServerConfig; + private _basePath: string; protected _httpHandler: HttpHandler; protected get httpHandler(): HttpHandler { this._setupHandlerIfNeeded(); @@ -186,6 +187,12 @@ export class RestServer extends Context implements Server, HttpServerLike { this.sequence(config.sequence); } + let basePath = config.basePath || ''; + // Trim leading and trailing `/` + basePath = basePath.replace(/(^\/)|(\/$)/, ''); + if (basePath) basePath = '/' + basePath; + this._basePath = basePath; + this._setupRequestHandler(); this.bind(RestBindings.HANDLER).toDynamicValue(() => this.httpHandler); @@ -220,7 +227,7 @@ export class RestServer extends Context implements Server, HttpServerLike { this._setupOpenApiSpecEndpoints(); // Mount our router & request handler - this._expressApp.use((req, res, next) => { + this._expressApp.use(this._basePath, (req, res, next) => { this._handleHttpRequest(req, res).catch(next); }); @@ -374,6 +381,14 @@ export class RestServer extends Context implements Server, HttpServerLike { specObj.servers = [{url: this._getUrlForClient(request)}]; } + if (specObj.servers && this._basePath) { + for (const s of specObj.servers) { + if (s.url.startsWith('/')) { + s.url = s.url === '/' ? this._basePath : this._basePath + s.url; + } + } + } + if (specForm.format === 'json') { const spec = JSON.stringify(specObj, null, 2); response.setHeader('content-type', 'application/json; charset=utf-8'); @@ -442,7 +457,7 @@ export class RestServer extends Context implements Server, HttpServerLike { // add port number of present host += port !== '' ? ':' + port : ''; - return protocol + '://' + host; + return protocol + '://' + host + this._basePath; } private async _redirectToSwaggerUI( @@ -884,6 +899,10 @@ export interface ApiExplorerOptions { * Options for RestServer configuration */ export interface RestServerOptions { + /** + * Base path for API/static routes + */ + basePath?: string; cors?: cors.CorsOptions; openApiSpec?: OpenApiSpecOptions; apiExplorer?: ApiExplorerOptions; diff --git a/packages/rest/test/integration/rest.server.integration.ts b/packages/rest/test/integration/rest.server.integration.ts index f2748aefdf36..03918bea1b79 100644 --- a/packages/rest/test/integration/rest.server.integration.ts +++ b/packages/rest/test/integration/rest.server.integration.ts @@ -701,6 +701,39 @@ paths: await server.stop(); }); + it('allows `basePath` for routes', async () => { + const root = ASSETS; + const server = await givenAServer({ + rest: { + basePath: '/api', + port: 0, + }, + }); + server.static('/html', root); + server.controller(DummyController); + + const content = fs + .readFileSync(path.join(root, 'index.html')) + .toString('utf-8'); + await createClientForHandler(server.requestHandler) + .get('/api/html/index.html') + .expect('Content-Type', /text\/html/) + .expect(200, content); + + await createClientForHandler(server.requestHandler) + .get('/api/html') + .expect(200, 'Hi'); + + await createClientForHandler(server.requestHandler) + .get('/html') + .expect(404); + + const response = await createClientForHandler(server.requestHandler).get( + '/openapi.json', + ); + expect(response.body.servers).to.containEql({url: '/api'}); + }); + async function givenAServer(options?: {rest: RestServerConfig}) { const app = new Application(options); app.component(RestComponent);