Custom resources
With Amplify Gen 2, you can add custom AWS resources to an Amplify app using the AWS Cloud Development Kit (AWS CDK), which is installed by default as part of the create-amplify
workflow. The AWS CDK is an open source software development framework that defines your cloud application resources using familiar programming languages, such as TypeScript.
The AWS CDK can be used within an Amplify app to add custom resources and configurations beyond what Amplify supports out of the box. For example, a developer could use CDK to hook up a Redis cache, implement custom security rules, deploy containers on AWS Fargate, or use any other AWS service.
The infrastructure defined through the AWS CDK code is deployed along with the Amplify app backend. This provides the simplicity of Amplify combined with the flexibility of CDK for situations where you need more customization.
With the Amplify code-first DX, you can add existing or custom CDK constructs to the backend of your Amplify app.
Adding an existing CDK construct
The AWS CDK comes with many existing constructs that can be directly added to your Amplify backend. For example, to add an Amazon Simple Queue Service (Amazon SQS) queue and an Amazon Simple Notification Service (Amazon SNS) topic to your backend, you can add the following to your amplify/backend.ts
file.
1import * as sns from 'aws-cdk-lib/aws-sns';2import * as sqs from 'aws-cdk-lib/aws-sqs';3import { defineBackend } from '@aws-amplify/backend';4import { auth } from './auth/resource';5import { data } from './data/resource';6
7const backend = defineBackend({8 auth,9 data10});11
12const customResourceStack = backend.createStack('MyCustomResources');13
14new sqs.Queue(customResourceStack, 'CustomQueue');15new sns.Topic(customResourceStack, 'CustomTopic');
Note the use of backend.createStack()
. This method instructs the backend to create a new CloudFormation Stack for your custom resources to live in. You can create multiple custom stacks and you can place multiple resources in any given stack.
Defining a CDK construct
As shown above, you can use the existing AWS CDK constructs directly in an Amplify backend. However, you may find yourself repeating some patterns of common constructs. Custom constructs allow you to encapsulate common patterns into reusable components. This helps you implement best practices, accelerate development, and maintain consistency across applications.
A common use case is creating a custom notification construct that combines a Lambda function with Amazon SNS and Amazon Simple Email Service (Amazon SES).
This AWS CDK construct implements a decoupled notification system using Amazon SNS and Lambda. It allows publishing notification messages to an SNS topic from one Lambda function, and processing those messages asynchronously using a separate Lambda subscribed to the topic.
The key components are:
- An Amazon SNS topic to receive notification messages
- A Lambda function to publish messages to the Amazon SNS topic
- A second Lambda subscribed to the topic that processes the messages and sends emails through Amazon SES
The publisher Lambda allows publishing a message containing the email subject, body text, and recipient address. The emailer Lambda retrieves messages from the SNS topic and handles sending the actual emails.
The CustomNotifications
custom CDK construct can be defined as follows:
1import * as url from 'node:url';2import { Runtime } from 'aws-cdk-lib/aws-lambda';3import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';4import * as sns from 'aws-cdk-lib/aws-sns';5import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';6import { Construct } from 'constructs';7
8// message to publish9export type Message = {10 subject: string;11 body: string;12 recipient: string;13};14
15type CustomNotificationsProps = {16 /**17 * The source email address to use for sending emails18 */19 sourceAddress: string;20};21
22export class CustomNotifications extends Construct {23 public readonly topic: sns.Topic;24 constructor(scope: Construct, id: string, props: CustomNotificationsProps) {25 super(scope, id);26
27 const { sourceAddress } = props;28
29 // Create SNS topic30 this.topic = new sns.Topic(this, 'NotificationTopic');31
32 // Create Lambda to publish messages to SNS topic33 const publisher = new lambda.NodejsFunction(this, 'Publisher', {34 entry: url.fileURLToPath(new URL('publisher.ts', import.meta.url)),35 environment: {36 SNS_TOPIC_ARN: this.topic.topicArn37 },38 runtime: Runtime.NODEJS_18_X39 });40
41 // Create Lambda to process messages from SNS topic42 const emailer = new lambda.NodejsFunction(this, 'Emailer', {43 entry: url.fileURLToPath(new URL('emailer.ts', import.meta.url)),44 environment: {45 SOURCE_ADDRESS: sourceAddress46 },47 runtime: Runtime.NODEJS_18_X48 });49
50 // Subscribe emailer Lambda to SNS topic51 this.topic.addSubscription(new subscriptions.LambdaSubscription(emailer));52
53 // Allow publisher to publish to SNS topic54 this.topic.grantPublish(publisher);55 }56}
The Lambda function code for the Publisher
is:
1// amplify/custom/CustomNotifications/publisher.ts2import { PublishCommand, SNSClient } from '@aws-sdk/client-sns';3import type { Handler } from 'aws-lambda';4import type { Message } from './resource';5
6const client = new SNSClient({ region: process.env.AWS_REGION });7
8// define the handler that will publish messages to the SNS Topic9export const handler: Handler<Message, void> = async (event) => {10 const { subject, body, recipient } = event;11 const command = new PublishCommand({12 TopicArn: process.env.TOPIC_ARN,13 Message: JSON.stringify({14 subject,15 body,16 recipient17 })18 });19 try {20 const response = await client.send(command);21 console.log('published', response);22 } catch (error) {23 console.log('failed to publish message', error);24 throw new Error('Failed to publish message', { cause: error });25 }26};
The Lambda function code for the Emailer
is:
1// amplify/custom/CustomNotifications/emailer.ts2import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';3import type { SNSHandler } from 'aws-lambda';4import type { Message } from './resource';5
6const sesClient = new SESClient({ region: process.env.AWS_REGION });7
8// define the handler to process messages from the SNS topic and send via SES9export const handler: SNSHandler = async (event) => {10 for (const record of event.Records) {11 const message: Message = JSON.parse(record.Sns.Message);12
13 // send the message via email14 await sendEmail(message);15 }16};17
18const sendEmail = async (message: Message) => {19 const { recipient, subject, body } = message;20
21 const command = new SendEmailCommand({22 Source: process.env.SOURCE_ADDRESS,23 Destination: {24 ToAddresses: [recipient]25 },26 Message: {27 Body: {28 Text: { Data: body }29 },30 Subject: { Data: subject }31 }32 });33
34 try {35 const result = await sesClient.send(command);36 console.log(`Email sent to ${recipient}: ${result.MessageId}`);37 } catch (error) {38 console.error(`Error sending email to ${recipient}: ${error}`);39 throw new Error(`Failed to send email to ${recipient}`, { cause: error });40 }41};
The CustomNotifications
CDK construct can then be added to the Amplify backend
one or more times, with different properties for each instance.
1// amplify/backend.ts2import { defineBackend } from '@aws-amplify/backend';3import { auth } from './auth/resource';4import { data } from './data/resource';5import { CustomNotifications } from './custom/CustomNotifications/resource';6
7const backend = defineBackend({8 auth,9 data10});11
12const customNotifications = new CustomNotifications(13 backend.createStack('CustomNotifications'),14 'CustomNotifications',15 { sourceAddress: 'sender@example.com' }16);17
18backend.addOutput({19 custom: {20 topicArn: customNotifications.topic.topicArn,21 topicName: customNotifications.topic.topicName,22 },23});
Community CDK resources
The Construct Hub is a community-driven catalog of reusable infrastructure components. It is a place for developers to discover and share reusable patterns for AWS CDK, maintained by AWS.
In addition, the example projects using the AWS CDK repository contains a number of examples of reusable CDK constructs.
You can use these resources to create custom CDK constructs that can be used in your Amplify app.