Introduction

As a cloud engineer, working with APIs and serverless services will almost always come into play at some point in your journey. I recently completed the LearnToCloud Phase 2 guide and took on the capstone project. In this article, I'll walk you through a version of the project where I built a FastAPI, leveraging Azure Blob Storage for storage, CosmosDB for the database, and Terraform for Infrastructure as Code (IaC), all deployed using Azure Functions.

Project Overview

The API serves data on clubs that participate in the Nigerian Professional Football League, including their short name, nickname, home stadium, link to their logo image, and number of titles they have won. It also has a fun feature that generates fun facts about a club when requested using the OpenAI API.

Tools and services used:

  • Terraform is used to provison resources on Azure.
  • Azure Blob storage is used to save image files.
  • Azure CosmosDB serves as the database which FastAPI interacts with to retrieve and serve the data
  • OpenAI API
  • Azure Functions App to host the API as a serverless function

Architecture diagram:

project diagram

Setting Up The Project

Prerequisites:

  • Azure CLI
  • Python Installed
  • Knowledge of Terraform
  • Basic knowledge of Python and FastAPI
  • Familiarity with Azure CosmosDB, Blob Storage, Azure Functions, and Azure Core Tools

You can either run the API locally or deploy using Azure Functions. The former is just the basic steps of running a FastAPI application using uvicorn and does not leverage the full potential of the setup. So I will walk you through the full setup. First, you should clone the repository from Github here.

Terraform
Once you are in the project folder, cd into the infra directory and update the necessary variables and state information, then run:

terraform apply -auto-approve

Once done, Terraform will create a:

  • Storage account and blob container
  • Upload logo images to the blob storage
  • Azure CosmosDB account
  • Azure Functions App

Cosmos DB
Next, access the data.json file and populate Azure CosmosDB with the JSON data. If you want to learn a bit more about CosmosDB SQL server you can use this link.

FastAPI
Before we proceed, I would like to hint at some parts of the main API code. The API has 4 prominent endpoints and they serve data stored in Azure CosmosDB.

  • GET /clubs: Get all clubs.
  • GET /clubs/{club_name}: Get a particular club details by name.
  • GET /clubs/by-titles?min_titles=5: Get clubs with at least 5 titles.
  • GET /clubs/fun-fact?club_name=Enyimba: Get a fun fact about a club.

The part of the code that interacts with CosmosDB looks like this:

from azure.cosmos import CosmosClient
from .config import settings

class CosmosDB:
    """class to handle cosmosdb connection and queries"""
    def __init__(self):
        self.client = CosmosClient(settings.cosmos_endpoint, settings.cosmos_key)
        self.database = self.client.get_database_client(settings.cosmos_database)
        self.container = self.database.get_container_client(settings.cosmos_container)

    def get_clubs(self):
        query = """
        SELECT 
            c.id, 
            c.club, 
            c.full_name, 
            c.short_name, 
            c.nickname, 
            c.stadium, 
            c.titles_won, 
            c.logo
        FROM c
        """
        try:
            items = list(self.container.query_items(query, enable_cross_partition_query=True))
            return items
        except Exception:
            return {"Internal Server Error"}

Also, a basic FastAPI endpoint looks like this:

@app.get("/clubs/by-titles-won")
def get_clubs_by_titles(min_titles: int):
    """get clubs with titles won greater than or equal to min"""
    try:
        clubs = db.get_clubs()
        filtered_clubs = [club for club in clubs if int(club["titles_won"]) >= min_titles]
        return filtered_clubs
    except Exception:
        raise HTTPException(status_code=500, detail="Internal Server Error")

Tests are also written to test the endpoint to ensure it does what it should:

def test_get_club_by_name():
    response = client.get("/clubs/heartland")
    assert response.status_code == 200
    assert response.json()["club"] == "Heartland"

You can go through more parts of the API code at .api/WrapperFunction

Azure Functions
The configurations setup for Azure Functions are already available. If you want to learn how to setup Azure Functions using the Core Tools, you should use this link.

For specifically using Azure functions with FastAPI, this link will serve as a better guide.

Now, there are two ways to host your functions, locally (usually for testing your setup) and by publishing to Azure Functions App. To publish locally, run:

func start

To deploy to Azure Functions App, there are some considerations you should keep in mind. First of all, ensure your Python version used locally is compatible with the one you chose when creating the Functions App, also, ensure that the folder structure is properly set up and that environment variables are updated. After all this is done, run:

func azure functionapp publish

Upon successful deployment, Azure Functions will provide a link where you can access your API. You can access the API via the various available endpoints.

- GET /
testing endpoint in browser

- GET /clubs/by-titles?min_titles=5
testing endpoint in browser

Conclusion

Summary
In this project, I was able to build and deploy a FastAPI-based serverless API using Azure Functions, CosmosDB, Blob Storage, and Terraform. The API provides information on Nigerian football clubs and even generates fun facts using OpenAI.

Next Steps

  • Try extending the API by adding more endpoints, integrating authentication.

  • Explore other serverless compute options like AWS Lambda or Google Cloud Functions.

Call to Action
Feel free to check out the GitHub repo, experiment with the codebase ad drop some suggestions. If you found this helpful, don’t forget to share!

Happy Hacking!