(apigateway): L2 construct support for Routing Rules on custom domain names
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### 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)
```ts
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
```ts
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](https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-routing-rules.html#rest-api-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`.
```ts
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
- [Routing rules overview](https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-routing-rules.html)
- [How to use routing rules](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-routing-rules-use.html)
- [Set the routing mode](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-routing-mode.html)
### Acknowledgements
- [x] 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
Contributor guide
Research direction
Start by reading the existing aws-apigateway DomainName construct and its addBasePathMapping(), addApiMapping(), and apigatewayv2 CfnApiMapping patterns. Trace how the proposed RoutingRule, RoutingMode, routing-mode enforcement, and synth-time validation should fit those APIs; done means the L2 construct, standalone form, validation, and documented routing behavior are covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- api, cloud
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100