Day 2 - Building an NBA Game Data Notifier Using AWS Lambda, SNS, and EventBridge

Day 2 - Building an NBA Game Data Notifier Using AWS Lambda, SNS, and EventBridge

In this article, we will walk through an AWS architecture that leverages Lambda functions, SNS topics, EventBridge rules, and IAM policies to automate the process of fetching NBA game data and sending notifications via email. I opted to use AWS, unlike the first post where I used Google Cloud Platform (GCP).
The code for this article can be found in the Day_2 folder in this GitHub repo

Components of the Architecture

  • AWS Lambda Functions: Serverless functions that will be triggered to fetch NBA game data from sportsdata.io at scheduled intervals.

  • Amazon SNS: A messaging service that will be used to publish the fetched NBA game data to your email address.

  • Amazon EventBridge: A serverless event bus that will be used to schedule the Lambda function execution at predefined intervals.

  • IAM Policies: Policies that will be created to grant the necessary permissions for SNS to publish messages to the SNS topic.

Step by step guide

  1. Create a Simple Notification Service (SNS) Topic & Subscription

    Navigate to the Simple Notification Service (SNS) page in the AWS Console and click on Topics. Click on Create topic, select the Standard tab, give the topic a name, leave the rest as default and click on Create topic to finish.
    We then need to create a subscription where we will be adding our email (or the channel of your choice) to receive the data from the API through the SNS service. Navigate to Subscriptions on the SNS page and click on Create subscription. In the Protocol dropdown select Email (or other) and set the endpoint as your email address (or other format based on the protocol you chose). Leave the rest as default and click on Create subscription to finish.
    If you chose Email as the protocol, you will need to login to your email account and confirm the subscription that has been sent to you via email.

    You can now get data sent to your email once it’s published since your email is now subscribed to the SNS topic

  2. Create an IAM Policy

    Go to the IAM page in AWS Console and click on Policies. Click on Create Policy. You will be redirected to a new window where you will create a new policy. Select SNS from the dropdown, then select JSON at the top of that window. This will give you a Policy editor where you can replace its contents using the following policy. Make sure you update the Resource section of the policy to match the ARN of the topic you created. Go to the SNS page, click on Topics and copy the ARN value of the SNS topic you created earlier on, copy it in the quotation marks of the Resource section of this policy:

     {
         "Version": "2012-10-17",
         "Statement": [
             {
                 "Effect": "Allow",
                 "Action": "sns:Publish",
                 "Resource": "arn:aws:sns:REGION:ACCOUNT_ID:gd_topic"
             }
         ]
     }
    

    Scroll to the bottom and click Next. You will be required to give the Policy a name, give it one, leave the rest of the entries as default and click on Create Policy at the bottom.

  3. Create a Role

    We will be creating a role that will be attached to the lambda function. This role will have the policy that we have created that gives it permission to publish on SNS. Navigate to Roles on the IAM page and click on Create Role. Choose AWS Service tab, then select Lambda from the dropdown as the use case then click on Next. You will be taken to a page where you will choose the permission policies that will be attached to this role. You will search and select the policy you created above, along with another policy called AWSLambdaBasicExecutionRole. This is an AWS managed role that will help us send logs to CloudWatch, Monitor etc. Make sure you select these two policies, then click on Next. Give the role a name and click on Create role to finish.

  4. Create a Lambda Function

    The first step involves creating a Lambda function that will be responsible for fetching NBA game data from sportsdata.io. You can use any programming language supported by Lambda, such as Python, Node.js or Java.
    Navigate to the Lambda page in AWS and click on Create function. Select Author from scratch, give the function a name and select the appropriate runtime for the programming language that you’ll be using. I used Python, so I selected Python 3.13. Go to the Change default execution role and expand this section. Select Use an existing role and select the role you had created earlier on from the dropdown and then click on Create function to finish.
    You will be presented with a code editor where you will be writing your code.
    Here is the code snippet in Python that fetches the NBA game data from sportsdata.io using their API:

     import os
     import json
     import urllib.request
     import boto3
     from datetime import datetime, timedelta, timezone
    
     def format_game_data(game):
         status = game.get("Status", "Unknown")
         away_team = game.get("AwayTeam", "Unknown")
         home_team = game.get("HomeTeam", "Unknown")
         final_score = f"{game.get('AwayTeamScore', 'N/A')}-{game.get('HomeTeamScore', 'N/A')}"
         start_time = game.get("DateTime", "Unknown")
         channel = game.get("Channel", "Unknown")
    
         # Format quarters
         quarters = game.get("Quarters", [])
         quarter_scores = ', '.join([f"Q{q['Number']}: {q.get('AwayScore', 'N/A')}-{q.get('HomeScore', 'N/A')}" for q in quarters])
    
         if status == "Final":
             return (
                 f"Game Status: {status}\n"
                 f"{away_team} vs {home_team}\n"
                 f"Final Score: {final_score}\n"
                 f"Start Time: {start_time}\n"
                 f"Channel: {channel}\n"
                 f"Quarter Scores: {quarter_scores}\n"
             )
         elif status == "InProgress":
             last_play = game.get("LastPlay", "N/A")
             return (
                 f"Game Status: {status}\n"
                 f"{away_team} vs {home_team}\n"
                 f"Current Score: {final_score}\n"
                 f"Last Play: {last_play}\n"
                 f"Channel: {channel}\n"
             )
         elif status == "Scheduled":
             return (
                 f"Game Status: {status}\n"
                 f"{away_team} vs {home_team}\n"
                 f"Start Time: {start_time}\n"
                 f"Channel: {channel}\n"
             )
         else:
             return (
                 f"Game Status: {status}\n"
                 f"{away_team} vs {home_team}\n"
                 f"Details are unavailable at the moment.\n"
             )
    
     def lambda_handler(event, context):
         # Get environment variables
         api_key = os.getenv("NBA_API_KEY")
         sns_topic_arn = os.getenv("SNS_TOPIC_ARN")
         sns_client = boto3.client("sns")
    
         # Adjust for Central Time (UTC-6)
         utc_now = datetime.now(timezone.utc)
         central_time = utc_now - timedelta(hours=6)  # Central Time is UTC-6
         today_date = central_time.strftime("%Y-%m-%d")
    
         print(f"Fetching games for date: {today_date}")
    
         # Fetch data from the API
         api_url = f"https://api.sportsdata.io/v3/nba/scores/json/GamesByDate/{today_date}?key={api_key}"
         print(today_date)
    
         try:
             with urllib.request.urlopen(api_url) as response:
                 data = json.loads(response.read().decode())
                 print(json.dumps(data, indent=4))  # Debugging: log the raw data
         except Exception as e:
             print(f"Error fetching data from API: {e}")
             return {"statusCode": 500, "body": "Error fetching data"}
    
         # Include all games (final, in-progress, and scheduled)
         messages = [format_game_data(game) for game in data]
         final_message = "\n---\n".join(messages) if messages else "No games available for today."
    
         # Publish to SNS
         try:
             sns_client.publish(
                 TopicArn=sns_topic_arn,
                 Message=final_message,
                 Subject="NBA Game Updates"
             )
             print("Message published to SNS successfully.")
         except Exception as e:
             print(f"Error publishing to SNS: {e}")
             return {"statusCode": 500, "body": "Error publishing to SNS"}
    
         return {"statusCode": 200, "body": "Data processed and sent to SNS"}
    

    After writing your code, you can click on Deploy to deploy the function
    Script explanation:
    This script fetches the NBA game data for the current day from the sportsdata.io API and publishes it to an AWS SNS topic. The format_game_data function processes individual game details, formatting them differently based on the game’s status (Final, InProgress, Scheduled, or Unknown). The lambda_handler retrieves API credentials and SNS topic ARN from environment variables, adjusts the current time to Central Time, and fetches game data from the API. The data is then formatted, compiled into a message, and sent via SNS. The function includes error handling for both API requests and SNS publishing, ensuring robust operation in case of failures.

    You will notice that on the script, we will be reading values from environment variables: NBA_API_KEY and SNS_TOPIC_ARN. Since we won’t be hardcoding these values into the code for security purposes, we will be setting these values elsewhere.
    Make sure you have the NBA API key and SNS_TOPIC_ARN values ready. On the Lambda function page, click on the Configuration tab and click Environment variables on the left panel. Click on Edit to add the environment variables and their respective values. Make sure to match the case and spelling of your environment variables as they appear in your code. Click on Save to finish.

  5. Test the function

    On the Lambda function page, click on the Test tab, fill in the Event name field with a name of your choice, leave the rest as default, click on Save at the top right and click on Test.
    The function will execute and the data will be sent to SNS, which will in turn send it to your email:

  6. Schedule the function to be invoked using EventBridge

    Navigate to EventBridge page on AWS and click on Create rule. Give the rule a name and select Schedule on the Rule type section. Leave the rest as default and click on Continue in EventBridge Scheduler.
    In the second page, scroll to the bottom section and select Recurring schedule in the Schedule pattern section.
    In the Schedule type section, select Cron-based schedule and provide the Cron expression that will be used. You can use this page to learn how to do so.
    I set mine to run every 5 minutes, just to test its functionality.
    In the Flexible time window, you can set this to off (optional - choose the option that fits your needs)
    Scroll to the bottom of the page and click on Next.
    On the new page, select Templated targets tag and select AWS Lambda
    You will then select the name of your Lambda function from the dropdown in the Invoke section; and click on Next at the bottom of the page.
    On the Settings page that will appear next, leave the rest as defaults and click on Next.
    Review the details on the next page and click on Create schedule to finish.
    You will then start receiving the data to your email based on the schedule you set above.

Conclusion

By following these steps, you will have a serverless architecture that automates the process of fetching NBA game data and sending notification emails. This architecture leverages the power of AWS Lambda, SNS, and EventBridge to create a scalable and cost-effective solution.