Building A Serverless Notifications Feature With AWS
Uriel Bitton
AWS Cloud Consultant | The DynamoDB guy | AWS Certified | I help you supercharge your DynamoDB database ????
?? Hello there! Welcome to The Serverless Spotlight!
In this week's edition, I'll guide you through building a simple and efficient notifications feature with AWS that you can use in your front-end application.
The goal of this challenge is to design the backend services required to build a simple but fully functioning notification system which you can easily attach to your frontend client application.
Here's the general overview of how we will build this system.
Architecture Overview
We'll use the following AWS services to design our solution:
Building the Solution
Create a DynamoDB Table
Log in to your AWS console and head over to the Amazon DynamoDB service.
Let's create a new table here.
On the create table page, name the table "notifications" and define the partition key as "notificationID" and sort key as "timestamp".
(note you can also optionally create a GSI if you want to fetch notifications by status).
Here is our table item structure:
Go ahead and create that table.
Let's now head into the AWS Lambda service.
Create a Lambda function to create notifications
On the Lambda console, create a new Lambda function.
Call it "createNotification" and use the Node JS runtime.
Add permissions for the function to access DynamoDB and SNS.
In the code source section add the following code:
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
const dynamoDB = new DynamoDBClient();
export const handler = async (event) => {
const { userId, message } = JSON.parse(event.body);
const timestamp = Date.now();
const notificationId = `${userId}-${timestamp}`;
// Store notification in DynamoDB
const dynamoParams = {
TableName: "notifications",
Item: {
userId: { S: userId },
notificationId: { S: notificationId },
timestamp: { N: timestamp.toString() },
message: { S: message },
status: { S: "unread" }
}
};
await dynamoDB.send(new PutItemCommand(dynamoParams));
return {
statusCode: 200,
body: JSON.stringify({ message: "Notification created and sent" })
};
};
The Lambda function above performs two principle operations:
Let's deploy that function by clicking on the deploy button.
In the new Lambda code editor, the deploy button can be found here.
Once deployed, we'll need to create an endpoint to call this function from our frontend.
Note that we can also use API Gateway for this, but i'll use Lambda function URLs for the sake of brevity.
If you choose to use API Gateway, you can safely skip the steps below on Lambda function URLs.
Here's an article I wrote on how to work with real-time data with API Gateway Websockets.
For a simpler, non-realtime method, continue with function URLs below.
Find the Configuration tab and click on Function URL in the left side menu bar.
Here you can create a function URL for this function.
If you are not familiar with this, I wrote a concise and easy post here to help you do this in minutes.
Once you create the function URL you can copy it and note it down, we'll be using it very soon in our frontend app.
领英推荐
Let's now create the second Lambda function to fetch notifications.
In Lambda again, create a new function and call it "fetchNotifications".
Use the same settings as the first function with the same permissions.
Add the following code to fetch notifications from DynamoDB:
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { QueryCommand } from "@aws-sdk/client-dynamodb";
const dbClient = new DynamoDBClient();
exports.handler = async (event) => {
const { userId } = event.queryStringParameters || {};
const params = {
TableName: "notifications",
IndexName: "userId-index",
KeyConditionExpression: "userId = :userId",
ExpressionAttributeValues: {
":userId": { S: userId },
},
};
try {
const command = new QueryCommand(params);
const data = await dbClient.send(command);
return {
statusCode: 200,
body: JSON.stringify(data.Items),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: "Failed to retrieve notifications" }),
};
}
};
You might have to create a GSI to be able to query the table by userId.
Here's a guide on creating GSIs in DynamoDB.
Deploy the Lambda code above.
You can also follow the same steps as previously to create a function URL for this function as well.
We'll use the endpoint URL in the frontend code below.
Alternate Solution
Another simpler real-time solution to get notifications would be by using DynamoDB streams.
If you require the notifications to be pushed in real-time, you can skip API Gateway and use DynamoDB streams with Lambda.
Here's a general overview of the solution:
This is a simpler solution to using API Gateway Websockets (and perhaps a lower cost solution as well).
Here's an article I wrote on working with DynamoDB Streams to design this solution.
Building the frontend React App
Let's build out the frontend app.
Start by creating a new react app - to go quicker use Vite.
Once your react app is initiated, let's install Axios to simplify POST calls to Lambda.
Open a new terminal and enter the following command.
npm i axios
Here's the notifications component code:
import React, { useState } from 'react';
import axios from 'axios';
const NotificationForm = () => {
const [message, setMessage] = useState('');
const [recipientId, setRecipientId] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post('your-lambda-function-url-endpoint', {
message,
recipientId,
});
console.log('Notification sent:', response.data);
setMessage('');
setRecipientId('');
} catch (error) {
console.error('Error sending notification:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Recipient ID"
value={recipientId}
onChange={(e) => setRecipientId(e.target.value)}
required
/>
<textarea
placeholder="Your message"
value={message}
onChange={(e) => setMessage(e.target.value)}
required
/>
<button type="submit">Send Notification</button>
</form>
);
};
export default NotificationForm;
The code above displays a input field to add a recipient user's ID, a textarea to add a message and a button to submit the notification message.
When the user submits the message, the handleSubmit function is called.
This makes a fetch to our Lambda function and runs the code on the serverless function.
It'll create the notification object with a recipientId and a message and store it in DynamoDB.
Then to display these notifications we can add the following component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const NotificationList = ({ recipientId }) => {
const [notifications, setNotifications] = useState([]);
useEffect(() => {
const fetchNotifications = async () => {
try {
const response = await axios.get(
`your-second-lambda-url-endpoint`,
{ params: { userId } }
);
setNotifications(response.data);
} catch (err) {
console.error(err);
}
};
if (recipientId) {
fetchNotifications();
}
}, [recipientId]);
return (
<div>
<h2>Notifications</h2>
{notifications.length === 0 ? (
<p>No notifications</p>
) : (
<ul>
{notifications.map((notification) => (
<li key={notification.notificationId.S}>
<p><strong>Message:</strong> {notification.message.S}</p>
<p><small>Received at: {new Date(notification.createdAt.S).toLocaleString()}</small></p>
</li>
))}
</ul>
)}
</div>
);
};
export default NotificationList;
The code above fetches the notifications in our notifications DynamoDB table and stores them inside an array.
In the UI we map over this notifications array to display the notifications to the user.
Conclusion
In this article, we've used a few AWS services to create a notifications feature for a frontend web app.
We used AWS Lambda to run our serverless server-side code to create and fetch notifications, DynamoDB to store the notifications data and Lambda function URLs to invoke the serverless functions from our frontend react app.
With this simple architecture we can create an effective notifications system.
?? 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 want to learn how to save money in the cloud you can subscribe to my brand new newsletter The Cloud Economist.
?? I hope to see you in next week's edition!