Page updated Feb 23, 2024

Connect to existing AWS resources built with the CDK

This guide shows you how to connect new mobile and web apps to AWS resources you've already created using the AWS Cloud Development Kit (AWS CDK). The AWS CDK is an open-source software development framework for defining cloud infrastructure as code with modern programming languages. This infrastructure is then deployed through AWS CloudFormation.

The guide has three sections to help you build and connect a fullstack app:

  1. Section one provides instructions for using the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend.
  2. Section two explains how to create and connect a React web app to the GraphQL API. Follow this if you are using React to build a web frontend.
  3. Section three shows how to build and integrate a Flutter app with the GraphQL API. Follow this if you are using Flutter to build a cross-platform frontend.

The recommended approach is to complete section one to build the backend, then optionally complete section two (React) or three (Flutter), or both, to connect frontends to the GraphQL API. Once you're finished experimenting with these demo apps, we recommend visiting the "Clean up resources" section to delete the backend resources to avoid incurring unexpected costs.

Before you begin, you will need:

  • An AWS account: If you don't already have an account, follow the Setting Up Your AWS Environment tutorial for a quick overview.
  • The Amplify CLI installed and configured.
  • A text editor. For this guide, we will use VS Code, but you can use your preferred IDE.
  • Flutter and its command-line tools installed and configured (if completing the third part of this guide).

Build a GraphQL API using the Amplify Data CDK construct

The CDK provides a simple way to define cloud infrastructure in code. In this section, we will use the CDK to build out the backend resources for our application.

Step 1: Create a folder for the CDK app by running the following command in your terminal.

Terminal
mkdir cdk-backend

Step 2: Navigate to the cdk-backend folder and create a new CDK project by running the cdk init command and specifying your preferred language.

Terminal
1cd cdk-backend
2cdk init --language typescript

Step 3: Open the newly created CDK project using VS Code, or your preferred IDE.

Step 4: In your terminal, navigate to the cdk_backend root folder, and install the AWS Amplify Data package by running the following command.

Terminal
npm install @aws-amplify/data-construct

Step 5: Update the cdk_backend/lib/cdk-backend-stack.ts file as shown in the following code to use the AmplifyData construct to create an AWS AppSync API.

lib/cdk-backend-stack.ts
1import * as cdk from 'aws-cdk-lib';
2import { Construct } from 'constructs';
3import {
4 AmplifyData,
5 AmplifyDataDefinition
6} from '@aws-amplify/data-construct';
7
8export class CdkBackendStack extends cdk.Stack {
9 constructor(scope: Construct, id: string, props?: cdk.StackProps) {
10 super(scope, id, props);
11
12 new AmplifyData(this, 'AmplifyCdkData', {
13 definition: AmplifyDataDefinition.fromString(/* GraphQL */ `
14 type Todo @model @auth(rules: [{ allow: public }]) {
15 id: ID!
16 name: String!
17 description: String
18 complete: Boolean
19 }
20 `),
21 authorizationModes: {
22 defaultAuthorizationMode: 'API_KEY',
23 apiKeyConfig: {
24 expires: cdk.Duration.days(30)
25 }
26 }
27 });
28 }
29}

Step 6: Deploy the CDK stacks by running the following command.

cdk deploy

Step 7: The CDK will prepare the resources for deployment and will display the following prompt. Enter Y and press Enter.

The CDK preparing for deployment.

The CDK will deploy the stacks and display the following confirmation. Note the details of the deployed API; we’re going to use them in the next section.

CDK deploying the stacks.

Now that you have built the backend API with the CDK, you can connect a frontend. Refer to section two to connect a React app or section three to connect a Flutter app, or complete both to connect frontends in each framework.

Build a React app and connect to the GraphQL API

In this section, we will connect a React web app to our existing GraphQL API. First, we will create a new React project and install the necessary Amplify packages. Next, we will use the Amplify CLI to generate GraphQL code matching our API structure. Then, we will add React components to perform queries and mutations to manage to-do items in our API. After that, we will configure the Amplify library with details of our backend API. Finally, we will run the application to demonstrate full CRUD functionality with our existing API.

Step 1: Create a React app by running the following command in your terminal.

Terminal
npx create-react-app react-amplify-connect

Step 2: Open the newly created React app using VS Code, or your preferred IDE.

Step 3: Install the aws-amplify, @aws-amplify/ui-react, and @aws-amplify/cli packages by running the following commands.

Terminal
npm install aws-amplify @aws-amplify/ui-react @aws-amplify/cli

Step 4: Use the awsAppsyncApiId and awsAppsyncRegion values of the CDK stack you created previously to generate the GraphQL client helper code by running the following command.

awsAppsyncApiId and awsAppsyncRegion values highlighted within the outputs of the CDK stack.
npx @aws-amplify/cli codegen add --apiId <aws-appsync-api-id> --region <aws-appsync-region>

Step 5: Accept the default values for the prompts.

Terminal
? Choose the type of app that you're building javascript
? What javascript framework are you using react
✔ Getting API details
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
✔ Downloaded the schema
✔ Generated GraphQL operations successfully and saved at src/graphql

The Amplify CLI will create the GraphQL client helper code inside the src/graphql folder.

mutations.js, queries.js, and subscriptions.js within the graphql folder.

Step 6: Update the App.js file with the following code to create a form with a button to create to-dos, as well as a way to fetch and render the to-do list.

src/App.js
1import { Amplify} from 'aws-amplify'
2import '@aws-amplify/ui-react/styles.css';
3import { useEffect, useState } from 'react';
4import { generateClient } from 'aws-amplify/api';
5import { createTodo } from './graphql/mutations';
6import { listTodos } from './graphql/queries';
7
8Amplify.configure({
9 API: {
10 GraphQL: {
11 endpoint: '<your-graphql-endpoint>',
12 region: '<your-aws-region>',
13 defaultAuthMode: 'apiKey',
14 apiKey: '<your-api-key>'
15 }
16 }
17});
18
19const initialState = { name: '', description: '' };
20const client = generateClient();
21
22const App = () => {
23 const [formState, setFormState] = useState(initialState);
24 const [todos, setTodos] = useState([]);
25
26 useEffect(() => {
27 fetchTodos();
28 }, []);
29
30 function setInput(key, value) {
31 setFormState({ ...formState, [key]: value });
32 }
33
34 async function fetchTodos() {
35 try {
36 const todoData = await client.graphql({
37 query: listTodos
38 });
39 const todos = todoData.data.listTodos.items;
40 setTodos(todos);
41 } catch (err) {
42 console.log('error fetching todos');
43 }
44 }
45
46 async function addTodo() {
47 try {
48 if (!formState.name || !formState.description) return;
49 const todo = { ...formState };
50 setTodos([...todos, todo]);
51 setFormState(initialState);
52 await client.graphql({
53 query: createTodo,
54 variables: {
55 input: todo
56 }
57 });
58 } catch (err) {
59 console.log('error creating todo:', err);
60 }
61 }
62
63 return (
64 <div style={styles.container}>
65 <h2>Amplify Todos</h2>
66 <input
67 onChange={(event) => setInput('name', event.target.value)}
68 style={styles.input}
69 value={formState.name}
70 placeholder="Name"
71 />
72 <input
73 onChange={(event) => setInput('description', event.target.value)}
74 style={styles.input}
75 value={formState.description}
76 placeholder="Description"
77 />
78 <button style={styles.button} onClick={addTodo}>
79 Create Todo
80 </button>
81 {todos.map((todo, index) => (
82 <div key={todo.id ? todo.id : index} style={styles.todo}>
83 <p style={styles.todoName}>{todo.name}</p>
84 <p style={styles.todoDescription}>{todo.description}</p>
85 </div>
86 ))}
87 </div>
88 );
89};
90
91const styles = {
92 container: {
93 width: 400,
94 margin: '0 auto',
95 display: 'flex',
96 flexDirection: 'column',
97 justifyContent: 'center',
98 padding: 20
99 },
100 todo: { marginBottom: 15 },
101 input: {
102 border: 'none',
103 backgroundColor: '#ddd',
104 marginBottom: 10,
105 padding: 8,
106 fontSize: 18
107 },
108 todoName: { fontSize: 20, fontWeight: 'bold' },
109 todoDescription: { marginBottom: 0 },
110 button: {
111 backgroundColor: 'black',
112 color: 'white',
113 outline: 'none',
114 fontSize: 18,
115 padding: '12px 0px'
116 }
117};
118
119export default App;

Step 7: Run the app using the following command.

Terminal
npm start

Step 8: Use the form to create a few to-do items.

In this section, we generated GraphQL code, created React components, configured Amplify, and connected the app to the API. This enabled full CRUD functionality with our backend through queries and mutations.

Conclusion - CDK and React

Congratulations on successfully creating a new React app and linking it to a preexisting GraphQL API built with AWS CDK. The next optional section will guide you through doing the same for a Flutter mobile app. Alternatively, you can skip ahead to the "Clean up resources" section.

Build a Flutter app and connect to the GraphQL API

In this section, we will connect a Flutter mobile app to the GraphQL API we created with the CDK. First, we will initialize a Flutter project, define models matching our schema, and use Amplify to integrate CRUD operations. Then we will add UI pages to manage to-do items with queries and mutations. Finally, we will configure Amplify with our backend details and run the app to demonstrate full functionality with our existing API.

Step 1: Create a Flutter app by running the following command in your terminal.

Terminal
flutter create flutter_todo_app --platforms=web

Step 2: Open the newly created Flutter app by running the following commands in your terminal.

Terminal
1cd flutter_todo_app
2code . -r

Step 3: Update the pubspec.yaml file in the app’s root directory to add the required dependencies, as shown in the following code.

pubspec.yaml
1name: flutter_todo_app
2description: "A new Flutter project."
3publish_to: 'none' # Remove this line if you wish to publish to pub.dev
4version: 1.0.0+1
5
6environment:
7 sdk: '>=3.2.0 <4.0.0'
8
9dependencies:
10 flutter:
11 sdk: flutter
12 amplify_flutter: ^1.0.0
13 amplify_api: ^1.0.0
14 go_router: ^6.5.5
15 cupertino_icons: ^1.0.2
16
17dev_dependencies:
18 flutter_test:
19 sdk: flutter
20 flutter_lints: ^2.0.0
21
22flutter:
23 uses-material-design: true

Step 4: Run the following command in your terminal to install the dependencies you added to the pubspec.yaml file.

Terminal
flutter pub get

Step 5: Install the @aws-amplify/cli packages by running the following command.

Terminal
npm install @aws-amplify/cli

Step 6: Create a new folder and name it graphql. Inside it, create the file schema.graphql.

The schema.graphql file inside the graphql folder.

Step 7: Update the file schema.graphql, as shown in the following example, to define the Todo data model, similar to what you used for the CDK app.

schema.graphql
1type Todo @model @auth(rules: [{ allow: public }]) {
2 id: ID!
3 name: String!
4 description: String
5 complete: Boolean
6}

Step 8: Run the following command to generate the GraphQL client helper models inside the lib/models folder.

npx @aws-amplify/cli codegen models --model-schema ./graphql --target flutter --output-dir ./lib/models
The ModelProvider.dart and Todo.dart files within the models folder.

Step 9: Create the file todo_item_page.dart inside the lib folder and update it with the following code to present a form to the user for creating a to-do item. Once submitted, the form will initiate a GraphQL mutation to add or modify the item in the database.

todo_item_page.dart
1import 'package:amplify_api/amplify_api.dart';
2import 'package:amplify_flutter/amplify_flutter.dart';
3import 'package:flutter/material.dart';
4import 'package:go_router/go_router.dart';
5
6import '../models/ModelProvider.dart';
7
8class ToDoItemPage extends StatefulWidget {
9 const ToDoItemPage({
10 required this.todoItem,
11 super.key,
12 });
13
14 final Todo? todoItem;
15
16
17 State<ToDoItemPage> createState() => _ToDoItemPageState();
18}
19
20class _ToDoItemPageState extends State<ToDoItemPage> {
21 final _formKey = GlobalKey<FormState>();
22 final TextEditingController _nameController = TextEditingController();
23 final TextEditingController _descriptionController = TextEditingController();
24
25 late final String _nameText;
26 late bool _isDone;
27
28 bool get _isCreate => _todoItem == null;
29 Todo? get _todoItem => widget.todoItem;
30
31
32 void initState() {
33 super.initState();
34
35 final todoItem = _todoItem;
36 if (todoItem != null) {
37 _nameController.text = todoItem.name;
38 _descriptionController.text = todoItem.description ?? '';
39
40 _nameText = 'Update to-do Item';
41 _isDone = todoItem.complete ?? false;
42 } else {
43 _nameText = 'Create to-do Item';
44 _isDone = false;
45 }
46 }
47
48
49 void dispose() {
50 _nameController.dispose();
51 _descriptionController.dispose();
52
53 super.dispose();
54 }
55
56 Future<void> submitForm() async {
57 if (!_formKey.currentState!.validate()) {
58 return;
59 }
60
61 // If the form is valid, submit the data
62 final name = _nameController.text;
63 final description = _descriptionController.text;
64 final complete = _isDone;
65
66 if (_isCreate) {
67 // Create a new todo item
68 final newEntry = Todo(
69 name: name,
70 description: description.isNotEmpty ? description : null,
71 complete: complete,
72 );
73 final request = ModelMutations.create(newEntry);
74 final response = await Amplify.API.mutate(request: request).response;
75 safePrint('Create result: $response');
76 } else {
77 // Update todoItem instead
78 final updateToDoItem = _todoItem!.copyWith(
79 name: name,
80 description: description.isNotEmpty ? description : null,
81 complete: complete,
82 );
83 final request = ModelMutations.update(updateToDoItem);
84 final response = await Amplify.API.mutate(request: request).response;
85 safePrint('Update result: $response');
86 }
87
88 // Navigate back to homepage after create/update executes
89 if (mounted) {
90 context.pop();
91 }
92 }
93
94
95 Widget build(BuildContext context) {
96 return Scaffold(
97 appBar: AppBar(
98 title: Text(_nameText),
99 ),
100 body: Align(
101 alignment: Alignment.topCenter,
102 child: ConstrainedBox(
103 constraints: const BoxConstraints(maxWidth: 800),
104 child: Padding(
105 padding: const EdgeInsets.all(16),
106 child: SingleChildScrollView(
107 child: Form(
108 key: _formKey,
109 child: Column(
110 crossAxisAlignment: CrossAxisAlignment.start,
111 children: [
112 TextFormField(
113 controller: _nameController,
114 decoration: const InputDecoration(
115 labelText: 'Name (required)',
116 ),
117 validator: (value) {
118 if (value == null || value.isEmpty) {
119 return 'Please enter a name';
120 }
121 return null;
122 },
123 ),
124 TextFormField(
125 controller: _descriptionController,
126 decoration: const InputDecoration(
127 labelText: 'Description',
128 ),
129 ),
130 SwitchListTile(
131 title: const Text('Done'),
132 value: _isDone,
133 onChanged: (bool value) {
134 setState(() {
135 _isDone = value;
136 });
137 },
138 secondary: const Icon(Icons.done_all_outlined),
139 ),
140 const SizedBox(height: 20),
141 ElevatedButton(
142 onPressed: submitForm,
143 child: Text(_nameText),
144 ),
145 ],
146 ),
147 ),
148 ),
149 ),
150 ),
151 ),
152 );
153 }
154}

Step 10: Create the file home_page.dart.dart inside the lib folder and update it with the following code. This page will use a GraphQL query to retrieve the list of to-do items and display them in a ListView widget. The page will also allow the user to delete a to-do item by using a GraphQL mutation.

home_page.dart.dart
1import 'package:amplify_api/amplify_api.dart';
2import 'package:amplify_flutter/amplify_flutter.dart';
3import 'package:flutter/material.dart';
4import 'package:go_router/go_router.dart';
5
6import '../models/ModelProvider.dart';
7
8class HomePage extends StatefulWidget {
9 const HomePage({super.key});
10
11
12 State<HomePage> createState() => _HomePageState();
13}
14
15class _HomePageState extends State<HomePage> {
16 var _todoItems = <Todo>[];
17
18
19 void initState() {
20 super.initState();
21 _refreshTodoItems();
22 }
23
24 Future<void> _refreshTodoItems() async {
25 try {
26 final request = ModelQueries.list(Todo.classType);
27 final response = await Amplify.API.query(request: request).response;
28
29 final todos = response.data?.items;
30 if (response.hasErrors) {
31 safePrint('errors: ${response.errors}');
32 return;
33 }
34 setState(() {
35 _todoItems = todos!.whereType<Todo>().toList();
36 });
37 } on ApiException catch (e) {
38 safePrint('Query failed: $e');
39 }
40 }
41
42 Future<void> _deleteToDoItem(Todo todoItem) async {
43 final request = ModelMutations.delete<Todo>(todoItem);
44 final response = await Amplify.API.mutate(request: request).response;
45 safePrint('Delete response: $response');
46 await _refreshTodoItems();
47 }
48
49 Future<void> _openToDoItem({Todo? todoItem}) async {
50 await context.pushNamed('manage', extra: todoItem);
51 // Refresh the entries when returning from the
52 // todo item screen.
53 await _refreshTodoItems();
54 }
55
56 Widget _buildRow({
57 required String name,
58 required String description,
59 required bool isDone,
60 TextStyle? style,
61 }) {
62 return Row(
63 children: [
64 Expanded(
65 child: Text(
66 name,
67 textAlign: TextAlign.center,
68 style: style,
69 ),
70 ),
71 Expanded(
72 child: Text(
73 description,
74 textAlign: TextAlign.center,
75 style: style,
76 ),
77 ),
78 Expanded(
79 child: isDone ? const Icon(Icons.done) : const SizedBox(),
80 ),
81 ],
82 );
83 }
84
85
86 Widget build(BuildContext context) {
87 return Scaffold(
88 floatingActionButton: FloatingActionButton(
89 // Navigate to the page to create new todo item
90 onPressed: _openToDoItem,
91 child: const Icon(Icons.add),
92 ),
93 appBar: AppBar(
94 title: const Text('To-Do List'),
95 ),
96 body: Center(
97 child: Padding(
98 padding: const EdgeInsets.only(top: 25),
99 child: RefreshIndicator(
100 onRefresh: _refreshTodoItems,
101 child: Column(
102 children: [
103 if (_todoItems.isEmpty)
104 const Text('Use the \u002b sign to add new to-do items')
105 else
106 const SizedBox(height: 30),
107 _buildRow(
108 name: 'Name',
109 description: 'Description',
110 isDone: false,
111 style: Theme.of(context).textTheme.titleMedium,
112 ),
113 const Divider(),
114 Expanded(
115 child: ListView.builder(
116 itemCount: _todoItems.length,
117 itemBuilder: (context, index) {
118 final todoItem = _todoItems[index];
119 return Dismissible(
120 key: ValueKey(todoItem),
121 background: const ColoredBox(
122 color: Colors.red,
123 child: Padding(
124 padding: EdgeInsets.only(right: 10),
125 child: Align(
126 alignment: Alignment.centerRight,
127 child: Icon(Icons.delete, color: Colors.white),
128 ),
129 ),
130 ),
131 onDismissed: (_) => _deleteToDoItem(todoItem),
132 child: ListTile(
133 onTap: () => _openToDoItem(
134 todoItem: todoItem,
135 ),
136 title: _buildRow(
137 name: todoItem.name,
138 description: todoItem.description ?? '',
139 isDone: todoItem.complete ?? false,
140 ),
141 ),
142 );
143 },
144 ),
145 ),
146 ],
147 ),
148 ),
149 ),
150 ),
151 );
152 }
153}

Step 11: Update main.dart to configure Amplify using the details of the GraphQL API you created using the CDK app in the previous section.

main.dart
1import 'package:amplify_api/amplify_api.dart';
2
3import 'package:amplify_flutter/amplify_flutter.dart';
4import 'package:flutter/material.dart';
5import 'package:go_router/go_router.dart';
6
7import 'models/ModelProvider.dart';
8import 'home_page.dart';
9import 'todo_item_page.dart';
10
11Future<void> main() async {
12 WidgetsFlutterBinding.ensureInitialized();
13 await _configureAmplify();
14 runApp(const MyApp());
15}
16
17Future<void> _configureAmplify() async {
18 try {
19 final api = AmplifyAPI(modelProvider: ModelProvider.instance);
20
21 await Amplify.addPlugins([api]);
22 const amplifyconfig = '''{
23 "api": {
24 "plugins": {
25 "awsAPIPlugin": {
26 "flutter_todo_app": {
27 "endpointType": "GraphQL",
28 "endpoint": "<The-GraphQL-Endpoint>",
29 "region": "<The-Region>",
30 "authorizationType": "API_KEY",
31 "apiKey": "<The-API-KEY-Value>"
32 }
33 }
34 }
35 }
36}''';
37
38 await Amplify.configure(amplifyconfig);
39
40 safePrint('Successfully configured');
41 } on Exception catch (e) {
42 safePrint('Error configuring Amplify: $e');
43 }
44}
45
46class MyApp extends StatelessWidget {
47 const MyApp({super.key});
48
49 // GoRouter configuration
50 static final _router = GoRouter(
51 routes: [
52 GoRoute(
53 path: '/',
54 builder: (context, state) => const HomePage(),
55 ),
56 GoRoute(
57 path: '/manage-todo-item',
58 name: 'manage',
59 builder: (context, state) => ToDoItemPage(
60 todoItem: state.extra as Todo?,
61 ),
62 ),
63 ],
64 );
65
66
67 Widget build(BuildContext context) {
68 return MaterialApp.router(
69 routerConfig: _router,
70 debugShowCheckedModeBanner: false,
71 builder: (context, child) {
72 return child!;
73 },
74 );
75 }
76}

Step 12: Run the app in the Chrome browser using the following command.

Terminal
flutter run -d chrome

Conclusion - CDK and Flutter

Congratulations! You used the AWS Amplify Data CDK construct to create a GraphQL API backend using AWS AppSync. You then connected either a Flutter app, a React app, or both to that API using the Amplify libraries. If you have any feedback, leave a GitHub issue or join our Discord Community!

Clean up resources

Once you're finished experimenting with these demo apps, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the CDK app created above.

Terminal
cdk destroy