Build A Serverless Blog Platform With AWS CDK Part 1
Uriel Bitton
AWS Cloud Engineer | The DynamoDB guy | AWS Certified & AWS Community Builder | I help you build scalable DynamoDB databases ????
?? Hello there! Welcome to The Serverless Spotlight!
In this week's Serverless Challenge edition, we're going to be building a blog platform that will enable users to write and view blog posts.
We'll break this challenge into two parts:
Part 1 (this week): We'll be using the AWS CDK (Cloud Development Kit) to quickly create the resources we need for the backend, such as Lambda functions and DynamoDB tables.
Part 2 (next week): For the frontend we'll use React with react-draft-wysiwyg - a simple rich text editor - and communicate with our backend to write and manage the blog posts.
Overview
What we are aiming to build is a web app that will allow users to write and publish blog posts and for others to read and interact with them. The features we will build are the following:
Architecture
A user will use react-draft editor to write a blog post. The post data is sent to a Lambda function URL via an http request - this invokes the Lambda function which writes the data to DynamoDB.
Later, users can read and interact with blog posts by visiting the website at which point we will serve blog posts from DynamoDB to the blog page.
Backend Implementation
To create the necessary resources - Lambda and DynamoDB - for our backend we'll use the AWS CDK to streamline this process.
The first pre-requisite we need to install is the AWS CLI and configure it with our AWS account. To do this follow this guide.
With the AWS CLI installed, let's initialize a new project in VSCode. In the integrated terminal run the following commands:
mkdir cdk-blog-app
cd cdk-blog-app
The first thing we need to do is install the AWS CDK.
Write the command:
sudo npm install -g aws-cdk
Let's now create a new CDK project (I'm using TypeScript):
cdk init app --language typescript
Let's install the necessary CDK libraries:
npm install @aws-cdk/aws-lambda @aws-cdk/aws-dynamodb @aws-cdk/aws-lambda-event-sources aws-sdk
AWS CDK
We'll write out the code to define our CDK stack.
In the file lib/cdk-blog-app-stack.ts, copy the following code:
//cdk-blog-app-stack.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
export class BlogAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create DynamoDB table
const table = new dynamodb.Table(this, "BlogPosts", {
partitionKey: { name: "postID", type: dynamodb.AttributeType.STRING },
sortKey: { name: "postDateCreated", type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
});
// Create Lambda function
const blogLambda = new lambda.Function(this, "BlogHandler", {
runtime: lambda.Runtime.NODEJS_20_X,
code: lambda.Code.fromAsset("lambda"),
handler: "index.handler",
environment: {
TABLE_NAME: table.tableName,
},
});
// Grant Lambda permissions to access DynamoDB table
table.grantReadWriteData(blogLambda);
// Create a Lambda function URL
const functionUrl = blogLambda.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
});
new cdk.CfnOutput(this, "FunctionUrl", { value: functionUrl.url });
}
}
The CDK code above imports the cdk libraries we need to define our stack.
We start by allowing it to create a DynamoDB called BlogPosts with a partition key of postID, and a sort key of postDateCreated. We'll also use the pay per request billing mode.
Next we create a Lambda function called BlogHandler, using Node JS 20. This Lambda function will handle all the blog actions like writes, modifies, deletes and lists of blog posts.
We then give permissions to Lambda for read and writes to DynamoDB.
Lastly, we create a Lambda function URL to expose an endpoint to our client side to perform the writes and reads of blog posts.
AWS Lambda Function
Next we need to write our Lambda function code so the CDK can upload it to Lambda.
In the root of our project, let's create a lambda directory. Inside it create a file called index.js.
Let's write the code for Lambda to handle the CRUD operations on our blog posts.
领英推荐
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const {
DynamoDBDocumentClient,
PutCommand,
UpdateCommand,
DeleteCommand,
ScanCommand,
GetCommand,
} = require("@aws-sdk/lib-dynamodb");
const client = new DynamoDBClient();
const ddbDocClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME;
exports.handler = async (event) => {
const method = event.requestContext.http.method;
const body = JSON.parse(event.body || "{}");
const postID = event.queryStringParameters?.postID;
const postDateCreated = event.queryStringParameters?.postDateCreated
switch (method) {
case "POST":
return await createPost(body);
case "PUT":
return await updatePost(body);
case "DELETE":
return await deletePost(body.postID, body.postDateCreated);
case "GET":
if (postID && postDateCreated) {
return await getPostById(postID, postDateCreated);
}
return await listPosts();
default:
return {
statusCode: 400,
body: JSON.stringify({ message: "Unsupported method" }),
};
}
};
const createPost = async (post) => {
const params = {
TableName: TABLE_NAME,
Item: post,
};
try {
await ddbDocClient.send(new PutCommand(params));
return {
statusCode: 200,
body: JSON.stringify({ message: "Post created successfully" }),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ message: "Could not create post", error }),
};
}
};
const updatePost = async (post) => {
const params = {
TableName: TABLE_NAME,
Key: { postID: post.postID, postDateCreated: post.postDateCreated },
UpdateExpression: "set #title = :title, #content = :content",
ExpressionAttributeNames: {
"#title": "title",
"#content": "content",
},
ExpressionAttributeValues: {
":title": post.title,
":content": post.content,
},
};
try {
await ddbDocClient.send(new UpdateCommand(params));
return {
statusCode: 200,
body: JSON.stringify({ message: "Post updated successfully" }),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ message: "Could not update post", error }),
};
}
};
const deletePost = async (postID, postDateCreated) => {
const params = {
TableName: TABLE_NAME,
Key: { postID, postDateCreated },
};
try {
await ddbDocClient.send(new DeleteCommand(params));
return {
statusCode: 200,
body: JSON.stringify({ message: "Post deleted successfully" }),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ message: "Could not delete post", error }),
};
}
};
const listPosts = async () => {
const params = {
TableName: TABLE_NAME,
};
try {
const data = await ddbDocClient.send(new ScanCommand(params));
return {
statusCode: 200,
body: JSON.stringify(data.Items),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ message: "Could not retrieve posts", error }),
};
}
};
const getPostById = async (postID, postDateCreated) => {
const params = {
TableName: TABLE_NAME,
Key: { postID, postDateCreated },
};
try {
const data = await ddbDocClient.send(new GetCommand(params));
if (!data.Item) {
return {
statusCode: 404,
body: JSON.stringify({ message: "Post not found" }),
};
}
return {
statusCode: 200,
body: JSON.stringify(data.Item),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ message: "Could not retrieve post", error }),
};
}
};
The Lambda code above creates the 4 different functions to support post creation, editing, deletion and listing.
When the request comes in through the endpoint, it is routed to a switch function and the appropriate function is called and interacts with DynamoDB, to either create, modify, delete or list posts.
Let's now deploy our stack.
Run the following command:
cdk bootstrap
(if you get an import error, it's because you might need to change the code in the file at path /bin/cdk-blog-app-stack.ts: use import { BlogAppStack } instead of { CdkBlogAppStack } ).
Once the bootstrap is complete, run the deploy command, to deploy our CDK to AWS:
cdk deploy
Confirm the prompt that you wish to deploy the changes with 'y'.
Wait for the command to deploy your stack successfully.
You should see this message when it does:
Notice in the outputs in the screenshot above, the functionURL is returned. Copy that as we will need it for the frontend application as well as to test our backend below.
Testing
Let's now test our Lambda function and endpoints.
Using the function URL we copied above, enter the following command to create a test post:
curl -X POST <YourLambdaFunctionUrl> -d '{"postID": "1", "postDateCreated": "2023-07-15T12:34:56Z", "title": "Deploying a CDK App", "content": "A blog post on deploying serverless apps with the AWS CDK"}'
Alternatively, you can run a test in the Lambda Test feature in AWS. In your AWS account, find your Lambda function and use the following JSON to test your function:
{
"requestContext": {
"http": {
"method": "POST"
}
},
"body": "{\"postID\": \"1\", \"postDateCreated\": \"2023-07-15T12:34:56Z\", \"title\": \"Deploying a CDK App\", \"content\": \"A blog post on deploying serverless apps with the AWS CDK\"}"
}
After running either test, you should see a success "200" status code message and will see the item written in your DynamoDB table.
That wraps up the backend setup for our blog post platform.
Frontend Implementation
Stay tuned for next week's part 2 of this challenge, where we'll build out the frontend for our blogging platform.
Make sure to subscribe to this newsletter to receive a notification when it's out.
Conclusion
In this week’s Serverless Challenge, we successfully set up a serverless backend for our blog platform using AWS CDK to create and manage resources like Lambda and DynamoDB.
Next week we'll complete the blog platform by building out the frontend to be able to create and manage posts in a UI on our browser.
If you enjoyed this post, please consider subscribing and sharing the newsletter with your network: https://www.dhirubhai.net/newsletters/7181386750357893121/
?? My name is Uriel Bitton and I hope you learned something in this edition of The Serverless Spotlight
?? You can share the article with your network to help others learn as well.
?? If you enjoyed this you can subscribe to my newsletter on Medium to get my latest articles on serverless and cloud computing by email.
?? My blog website is coming soon, so stay tuned for that.
?? I hope to see you in next week's edition!
Uriel
Cloud Solutions Architect @ Ciena | AWS Community Builder
8 个月Nice example Uriel! Thanks for sharing.