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.
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.
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.
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.
1import * as cdk from 'aws-cdk-lib';2import { Construct } from 'constructs';3import {4 AmplifyData,5 AmplifyDataDefinition6} 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: String18 complete: Boolean19 }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 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.
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.
flutter create flutter_todo_app --platforms=web
Step 2: Open the newly created Flutter app by running the following commands in your 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.
1name: flutter_todo_app2description: "A new Flutter project."3publish_to: 'none' # Remove this line if you wish to publish to pub.dev4version: 1.0.0+15
6environment:7 sdk: '>=3.2.0 <4.0.0'8
9dependencies:10 flutter:11 sdk: flutter12 amplify_flutter: ^1.0.013 amplify_api: ^1.0.0 14 go_router: ^6.5.515 cupertino_icons: ^1.0.216
17dev_dependencies:18 flutter_test:19 sdk: flutter20 flutter_lints: ^2.0.021
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.
flutter pub get
Step 5: Install the @aws-amplify/cli packages by running the following command.
npm install @aws-amplify/cli
Step 6: Create a new folder and name it graphql
. Inside it, create the file schema.graphql
.
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.
1type Todo @model @auth(rules: [{ allow: public }]) {2 id: ID!3 name: String!4 description: String5 complete: Boolean6}
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
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.
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 data62 final name = _nameController.text;63 final description = _descriptionController.text;64 final complete = _isDone;65
66 if (_isCreate) {67 // Create a new todo item68 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 instead78 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 executes89 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.
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 the52 // 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 item90 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 else106 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.
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 configuration50 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.
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.
cdk destroy