Building Full Stack Serverless Application With Amplify, Flutter, GraphQL, AWS CDK, and Typescript(PART 3)

I love creating and writing about the creation of software.
Search for a command to run...

I love creating and writing about the creation of software.
No comments yet. Be the first to comment.
In this blog series, we'll walk through creating an authenticated GraphQl API with AWS CDK and Typescript, then consuming that API with a beautiful Flutter Mobile Application
Complete Source Code for the GraphQL CDK API Complete Source Code for the Flutter App Here's the Last Article in a 4 part series. We'll continue from where we left off in part 3. Part 1 Part 2 Part 3 I'm Assuming you already have the following in...
Watch the complete Video on Youtube https://www.youtube.com/watch?v=kKX-8L_R2XM Prerequisites Before proceeding make sure you have these dependencies Docker Python 3.11 and above AWS CLI Full access to a foundation model. For this workshop, we'll...

Introduction In a previous workshop, Building event-driven applications with AWS sam and python we built an e-commerce ordering service using AWS SAM, API Gateway, Lambda and Python. Here's the solutions architecture. In that workshop, whenever a ne...

Github Repository https://github.com/EducloudHQ/introduction_to_gen_ai Skill up with serverless on Educloud https://www.educloud.academy/content/da99ad07-7efa-41e7-ba50-b18e6b89e10d This post is an introductory lesson to building AI-enhanced serverle...

Introduction Serverless computing is a cloud-based approach to application development that eliminates the need for provisioning and managing servers. Instead, developers focus on writing code that is triggered by events such as HTTP requests or data...

https://github.com/trey-rosius/sam_stepfunctions Hey!!! How you doing? In this post, we’ll look at how to build a workflow, using SAM as IaC, Appsync, and python. Prerequisites Install AWS Cli (https://docs.aws.amazon.com/cli/latest/userguide/cli-c...

Hello,
In the last 2 articles of this series, we looked at some basic concepts needed to understand graphql and appsync, and we also started creating our API.
PART 1
Part 2
In this article, we'll pick up from where we left off
To do so, we’ll add the following code below the GraphQL resolver definitions in lib/notes-cdk-app-stack.ts:
// lib/notes-cdk-app-stack.ts
const notesTable = new ddb.Table(this, "CDKNotesTable", {
billingMode: ddb.BillingMode.PAY_PER_REQUEST,
partitionKey: {
name: "id",
type: ddb.AttributeType.STRING,
},
});
// enable the Lambda function to access the DynamoDB table (using IAM)
notesTable.grantFullAccess(notesLambda);
// Create an environment variable that we will use in the function code
notesLambda.addEnvironment("NOTES_TABLE", notesTable.tableName);
Outputting everything
// Prints out the AppSync GraphQL endpoint to the terminal
new cdk.CfnOutput(this, "GraphQLAPIURL", {
value: api.graphqlUrl,
});
// Prints out the AppSync GraphQL API key to the terminal
new cdk.CfnOutput(this, "GraphQLAPIKey", {
value: api.apiKey || "",
});
// Prints out the stack region to the terminal
new cdk.CfnOutput(this, "Stack Region", {
value: this.region,
});
We can use the event object that is passed into the function argument to find the fieldName of the operation that is invoking the function and then map the invocation to the proper DynamoDB operation.
For example, if the GraphQL operation that triggered the function is createNote, then the fieldName will be available at event.info.fieldName.
We can also access any arguments that are passed into the GraphQL operation by accessing the event.arguments object.
With this in mind, create the following files in the lambda-fns directory: main.ts, createNote.ts, deleteNote.ts, getNoteById.ts, listNotes.ts, updateNote.ts, and Note.ts.
Next, we’ll update these files with the code needed to interact with the DynamoDB table and perform various operations.
main.ts
import createNote from "./createNote";
import deleteNote from "./deleteNote";
import getNoteById from "./getNoteById";
import listNotes from "./listNotes";
import updateNote from "./updateNote";
import Note from "./Note";
type AppSyncEvent = {
info: {
fieldName: string;
};
arguments: {
noteId: string;
note: Note;
};
};
exports.handler = async (event: AppSyncEvent) => {
switch (event.info.fieldName) {
case "getNoteById":
return await getNoteById(event.arguments.noteId);
case "createNote":
return await createNote(event.arguments.note);
case "listNotes":
return await listNotes();
case "deleteNote":
return await deleteNote(event.arguments.noteId);
case "updateNote":
return await updateNote(event.arguments.note);
default:
return null;
}
};
Note.ts
type Note = {
id: string;
title: string;
description: string;
color: number;
completed: boolean;
};
export default Note;
createNote.ts
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient();
import Note from "./Note";
async function createNote(note: Note) {
const params = {
TableName: process.env.NOTES_TABLE,
Item: note,
};
try {
await docClient.put(params).promise();
return note;
} catch (err) {
console.log("DynamoDB error: ", err);
return null;
}
}
export default createNote;
updateNote.ts
// lambda-fns/updateNote.ts
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient();
type Params = {
TableName: string | undefined;
Key: string | {};
ExpressionAttributeValues: any;
ExpressionAttributeNames: any;
UpdateExpression: string;
ReturnValues: string;
};
async function updateNote(note: any) {
let params: Params = {
TableName: process.env.NOTES_TABLE,
Key: {
id: note.id,
},
ExpressionAttributeValues: {},
ExpressionAttributeNames: {},
UpdateExpression: "",
ReturnValues: "UPDATED_NEW",
};
let prefix = "set ";
let attributes = Object.keys(note);
for (let i = 0; i < attributes.length; i++) {
let attribute = attributes[i];
if (attribute !== "id") {
params["UpdateExpression"] +=
prefix + "#" + attribute + " = :" + attribute;
params["ExpressionAttributeValues"][":" + attribute] = note[attribute];
params["ExpressionAttributeNames"]["#" + attribute] = attribute;
prefix = ", ";
}
}
console.log("params: ", params);
try {
await docClient.update(params).promise();
return note;
} catch (err) {
console.log("DynamoDB error: ", err);
return null;
}
}
export default updateNote;
getNoteById
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient();
async function getNoteById(noteId: string) {
const params = {
TableName: process.env.NOTES_TABLE,
Key: { id: noteId },
};
try {
const { Item } = await docClient.get(params).promise();
return Item;
} catch (err) {
console.log("DynamoDB error: ", err);
}
}
export default getNoteById;
listNote.ts
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient();
async function listNotes() {
const params = {
TableName: process.env.NOTES_TABLE,
};
try {
const data = await docClient.scan(params).promise();
return data.Items;
} catch (err) {
console.log("DynamoDB error: ", err);
return null;
}
}
export default listNotes;
deleteNote.ts
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient();
async function deleteNote(noteId: string) {
const params = {
TableName: process.env.NOTES_TABLE,
Key: {
id: noteId,
},
};
try {
await docClient.delete(params).promise();
return noteId;
} catch (err) {
console.log("DynamoDB error: ", err);
return null;
}
}
export default deleteNote;
npm run build && cdk deploy
Now that the updates have been deployed, visit the AppSync console and click on the API name to view the dashboard for your API.
listNotes endpoint, all we need to do is use the API KEY which grants public access.

If you try to do a mutation with the API KEY, you'll get an unauthorized errorType.

In-order to do a mutation or access private endpoints, we have to create a user in aws Cognito.

Next, click on Manage User Pools
Next, select your application, click on users and groups in the left menu and then create a new user.

You'll need the username and password in appsync.
Next, go back to appsync and select Cognito user pool as your authorization provider.
Sign In and then perform a mutation.
Here are all the endpoints.
mutation createNote {
createNote(note: {id: 5, description: "this is a description", title: "this one", color: "3213"}) {
id
title
description
color
createdOn
}
updateNote(note: {id: 5, description: "this is an updated description", title: "updated title", color: "3213"}) {
id
title
description
color
createdOn
}
deleteNote(noteId: "5")
}
query listNotes {
getNoteById(noteId: "5") {
color
createdOn
description
id
title
}
listNotes {
color
createdOn
description
id
title
}
}
GraphQL subscriptions allow you to subscribe to mutations made against a GraphQL API. In our case, we may want to subscribe to changes like when a new note is created, when a note is deleted, or when a note is updated.
type Subscription {
onCreateNote: Note @aws_subscribe(mutations: ["createNote"])
onDeleteNote: String @aws_subscribe(mutations: ["deleteNote"])
onUpdateNote: Note @aws_subscribe(mutations: ["updateNote"])
}
To test out the changes, open another window and visit the AppSync Query editor for the GraphQL API.
From here, you can test out the subscription by running a subscription in one editor and triggering a mutation in another:
If you face the issue above, solve it like this.
https://github.com/awslabs/aws-mobile-appsync-sdk-ios/issues/372#issuecomment-621418400
The next step is about developing our mobile app using the amplify library and flutter framework, to consume the API we just created.
That's all for this article Folks.
Coming up next and last is flutter with Amplify. Stay tuned
Thanks for reading through.
If you loved it, there's a high chance, someone in your clique would love it too. Show a brother some love, by spreading the word, leaving a like or comment.
See you in the next article
Happy Coding ✌🏿