Building A Serverless Notifications Feature With AWS

Building A Serverless Notifications Feature With AWS

?? 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

Architecture overview

We'll use the following AWS services to design our solution:

  • Amazon DynamoDB to store notifications for each user
  • AWS Lambda to listen to events, store them in DynamoDB
  • A second Lambda function to fetch notifications from DynamoDB
  • Lambda function URLs to provide endpoints for the frontend to create and fetch notifications
  • React App frontend client to invoke serverless functions and display notifications to users.

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:

  1. It retrieves the userId and message from the event parameter (passed from the frontend client)
  2. It creates and stores a notification item in the DynamoDB table we created

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:

  • Use a Lambda function to create a notification and store it in DynamoDB
  • Use DynamoDB streams to trigger a second Lambda function
  • Whenever a notification item is added, use the second Lambda function to fetch the notifications for the current user
  • The user would receive new notifications in real-time

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!

要查看或添加评论,请登录

社区洞察

其他会员也浏览了