Building Your Own NBA Game Notification App with AWS: A Step-by-Step Guide

My journey in creating a system that sends you game notifications directly to your email or phone.

Created by Ron-tino on January 04, 2025
AWS Amplify

In this blog post, we'll walk you through creating a system that sends you game notifications directly to your email or phone. We'll be using an event-driven architecture on AWS, leveraging services like Lambda, SNS, and EventBridge. This project is perfect for those who want to learn how to use serverless technologies, interact with external APIs, and automate tasks in the cloud.

Project Overview

Here's a breakdown of the system we’ll be building:

  • Data Source: We'll be using a free API from Sportsdata.io to fetch NBA game data (scores, times, etc.).
  • Code Execution: AWS Lambda will host the Python code that retrieves data from the API, processes it, and prepares it for notification.
  • Notification System: We'll utilize AWS Simple Notification Service (SNS) to deliver messages to our chosen endpoints (email/SMS).
  • Scheduling: AWS EventBridge will schedule the execution of our Lambda function at specified intervals.

This project is event-driven, meaning that the schedule event will automatically trigger the workflow.

AWS Amplify

Detailed Step-by-Step Guide

1. Setting up SNS Topic and Subscriptions:

  • Navigate to SNS: Go to the AWS Management Console and search for "SNS." Select "Simple Notification Service."
  • Create a Topic: In the left-hand menu, go to "Topics" and click "Create Topic."
    • Choose "Standard" type.
    • Give your topic a name (e.g., "GD-topic" for Game Day).
    • Leave the rest as default and click "Create Topic."
  • Subscribe to the Topic:
    • Select the created topic.
    • Click "Create Subscription."
    • Choose "Email" as the protocol and enter your email address.
    • Click "Create Subscription."
    • Check your email inbox for a subscription confirmation email and confirm your subscription.
    • Refresh the subscription page in the AWS Console to see the confirmation status.

2. Creating IAM Policies and Roles:

  • Navigate to IAM: Open a new tab and search for "IAM" (Identity and Access Management).
  • Create a Policy for SNS Access:
    • Go to "Policies" on the left-hand menu and click "Create Policy."
    • Choose "JSON" tab.
    • Copy the JSON policy from the provided Github repo policies/GD-sns-policy.json and paste it into the editor.
    • Update the `Resource` value in the JSON policy with the ARN (Amazon Resource Name) of your SNS topic from Step 1 (make sure to put it in between the double quotes). This allows access to your specific topic.
    • Click "Next", give the policy a name (e.g., "GD-SNS-policy"), and click "Create Policy."
  • Create a Role for Lambda:
    • Go to "Roles" on the left-hand menu and click "Create Role."
    • Choose "AWS Service" and select "Lambda" as the service.
    • Click "Next."
    • Attach two policies to this role. Search for and select the following:
      • The `GD-SNS-policy` you just created.
      • `AWSLambdaBasicExecutionRole` (this is an AWS managed policy that allows Lambda to send logs to CloudWatch).
    • Click "Next", give the role a name (e.g., "GD-Lambda-role"), and click "Create Role."

3. Creating the Lambda Function:

  • Navigate to Lambda: Go back to the AWS Management Console and search for "Lambda." Select "Lambda."
  • Create a Function:
    • Click "Create Function."
    • Select "Author from scratch."
    • Give your function a name (e.g., "GD-notifications").
    • Choose "Python 3.x" as the runtime.
    • Under "Permissions," choose "Use an existing role" and select the "GD-Lambda-role" you just created.
    • Click "Create Function."
  • Paste the Python Code:
    • Copy the code from the provided Github repo source/GD-notifications/lambda_function.py and paste it into the code editor in the Lambda console.
    • Click "Deploy."
  • Set Environment Variables:
    • Go to "Configuration" tab and select "Environment Variables."
    • Click "Edit."
    • Add two environment variables:
      • Key: `NBA_API_KEY`, Value: Your API key from Sportsdata.io (blurred out in the video).
      • Key: `SNS_TOPIC_ARN`, Value: The ARN of your SNS topic from step 1.
    • Click "Save."

4. Understanding the Python Code:

Here's a breakdown of what the Python code does:

  • It starts with imports for various libraries like OS (for environment variables), JSON, urlib (for making HTTP requests), boto3 (for interacting with AWS services like SNS), and date.
  • It gets environment variables for the API key and SNS topic ARN.
  • It Initializes an SNS client using boto3.
  • The code adjusts for central time by getting the current UTC time and converting it to central. This ensures that the game data is fetched for the current day in central time.
  • Constructs API URL by adding in today's date and the API key from environment variables. This is how the script specifies which days games to fetch data for.
  • It uses the URL lib library to make the request to the API.
  • The JSON response is formatted and then a format\_game\_data function is called, which generates a readable message for each game.
  • The format\_game\_data function that loops through the games to extract specific information like away team, home team, scores, and status. It then formats the data into a readable string for publishing to SNS, and includes quarter scores if they exist.
  • Finally, the formatted message is published to the SNS topic along with the subject "NBA game scores."

5. Testing the Lambda Function:

  • In the Lambda console, select "Test" tab and create a new test event by clicking "Create new event".
  • Leave the default settings and save the test event.
  • Click "Test."
  • Check your email to see if you received the game notifications.

6. Scheduling the Lambda Function with EventBridge:

  • Navigate to EventBridge: Go back to the AWS Management Console and search for "EventBridge". Select the service.
  • Create a Rule:
    • Click "Create Rule".
    • Give the rule a name (e.g., "GD-rule").
    • Select "Schedule" as the rule type.
    • Select “Reoccurring Schedule" under schedule type.
    • Select "Cron-based schedule."
    • Copy the cron expression from the video or use the following: This will trigger the Lambda function to run every 2 hours from 9 AM to 11PM and then from 12AM to 2AM.
    • Click "Next".
    • Select "AWS Lambda" as the target type.
    • Choose your Lambda function ("GD-notifications").
    • Click "Next" Leave the defaults and click next and "Create Schedule".

Conclusion

Congratulations! You've built a fully automated NBA game notification system using AWS. You can now receive game updates on your email without lifting a finger. This project gives a great insight into event-driven architectures, working with external APIs, and using serverless services. Experiment with different cron schedules, explore other data points from the Sportsdata.io API, and extend this project to fit your specific interests!

Python Code

                        
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"}