Skip to content

(apigateway): L2 construct support for Routing Rules on custom domain names #36917

@kawaaaas

Description

@kawaaaas

Describe the feature

The DomainName construct in aws-apigateway supports base path mappings and API mappings, but there's no L2 for Routing Rules (AWS::ApiGatewayV2::RoutingRule). To use routing rules today, you have to drop down to L1 constructs, manually build domain name ARNs, and give up synth-time validation entirely.

This proposal adds

  1. A RoutingRule L2 construct in aws-apigateway
  2. A RoutingMode enum and routingMode property on DomainName
  3. An addRoutingRule() method on DomainName

Use Case

A common pattern with routing rules is putting multiple REST APIs behind a single custom domain and splitting traffic by path or header.

  • /users/* → Users Service API
  • /orders/* → Orders Service API
  • Header x-api-version: v2 → New Orders Service API
  • Everything else → Default API (catch-all)

Current workaround (L1)

const cfnDomainName = new apigw.CfnDomainName(this, 'CustomDomain', {
  domainName: 'api.example.com',
  regionalCertificateArn: certificate.certificateArn,
  endpointConfiguration: { types: ['REGIONAL'] },
  routingMode: 'ROUTING_RULE_ONLY',
});

const domainNameArn = cdk.Arn.format(
  {
    service: 'apigateway',
    resource: '/domainnames',
    resourceName: 'api.example.com',
  },
  cdk.Stack.of(this),
);

new apigwv2.CfnRoutingRule(this, 'UsersRule', {
  domainNameArn,
  priority: 100,
  conditions: [{ matchBasePaths: { anyOf: ['users'] } }],
  actions: [
    {
      invokeApi: {
        apiId: usersApi.restApiId,
        stage: usersApi.deploymentStage.stageName,
        stripBasePath: true,
      },
    },
  ],
});

// ... and repeat for every rule

The main pain points with this approach

  • You have to know the ARN format for API Gateway domain names and build it yourself with cdk.Arn.format()
  • You need to import aws-apigatewayv2 even though routing rules are a REST API feature, which is just confusing
  • The CloudFormation structure is deeply nested (matchBasePaths.anyOf, invokeApi.apiId, etc.) and easy to get wrong
  • There's zero validation until deploy time, so typos in header names, out-of-range priorities, or routing mode mismatches all turn into slow CloudFormation rollbacks
  • Nothing prevents you from mixing addBasePathMapping() with ROUTING_RULE_ONLY mode or vice versa

Proposed Solution

Proposed API

import { DomainName, RoutingMode } from 'aws-cdk-lib/aws-apigateway';

const domain = new DomainName(this, 'Domain', {
  domainName: 'api.example.com',
  certificate: certificate,
  routingMode: RoutingMode.ROUTING_RULE_ONLY,
});

// Path-based routing
domain.addRoutingRule('UsersRule', {
  priority: 100,
  conditions: {
    basePaths: ['users'],
  },
  action: {
    restApi: usersApi,
    stripBasePath: true,
  },
});

// Header-based routing
domain.addRoutingRule('HeaderV2Rule', {
  priority: 50,
  conditions: {
    headers: [{ header: 'x-api-version', valueGlob: 'v2' }],
  },
  action: {
    restApi: ordersApiV2,
  },
});

// Combined conditions (headers AND basePaths)
domain.addRoutingRule('V2OrdersRule', {
  priority: 75,
  conditions: {
    basePaths: ['orders'],
    headers: [{ header: 'x-api-version', valueGlob: 'v2' }],
  },
  action: {
    restApi: ordersApiV2,
    stripBasePath: true,
  },
});

// Catch-all (no conditions)
domain.addRoutingRule('CatchAllRule', {
  priority: 999999,
  action: {
    restApi: defaultApi,
  },
});

Design Decisions

addRoutingRule() on DomainName with RoutingMode enforcement

addRoutingRule() sits alongside the existing addBasePathMapping() and addApiMapping(). The construct checks that the method matches the configured routing mode — calling addRoutingRule() on a BASE_PATH_MAPPING_ONLY domain (the default) gives a clear error telling you to set the mode. Same in the other direction.

Synth-time validation

The construct validates all the constraints from the Routing rules restrictions docs at synth time, so you don't have to wait for a deploy to find out your header name is restricted or your priority is out of range. Unresolved tokens skip validation as usual.

RoutingRule also works as a standalone construct

For cross-stack scenarios, RoutingRule can be instantiated directly with a domainName: IDomainName prop, same pattern as BasePathMapping.

new RoutingRule(this, 'UsersRule', {
  domainName: importedDomain,
  priority: 100,
  conditions: {
    basePaths: ['users'],
  },
  action: {
    restApi: usersApi,
    stage: usersApi.deploymentStage,
    stripBasePath: true,
  },
});

Other Information

  • Under the hood this uses AWS::ApiGatewayV2::RoutingRule. It's a REST API feature but lives in the v2 namespace on the CloudFormation side. The existing DomainName L2 already uses apigwv2.CfnApiMapping internally, so this isn't a new pattern.
  • Routing rules only work with REGIONAL endpoints.
  • AWS docs

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

AWS CDK Library version (aws-cdk-lib)

2.x

AWS CDK CLI version

2.x

Environment details (OS name and version, etc.)

all

Metadata

Metadata

Assignees

No one assigned

    Labels

    @aws-cdk/aws-apigatewayRelated to Amazon API Gatewayeffort/mediumMedium work item – several days of effortfeature-requestA feature should be added or improved.p2

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions