AWS AppSync simplifies application development by letting you create a flexible API to securely access, manipulate, and combine data from one or more data sources. AppSync is a managed service that uses GraphQL to make it easy for applications to get exactly the data they need.
With AppSync, you can build scalable applications, including those requiring real-time updates, on a range of data sources such as NoSQL data stores, relational databases, HTTP APIs, and your custom data sources with AWS Lambda. For mobile and web apps, AppSync additionally provides local data access when devices go offline, and data synchronization with customizable conflict resolution, when they are back online.
See the AWS Construct Library Module Lifecycle doc for more information about maturity levels.
See the CDK API Reference for more implementation details.
Base Level Implementation
Higher Level Implementation
expires
prop in apiKeyConfig (pr: #9122)
This is a 📊Tracking Issue
ApiKeyConfig
currently takes a string
for expires
, rather than a Duration
.
/**
* Configuration for API Key authorization in AppSync
*/
export interface ApiKeyConfig extends AuthMode {
/**
* Unique description of the API key
*/
readonly apiKeyDesc: string;
/**
* The time from creation time after which the API key expires, using RFC3339 representation.
* It must be a minimum of 1 day and a maximum of 365 days from date of creation.
* Rounded down to the nearest hour.
* @default - 7 days from creation time
*/
readonly expires?: string;
}
Unclear if Duration
's toIsoString()
is sufficient to satisfy this date format as a workaround, though this link seems to imply it is:
Duration.days(365).toIsoString()
@aws-cdk/core/lib/duration.d.ts:92
/**
* Return an ISO 8601 representation of this period
*
* @returns a string starting with 'PT' describing the period
* @see https://www.iso.org/fr/standard/70907.html
*/
toIsoString(): string;
EDIT: Though attempting to pass in expires: Duration.days(365).toIsoString(),
just ended up getting a null
in the cloudformation template, then throwing a validation error.
Workaround:
const dateXDaysInFuture = (days: number) => {
const now = new Date()
now.setHours(0, 0, 0, 0)
now.setDate(now.getDate() + days)
return now.toISOString()
}
EDIT: See also: https://github.com/aws/aws-cdk/issues/7144 / https://github.com/aws/aws-cdk/pull/7145
Implementation of an AppSync NONE
DataSource :
import { Construct } from '@aws-cdk/core'
import { BaseDataSource } from '@aws-cdk/aws-appsync'
import { BaseDataSourceProps } from '@aws-cdk/aws-appsync/lib/graphqlapi'
/**
* An AppSync datasource backed by nothing
*
* @see https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-appsync.BaseDataSource.html
* @see https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-appsync.BaseDataSourceProps.html
* @see https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-local-resolvers.html
*/
export class NoneDataSource extends BaseDataSource {
constructor(scope: Construct, id: string, props: NoneDataSourceProps) {
super(scope, id, props, {
type: 'NONE',
})
}
}
/**
* Properties for an AppSync NoneDataSource
*/
export interface NoneDataSourceProps extends BaseDataSourceProps {
readonly serviceRole?: undefined
}
Usage:
new NoneDataSource(this, 'NoneDataSource', {
api: graphqlApi,
name: 'noneDataSource',
description: 'There is no data source. This type is used when you wish to invoke a GraphQL operation without connecting to a data source, such as performing data transformation with resolvers or triggering a subscription to be invoked from a mutation.',
})
GraphQLApi
doesn't appear to expose any way to access the generated API Key when used in defaultAuthorization
/additionalAuthorizationModes
.
Workaround:
const apiKeyDesc = 'Bar'
const api = new GraphQLApi(this, 'FooApi', {
// ..snip..
additionalAuthorizationModes: [
{
apiKeyDesc,
// ..snip..
},
// ..snip..
})
const cfnApiKey = myapi.node.findChild(`${apiKeyDesc}ApiKey`) as CfnApiKey
new CfnOutput(this, 'RemoteControlApiPublicApiKey', {
value: cfnApiKey.attrApiKey,
})
@0xdevalias these are all good things to add to the list. Feel free to make new issues for each one and just link to them from here. We will use this as a staging area to work these out as we develop the constructs.
Feel free to make new issues for each one and just link to them from here.
The extra effort in filling out issue templates/jumping through those hoops tends to be far more than just adding a comment to an existing thread.
Agreed. I'm okay with using this thread to propose new additions. Separate issues are ideal for tracking implementation and this issue can serve as a checklist that anyone can look at to see what's still in progress.
I'll work on adding these.
EDIT: disregard this bit. Wasn't watching/recompiling my typescript so it was using the old value.. The notes about passing in different date types to expires
are still relevant though.
~Can't be certain just now, but the API key expiry date is seeming to be off by 72 hours:~
More Details
~Initially I used my dateXDaysInFuture
helper, but realised that will change the date on every deploy (not ideal until I wire the API key into my app's build/deploy automatically, doing it manually for now)~
~So I figured I would just statically set the value to the same one that existed currently in my template from the diff. So I set expires
to new Date(1617804000 * 1000).toISOString()
to match the existing deployed value, and then my diff looks like this:~
[~] AWS::AppSync::ApiKey Api/PublicApiKey ApiPublicApiKeyC0492E48
└─ [~] Expires
├─ [-] 1617804000
└─ [+] 1618063200
~Calculating those 2 values, it seems like they are 72 hours (3 days) different, which seems wrong to me? I would have expected them to remain the same.~
new Date(1617804000*1000).toISOString()
// "2021-04-07T14:00:00.000Z"
new Date(1618063200*1000).toISOString()
// "2021-04-10T14:00:00.000Z"
(1618063200-1617804000)/60/60/24
// 3
new Date((1617804000+(3*24*60*60))*1000).toISOString() === new Date(1618063200*1000).toISOString()
// true
Cut down code:
const dateXDaysInFuture = (days: number) => {
const now = new Date()
now.setHours(0, 0, 0, 0)
now.setDate(now.getDate() + days)
return now.toISOString()
}
const api = new GraphQLApi(this, 'Api', {
name: 'foo-api',
schemaDefinitionFile: '../foo/schema.graphql',
authorizationConfig: {
defaultAuthorization: {
userPool,
defaultAction: UserPoolDefaultAction.ALLOW,
},
additionalAuthorizationModes: [
{
apiKeyDesc: 'Public',
// TODO: fix this so that we don't get a new date on every deploy, and it doesn't stay staticly hardcoded
// expires: dateXDaysInFuture(365),
expires: new Date(1617804000 * 1000).toISOString(),
},
],
},
logConfig: {
fieldLogLevel: FieldLogLevel.ALL,
// fieldLogLevel: FieldLogLevel.ERROR,
excludeVerboseContent: true,
},
})
It would be nice if we could pass more than just a string
into the expires
field as well. Eg. Being able to pass a Date
object directly would be nice. Or even the number timestamp. Basically, anything that can be turned into a Date
(or already is one) would be ideal. Or a Duration
representing how long the key should be valid for.
~I think that~ my schema (set with schemaDefinitionFile: '../foo/schema.graphql'
) is showing as changed on every diff too, even though the file ~shouldn't have been~ hasn't changed ~(need to actually confirm this).~ It's kind of hard to tell for sure with the current diffing mechanisms, as it just shows a giant block of removed + a giant block of added. There are some nice diff libs out there that give finer grained results which could be good to use here, eg.
@0xdevalias @MrArnoldPalmer added these all to the issue list to keep track of which ones are completed. also marked 🚀 when they are complete.
@BryanPan342 i see code first schema definition in the list at the top, but nothing about improving the current diff'ing. Personally I don't need the code first schema, but a usable diff would be invaluable.
@0xdevalias apologies i only read the edit section so i didnt see that part of the comment! ill add it to the issue list and make an issue for it
Most helpful comment
Agreed. I'm okay with using this thread to propose new additions. Separate issues are ideal for tracking implementation and this issue can serve as a checklist that anyone can look at to see what's still in progress.
I'll work on adding these.