serverless-express icon indicating copy to clipboard operation
serverless-express copied to clipboard

Request path should not include the stage name with API Gateway V2 event source

Open madmox opened this issue 2 years ago • 1 comments

AWS always includes the stage name in the request path of API Gateway V2 events, even when using a custom domain and an ApiMapping.

Without a custom domain

  • Invoke URL: https://{apiId}.execute-api.{region}.amazonaws.com/{stageName}/foo
  • API Gateway V2 event's rawPath: /{stageName}/foo
  • Path forwarded to the application: /{stageName}/foo

With a custom domain / ApiMapping using an ApiMappingKey

  • Invoke URL: https://{customDomain}/{ApiMappingKey}/foo
  • API Gateway V2 event's rawPath: /{stageName}/foo
  • Path forwarded to the application: /{stageName}/foo

This means there is no reliable way to determine the real path entered in the user's address bar (stage name is not relevant when using a custom domain with ApiMapping, and ApiMappingKey is never forwarded in the event) :/ In the end, I think serverless-express should not include the stage name in the request path forwarded to express and allow to set a custom path prefix for all requests. It would make it possible to have a valid path for all deployment scenarios with a relatively minimal configuration setup.

There are multiple ways to do this, a reliable one being to trim the stage name from the path using event.requestContext.stage. You could also use event.pathParameters.proxy to set the path, but the user might not have used the {proxy+} proxy resource for his API.

madmox avatar Sep 24 '21 10:09 madmox

As a workaround, I use this piece of code to boostrap my handler:

const serverlessExpress = require('@vendia/serverless-express');
const app = require('./app');

module.exports.handler = function handler (event, context, callback) {
    // Strips stage path prefix (e.g. '/Prod' path segment) so that the request path
    // is valid when using custom domains with API Gateway V2 event source
    event.rawPath = event.rawPath.substring(`/${event.requestContext.stage}`.length);

    return serverlessExpress({ app })(event, context, callback);
};

It works fine but it's not really pretty: one should never have to "fix" input (event.rawPath) to fit a specific use case, because it might break other use cases based on the same field that worked properly otherwise.

Rather, you should either provide an extension point so that we can change the way the express request is built from the API Gateway event (decorator pattern, etc.), or build the request path as I suggested above directly in the library (trim the stage path prefix).

Retrocompatible implementation signatures examples:

const url = require('url');
module.exports.handler = serverlessExpress({
    app: app,
    configureRequest: (event, request) => {
        request.path = url.format({
            pathname: event.rawPath.substring(`/${event.requestContext.stage}`.length),
            search: event.rawQueryString
        });
    }
});

or

module.exports.handler = serverlessExpress({
    app: app,
    trimStageFromRequestPath: true
});

madmox avatar Sep 24 '21 16:09 madmox