Skip to content

fix(ts#api,py#api): publish the custom domain URL in runtime config - #1278

Merged
shsrams merged 5 commits into
awslabs:mainfrom
AlexTo:fix/api-custom-domain-runtime-config
Oct 3, 2026
Merged

shsrams merged 5 commits into
awslabs:mainfrom
AlexTo:fix/api-custom-domain-runtime-config

Conversation

@AlexTo

@AlexTo AlexTo commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Reason for this change

When an API is given a custom domain, the vended constructs still publish the generated execute-api URL in runtime config, so websites and other clients keep calling the AWS endpoint instead of the custom domain. There is also no easy way to find the target for the domain's DNS record.

  • CDK RestApi: domainName is passed through to API Gateway, but runtime config always receives this.api.url (which also carries the /prod/ stage prefix the custom domain doesn't use).
  • CDK HttpApi: passing defaultDomainMapping throws, because the construct sets createDefaultStage: false and CDK only applies that prop to the default stage it creates.
  • Terraform: the REST and HTTP API app modules have no custom domain inputs at all, and runtime config is hard-coded to the stage invoke URL inside the module, so a domain wired up outside the module can't be published.

Description of changes

CDK (common/constructs/src/core/api)

  • RestApi destructures domainName, passes it to the CDK RestApi, and publishes https://<domainName>/ (plus <basePath>/ if set) in runtime config when it is configured, otherwise the execute-api URL as before.
  • HttpApi takes defaultDomainMapping out of the props passed to the CDK HttpApi and passes it as domainMapping to the stage it creates, then uses the stage's domainUrl for runtime config, the <apiName>Url stack output and the construct's url getter. The prop keeps CDK's name, but is typed with an IDomainName so its regional attributes are available (CDK types it as the narrower IDomainNameRef).
  • When a custom domain is configured, both output its DNS record target: <apiName>DomainNameAlias, the domain name to point a CNAME at with any DNS provider, and <apiName>DomainNameAliasHostedZoneId, needed only for a Route 53 alias record.

Terraform (common/terraform/src/app/apis/<name>/<name>.tf, REST and HTTP)

  • New optional custom_domain_name and acm_certificate_arn variables (named to match the static website module).
  • When set, a regional custom domain (TLS 1.2 minimum) and a base path mapping (REST) or API mapping (HTTP) are created. The REST domain serves the API without the stage path prefix. Setting custom_domain_name without acm_certificate_arn fails at plan time.
  • An api_url local is published in runtime config: the custom domain URL when configured, otherwise the stage invoke URL as before.
  • New outputs: api_url, plus custom_domain_target_domain_name and custom_domain_hosted_zone_id for the DNS record.

With no custom domain configured, runtime config and the deployed resources are unchanged.

Docs

  • New shared api/custom-domain snippet, included as a "Custom domain" section in the tRPC, FastAPI and Smithy API guides: configuring the domain and certificate in CDK (REST and HTTP) and Terraform, the runtime configuration URL, and creating the DNS record with any provider (CNAME) or Route 53 (alias).
  • The guides' Terraform output examples use the new api_url output, which reflects a configured custom domain.

Migration (latest/api-custom-domain-runtime-config, deterministic)

These files are vended with KeepExisting, so existing workspaces need a migration to reach the generated shape. It rewrites the vended CDK constructs and each Terraform API app module with GritQL, and a migrated file matches a freshly generated one exactly. It is conservative about what it touches:

  • Every precondition an edit depends on is checked before anything is written, so a file is migrated fully or not at all.
  • Edits are scoped to what changes: for example only the _HttpApiProps base of the HttpApiProps interface is replaced, keeping any other base interfaces, and an unrelated CfnOutput already in the construct doesn't block it.
  • The Terraform runtime config value must match the generated expression exactly; a customised URL (e.g. with a /v1/ suffix) is left alone.
  • Before inserting, it checks every .tf file in the module directory for the names it adds, since Terraform merges them. Any collision, or a partial/custom configuration already in place, leaves the module untouched.
  • Anything skipped is reported through nextSteps with what to reconcile by hand. A re-run is a no-op.

On the infrastructure transition: the only resources added are the domain and mapping, and only when a custom domain is configured, so migrating and redeploying an existing stack without one changes nothing but the new Terraform outputs.

Description of how you validated changes

  • Migration unit tests (21): today's generators produce REST and HTTP APIs for both CDK and Terraform, the new parts are stripped back out to recreate the previous shape, and the migrated files must equal the generated ones exactly. Also covers idempotency, diverged constructs and modules, a stage without shorthand throttle, extra base interfaces on HttpApiProps, an existing unrelated CfnOutput, an existing DNS alias output, a customised REST URL, the HTTP construct's url getter (rewritten, or reported when customised), and colliding or partial declarations in the same file and in sibling files. The guard tests were checked to fail against the earlier, looser guards.
  • Full plugin suite and lint pass (4392 tests); generator snapshots updated.
  • Docs site builds, with the new snippet's OptionFilter conditions validated against the guides' generator schemas.
  • Terraform e2e plan test: the Terraform smoke test's credential-free, mocked-provider terraform test now also plans the generated REST and HTTP API modules with a custom domain and asserts each publishes the custom domain URL as api_url.
  • End to end against the latest release: fresh CDK and Terraform workspaces created with @aws/nx-plugin@1.0.3, each with a tRPC REST and HTTP API, then upgraded to a locally packed build and migrated with nx migrate --run-migrations:
    • the migration updated exactly the four expected files with no nextSteps, and a re-run made no changes;
    • both workspaces build: format, lint, compile against aws-cdk-lib 2.270 and CDK synth, and checkov with 0 failed checks on Terraform;
    • in the CDK workspace, a stack using both APIs with custom domains synthesises https://rest.example.com/v1/ and the HTTP domain URL into the AppConfig runtime config, the DomainName and mapping resources, and the four DNS alias outputs (RegionalDomainName / RegionalHostedZoneId); the HTTP API's HttpApiUrl output and url getter resolve to the custom domain URL too;
    • in the Terraform workspace, an infra project using both migrated modules with custom_domain_name set, reading api_url and the DNS target outputs, passes terraform validate and a mocked-provider plan asserting both api_urls, the plan fails with a clear message when acm_certificate_arn is missing, and the migration's lines are terraform fmt clean;
    • with a sibling locals.tf declaring api_url, the HTTP module was left untouched and reported, and the REST module migrated.

Issue # (if applicable)

N/A

Checklist


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

When an API is given a custom domain, runtime config now publishes the
custom domain URL instead of the generated execute-api endpoint.

- CDK RestApi publishes https://<domainName>/[<basePath>/] when domainName
  is set.
- CDK HttpApi forwards defaultDomainMapping to the stage it creates (CDK
  rejected it while the default stage is disabled), typed with an
  IDomainName, and publishes the mapping's domain URL.
- Both output the custom domain's DNS record target (regional domain name
  and hosted zone ID).
- The Terraform REST and HTTP API modules gain optional custom_domain_name
  and acm_certificate_arn variables, the domain and mapping resources, an
  api_url local published in runtime config, and api_url / DNS target
  outputs.
- The api-custom-domain-runtime-config migration brings existing vended
  constructs and modules to the generated shape, skipping and reporting
  anything customised.
- The API guides gain a Custom domain section.
… and getter

Addresses PR review feedback:

- HttpApi computes its URL once (the custom domain's when one is mapped)
  and uses it for the <apiName>Url stack output, runtime config and the
  url getter; the migration rewrites all three.
- The e2e Terraform plan test plans the REST and HTTP API modules with a
  custom domain and asserts the published api_url.
- Terraform: a plan-time precondition requires acm_certificate_arn when
  custom_domain_name is set; the HTTP mapping references the domain name;
  api_url sits with the module's other locals.
- Sorted imports, an endpoint-neutral REST DNS output description, and
  docs noting basePath is CDK-only and TLS 1.2 is the default.
@AlexTo

AlexTo commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

@shsrams thanks for the thorough review. All addressed in ec96fce and 82e41f2:

Blocking

  1. HttpApi now computes the URL once (apiUrl: the custom domain URL when defaultDomainMapping is set, otherwise the stage URL). It's used for the <Api>Url output, runtime config and the url getter. The migration rewrites all three, checking each anchor before editing, so a customised getter leaves the file untouched and is reported. There's spec coverage for both.
  2. Sorry, that terraform test was a local check I didn't commit. It's now part of the e2e Terraform smoke test: the existing mocked-provider terraform test also plans the generated REST and HTTP modules with a custom domain and asserts each publishes it as api_url. I've corrected the description.

Suggestions: all applied.

  • A plan-time precondition requires acm_certificate_arn when custom_domain_name is set. A variable validation can't reference another variable before Terraform 1.9, and the modules allow >= 1.0.
  • The docs say basePath is CDK-only, and TLS 1.2 is "used by default".
  • The HTTP mapping uses .domain_name.
  • The imports are sorted, in the template and in what the migration inserts.
  • The REST DNS output description covers edge endpoints.
  • The comment is lowercase, and locals { api_url } sits after the module's other locals.

The translations came from scripts/translate.ts, run locally before the English changes were committed. That's why some guides were fully re-translated rather than updated incrementally. The snippet's translations were re-run after these changes (82e41f2).

@shsrams
shsrams self-requested a review October 2, 2026 19:27

@shsrams shsrams left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All comments addressed. Thanks Alex!!

@codecov-commenter

codecov-commenter commented Oct 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.13%. Comparing base (6deb83a) to head (87cd7e3).

Files with missing lines Patch % Lines
...test/api-custom-domain-runtime-config/migration.ts 96.42% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1278      +/-   ##
==========================================
+ Coverage   89.06%   89.13%   +0.06%     
==========================================
  Files         291      292       +1     
  Lines       12586    12698     +112     
  Branches     3052     3073      +21     
==========================================
+ Hits        11210    11318     +108     
- Misses        554      555       +1     
- Partials      822      825       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@shsrams
shsrams merged commit 5ddff59 into awslabs:main Oct 3, 2026
70 of 71 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants