Page updated Mar 26, 2024

Connect to existing AWS resources built with the CDK

This guide shows you how to connect a new app 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.

In this guide, you will use the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend. You will then build and integrate a Flutter app with the GraphQL API.

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.

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.

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

Congratulations! You used the AWS Amplify Data CDK construct to create a GraphQL API backend using AWS AppSync. You then connected your app 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 this demo app, 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