Building Full Stack Serverless Application With Amplify, Flutter, GraphQL, AWS CDK, and Typescript(PART 2)
Hi there,
in PART 1 of this series, we introduced basic concepts of the underlying technologies we need to build our application.
Continuing from where we left off,
Prerequisites
AWS AccountConfigured AWS CDK on your computer.
Getting Started.
To get started, we'll be using the CDK CLI to initialize a new project. To do so, create a new empty folder and initialize a new CDK project in TypeScript:mkdir notes-cdk-app && cd notes-cdk-app
cdk init --language=typescript
Once the command has been completed successfully, you should see a bunch of files and folders would be created. The root stack resides in the lib folder in a file called notes-cdk-app-stack.ts
To create this API, we need a couple of AWS resources. In CDK, they are called constructs.
Constructs are cloud components and encapsulate everything AWS cloudformation needs to create that component.
They are the basic building blocks of CDK apps.
For this project, we will need these constructs
- Amazon Cognito (authentication)
- Amazon DynamoDB (As a database to store all application data)
- AWS AppSync (GraphQL API, real-time)
- AWS Lambda (Lambda Functions) Let's go ahead and install them now
npm install @aws-cdk/aws-appsync @aws-cdk/aws-lambda @aws-cdk/aws-dynamodb @aws-cdk/aws-cognito
Running a build
This application is created in typescript and would ultimately be converted to javascript before deploying. Therefore, we need to create a build that would do the conversion.There are 2 ways to accomplish this.
- Using Watch mode
npm run watch
that'll automatically compile to javascript upon saving the file - Using manual build by running
npm run build
each time you want to compile.
Creating the API
Navigate to thellib/notes-cdk-app-stack.ts
and open it up. That's the root of the CDK app and it's where we would be writing our app.
Let's go ahead and import all our CDK modules
import * as cdk from "@aws-cdk/core";
import * as appsync from "@aws-cdk/aws-appsync";
import * as ddb from "@aws-cdk/aws-dynamodb";
import * as lambda from "@aws-cdk/aws-lambda";
import * as cognito from "@aws-cdk/aws-cognito";
Creating Cognito User Pools and User Pool Clients
Our API provides both public and private access to its resources.For private access, we will be using Cognito to give users the possibility to create accounts in order to access the API.
Within this block of code,
export class NotesCdkAppStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
}
}
Add the following code
const userPool = new cognito.UserPool(this, "cdk-notes-user-pool", {
selfSignUpEnabled: true,
accountRecovery: cognito.AccountRecovery.PHONE_AND_EMAIL,
userVerification: {
emailStyle: cognito.VerificationEmailStyle.CODE,
},
autoVerify: {
email: true,
},
standardAttributes: {
email: {
required: true,
mutable: true,
},
},
});
const userPoolClient = new cognito.UserPoolClient(this, "UserPoolClient", {
userPool,
});
new cdk.CfnOutput(this, "UserPoolId", {
value: userPool.userPoolId,
});
new cdk.CfnOutput(this, "UserPoolClientId", {
value: userPoolClient.userPoolClientId,
});
We've defined a user pool with the following configuration.
selfSignUpEnabled
:Allow users to be able to sign up on their ownaccountRecovery
: In case a user forgot their passwords, they should be able to recover it using PHONE or EmailuserVerification{emailStyle}
: Sends user an email with a code to verify their account.autoVerify
: We would be creating some users(test only)on Cognito in the AWS console and we want them to continue using the credentials without verifying their emails.standardAttributes
: All users in a user pool have a set of standard attributes such as given names, address, email, birthdate, etc. Here, we set the standard attribute to email and make it mutable and required.
After creating the user pool, we use it to initialize a user pool client and then output both the userPoolId
and userPoolClientId
using cdk.CfnOutput
.
Creating AppSync API
The next step is to instantiate an appsync api, with the following configuration:Add the following code below the User Pool Client definition in lib/notes-cdk-app-stack.ts:
const api = new appsync.GraphqlApi(this, "Api", {
name: "cdk-notes-appsync-api",
schema: appsync.Schema.fromAsset("graphql/schema.graphql"),
authorizationConfig: {
defaultAuthorization: {
authorizationType: appsync.AuthorizationType.API_KEY,
apiKeyConfig: {
expires: cdk.Expiration.after(cdk.Duration.days(365)),
},
},
additionalAuthorizationModes: [
{
authorizationType: appsync.AuthorizationType.USER_POOL,
userPoolConfig: {
userPool,
},
},
],
},
xrayEnabled: true,
});
name
: Defines the name of the AppSync APIschema
: Specifies the location of the GraphQL schemaauthorizationConfig
: This allows you to define the default authorization mode its configuration, as well as (optional) additional authorization modesadditionalAuthorizationModes
: This allows you to define more authorization modes for your API.This is where we use the user pool we created above -xrayEnabled
: Enables xray debugging.
Let's define a graphQL schema. It's located in a file called schema.graphql
in a folder called graphql
.
type Note @aws_api_key @aws_cognito_user_pools {
id: ID!
title: String!
description: String!
color: String!
createdOn: AWSTimestamp
}
input NoteInput {
id: ID!
title: String!
description: String!
color: String!
createdOn: AWSTimestamp
}
input UpdateNoteInput {
id: ID!
title: String!
description: String!
color: String!
updatedOn: AWSTimestamp
}
type Query {
getNoteById(noteId: String!): Note @aws_api_key @aws_cognito_user_pools
listNotes: [Note] @aws_api_key @aws_cognito_user_pools
}
type Mutation {
createNote(note: NoteInput!): Note @aws_cognito_user_pools
updateNote(note: UpdateNoteInput!): Note @aws_cognito_user_pools
deleteNote(noteId: String!): String @aws_cognito_user_pools
}
type Subscription {
onCreateNote: Note @aws_subscribe(mutations: ["createNote"])
onDeleteNote: String @aws_subscribe(mutations: ["deleteNote"])
onUpdateNote: Note @aws_subscribe(mutations: ["updateNote"])
}
It contains 2 queries, 3 mutations, and 3 subscriptions.
It also has appsync directives to provide public and private access to each endpoint.
For example, the createNote
endpoint can only be accessed by signed In users, while the listNotes
endpoint can be accessed by both signedIn
and non SignedIn
users.
Adding a Lambda Source
Now that we’ve created the API, we need a way to connect the GraphQL operations (createNote, updateNote, listNotes, etc..) to a data source. We will be doing this by mapping the operations into a Lambda function that will be interacting with a DynamoDB table.To build out this functionality, we’ll need to create the Lambda function and then add it as a data source to the AppSync API.
Add the following code below the API definition in lib/notes-cdk-app-stack.ts:
const notesLambda = new lambda.Function(this, "AppSyncNotesHandler", {
runtime: lambda.Runtime.NODEJS_12_X,
handler: "main.handler",
code: lambda.Code.fromAsset("lambda-fns"),
memorySize: 1024,
});
// Set the new Lambda function as a data source for the AppSync API
const lambdaDs = api.addLambdaDataSource("lambdaDatasource", notesLambda);
Attaching the GraphQL resolvers
Now that the Lambda DataSource has been created, we need to enable the Resolvers for the GraphQL operations to interact with the data source.To do so, we can add the following code below the Lambda data source definition:
lambdaDs.createResolver({
typeName: "Query",
fieldName: "getNoteById",
});
lambdaDs.createResolver({
typeName: "Query",
fieldName: "listNotes",
});
lambdaDs.createResolver({
typeName: "Mutation",
fieldName: "createNote",
});
lambdaDs.createResolver({
typeName: "Mutation",
fieldName: "deleteNote",
});
lambdaDs.createResolver({
typeName: "Mutation",
fieldName: "updateNote",
});
That's all for this post.
In the next post, we'll look into adding our DynamoDB Table.
If you've read this far, I appreciate you.
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 ✌🏿