Skip to content

Commit 8c7a4ac

Browse files
authored
feat(appsync): support custom domain mappings (#19368)
fixes #18040 This adds support for custom domains with AppSync.
1 parent 49ea263 commit 8c7a4ac

File tree

4 files changed

+117
-3
lines changed

4 files changed

+117
-3
lines changed

packages/@aws-cdk/aws-appsync/README.md

+37
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,43 @@ ds.createResolver({
285285
});
286286
```
287287

288+
## Custom Domain Names
289+
290+
For many use cases you may want to associate a custom domain name with your
291+
GraphQL API. This can be done during the API creation.
292+
293+
```ts
294+
import * as acm from '@aws-cdk/aws-certificatemanager';
295+
import * as route53 from '@aws-cdk/aws-route53';
296+
297+
const myDomainName = 'api.example.com';
298+
const certificate = new acm.Certificate(this, 'cert', { domainName: myDomainName });
299+
const api = new appsync.GraphqlApi(this, 'api', {
300+
name: 'myApi',
301+
domainName: {
302+
certificate,
303+
domainName: myDomainName,
304+
},
305+
});
306+
307+
// hosted zone and route53 features
308+
declare const hostedZoneId: string;
309+
declare const zoneName = 'example.com';
310+
311+
// hosted zone for adding appsync domain
312+
const zone = route53.HostedZone.fromHostedZoneAttributes(this, `HostedZone`, {
313+
hostedZoneId,
314+
zoneName,
315+
});
316+
317+
// create a cname to the appsync domain. will map to something like xxxx.cloudfront.net
318+
new route53.CnameRecord(this, `CnameApiRecord`, {
319+
recordName: 'api',
320+
zone,
321+
domainName: myDomainName,
322+
});
323+
```
324+
288325
## Schema
289326

290327
Every GraphQL Api needs a schema to define the Api. CDK offers `appsync.Schema`

packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts

+42-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import { ICertificate } from '@aws-cdk/aws-certificatemanager';
12
import { IUserPool } from '@aws-cdk/aws-cognito';
23
import { ManagedPolicy, Role, IRole, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam';
34
import { IFunction } from '@aws-cdk/aws-lambda';
45
import { ArnFormat, CfnResource, Duration, Expiration, IResolvable, Stack } from '@aws-cdk/core';
56
import { Construct } from 'constructs';
6-
import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated';
7+
import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema, CfnDomainName, CfnDomainNameApiAssociation } from './appsync.generated';
78
import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base';
89
import { Schema } from './schema';
910
import { IIntermediateType } from './schema-base';
@@ -254,6 +255,21 @@ export interface LogConfig {
254255
readonly role?: IRole;
255256
}
256257

258+
/**
259+
* Domain name configuration for AppSync
260+
*/
261+
export interface DomainOptions {
262+
/**
263+
* The certificate to use with the domain name.
264+
*/
265+
readonly certificate: ICertificate;
266+
267+
/**
268+
* The actual domain name. For example, `api.example.com`.
269+
*/
270+
readonly domainName: string;
271+
}
272+
257273
/**
258274
* Properties for an AppSync GraphQL API
259275
*/
@@ -292,6 +308,16 @@ export interface GraphqlApiProps {
292308
* @default - false
293309
*/
294310
readonly xrayEnabled?: boolean;
311+
312+
/**
313+
* The domain name configuration for the GraphQL API
314+
*
315+
* The Route 53 hosted zone and CName DNS record must be configured in addition to this setting to
316+
* enable custom domain URL
317+
*
318+
* @default - no domain name
319+
*/
320+
readonly domainName?: DomainOptions;
295321
}
296322

297323
/**
@@ -391,7 +417,7 @@ export class GraphqlApi extends GraphqlApiBase {
391417
class Import extends GraphqlApiBase {
392418
public readonly apiId = attrs.graphqlApiId;
393419
public readonly arn = arn;
394-
constructor (s: Construct, i: string) {
420+
constructor(s: Construct, i: string) {
395421
super(s, i);
396422
}
397423
}
@@ -450,7 +476,7 @@ export class GraphqlApi extends GraphqlApiBase {
450476
const additionalModes = props.authorizationConfig?.additionalAuthorizationModes ?? [];
451477
const modes = [defaultMode, ...additionalModes];
452478

453-
this.modes = modes.map((mode) => mode.authorizationType );
479+
this.modes = modes.map((mode) => mode.authorizationType);
454480

455481
this.validateAuthorizationProps(modes);
456482

@@ -472,6 +498,19 @@ export class GraphqlApi extends GraphqlApiBase {
472498
this.schema = props.schema ?? new Schema();
473499
this.schemaResource = this.schema.bind(this);
474500

501+
if (props.domainName) {
502+
new CfnDomainName(this, 'DomainName', {
503+
domainName: props.domainName.domainName,
504+
certificateArn: props.domainName.certificate.certificateArn,
505+
description: `domain for ${this.name} at ${this.graphqlUrl}`,
506+
});
507+
508+
new CfnDomainNameApiAssociation(this, 'DomainAssociation', {
509+
domainName: props.domainName.domainName,
510+
apiId: this.apiId,
511+
});
512+
}
513+
475514
if (modes.some((mode) => mode.authorizationType === AuthorizationType.API_KEY)) {
476515
const config = modes.find((mode: AuthorizationMode) => {
477516
return mode.authorizationType === AuthorizationType.API_KEY && mode.apiKeyConfig;

packages/@aws-cdk/aws-appsync/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"jest": "^27.5.1"
9090
},
9191
"dependencies": {
92+
"@aws-cdk/aws-certificatemanager": "0.0.0",
9293
"@aws-cdk/aws-cognito": "0.0.0",
9394
"@aws-cdk/aws-dynamodb": "0.0.0",
9495
"@aws-cdk/aws-ec2": "0.0.0",
@@ -104,6 +105,7 @@
104105
},
105106
"homepage": "https://github.com/aws/aws-cdk",
106107
"peerDependencies": {
108+
"@aws-cdk/aws-certificatemanager": "0.0.0",
107109
"@aws-cdk/aws-cognito": "0.0.0",
108110
"@aws-cdk/aws-dynamodb": "0.0.0",
109111
"@aws-cdk/aws-ec2": "0.0.0",

packages/@aws-cdk/aws-appsync/test/appsync.test.ts

+36
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as path from 'path';
22
import { Template } from '@aws-cdk/assertions';
3+
import { Certificate } from '@aws-cdk/aws-certificatemanager';
34
import * as iam from '@aws-cdk/aws-iam';
45
import * as cdk from '@aws-cdk/core';
56
import * as appsync from '../lib';
@@ -155,3 +156,38 @@ test('appsync GraphqlApi should not use custom role for CW Logs when not specifi
155156
},
156157
});
157158
});
159+
160+
test('appsync GraphqlApi should be configured with custom domain when specified', () => {
161+
const domainName = 'api.example.com';
162+
// GIVEN
163+
const certificate = new Certificate(stack, 'AcmCertificate', {
164+
domainName,
165+
});
166+
167+
// WHEN
168+
new appsync.GraphqlApi(stack, 'api-custom-cw-logs-role', {
169+
authorizationConfig: {},
170+
name: 'apiWithCustomRole',
171+
schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')),
172+
domainName: {
173+
domainName,
174+
certificate,
175+
},
176+
});
177+
178+
// THEN
179+
Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DomainNameApiAssociation', {
180+
ApiId: {
181+
'Fn::GetAtt': [
182+
'apicustomcwlogsrole508EAC74',
183+
'ApiId',
184+
],
185+
},
186+
DomainName: domainName,
187+
});
188+
189+
Template.fromStack(stack).hasResourceProperties('AWS::AppSync::DomainName', {
190+
CertificateArn: { Ref: 'AcmCertificate49D3B5AF' },
191+
DomainName: domainName,
192+
});
193+
});

0 commit comments

Comments
 (0)