Connecting FastAPI with Astra NoSQL DB for High-Performance APIs: A Comprehensive Guide

Build High Performance APIs with FastAPI and Astra DB Datastax

ยท

14 min read

Connecting  FastAPI with Astra NoSQL DB  for High-Performance APIs: A Comprehensive Guide

In the current digital landscape, the development of APIs that exhibit swift and seamless operation while being capable of scaling in response to the demands of contemporary applications is of utmost importance. APIs that can handle vast quantities of data and requests without compromising their performance are critical for ensuring the provision of high-quality services and applications to users.

This article aims to provide an extensive guide for developers on how to integrate Astra DB with FastAPI, enabling them to create APIs that meet high-performance standards. Astra DB is a NoSQL database built on the Apache Cassandra framework and designed to manage large volumes of data while offering fast read and write performance. In contrast, FastAPI is a modern web framework that utilizes asynchronous programming to achieve high scalability and optimal performance.

By combining Astra DB with FastAPI, developers can create APIs that are not only fast and scalable but also reliable and easily maintained. The article provides detailed instructions on how to connect Astra DB with FastAPI and also offers advice and guidelines for optimizing the efficiency and dependability of your API. Whether you are an experienced developer or a novice, this guide provides the knowledge and resources necessary to develop high-performance APIs that meet the needs of contemporary applications.

Overview of Astra DB and FastAPI

Astra DB is a cloud-native NoSQL database that is designed to handle massive amounts of data and provide high-speed read and write performance. It is built on Apache Cassandra, a highly scalable and distributed database system, and provides a fully managed, serverless experience to developers, which allows them to focus on building their applications without worrying about database management.

Astra DB offers a range of key features and benefits that are useful for modern applications, including;

  • Horizontal scalability enables it to handle millions of requests per second and petabytes of data. This makes it ideal for applications that require high scalability and availability.

  • Astra DB provides low read and writes latencies, making it suitable for real-time data access and analysis.

  • Its multi-cloud capability also allows it to be used across multiple cloud providers, including Amazon Web Services, Microsoft Azure, and Google Cloud Platform, which makes it easy to deploy and manage your database in the cloud.

Overall, Astra DB's combination of scalability, low latency, and multi-cloud support provides developers with a powerful tool to build modern, high-performance applications that can handle large amounts of data and requests. Its fully managed, serverless experience further simplifies database management, allowing developers to focus on their core business logic and application development.

FastAPI is a contemporary web framework that facilitates the construction of APIs using Python, utilizing asynchronous programming to achieve superior performance and scalability. It offers a straightforward way for building APIs, with features like automatic validation of request data, automatic generation of API documentation, and seamless integration with databases such as Astra DB.

FastAPI has several essential features and benefits that include:

High performance: FastAPI's asynchronous programming approach ensures high performance and scalability, which makes it an ideal choice for applications that require fast and responsive APIs.

User-friendly development: FastAPI provides a simple and user-friendly API for building APIs, with features like automatic validation of request data and automatic generation of API documentation that make it simple to develop and maintain APIs.

Integration with other tools: FastAPI can easily integrate with other Python tools and libraries, such as Pydantic for data validation and SQLAlchemy for database integration. This enhances the capability of FastAPI to integrate with third-party libraries and tools to improve API functionality.

Prerequisites

To successfully connect FastAPI with Astra DB, you must first ensure that you have met the following prerequisites:

  • Astra DB Account: To utilize Astra DB, you must first create an Astra DB account. Fortunately, Astra DB offers a free account that provides users with 5 GB of storage and 40 million read/write operations per month. Click here to signup

  • Python and pip: FastAPI requires Python 3.6 or later to be installed on your system, as well as pip, the package installer for Python. You can acquire the most recent version of Python by visiting the official website.

  • FastAPI and Required Packages: A basic understanding of how FastAPI and its standard dependencies work will be helpful.

Once you have these prerequisites in place, you're ready to create a database, connect Astra DB with FastAPI, and start building high-performance APIs.

Create a Database with Astra DB

In this section, you're going to create your database. Jump into the Astra DB Datastax console and click on "Create Database".

Next, fill out the database creation form.

Database Name: The name of the database(blog-db in this case).

Keyspace Name: A keyspace stores your group of tables, like a schema in a relational database. This helps keep related tables of information in a single place within your database. You can have multiple keyspace names with multiple tables. This is usually not the case with SQL databases. Visit here to read more.

Provider: Select a cloud provider that you want to host your database on. Google Cloud appears to be a free provider at the time of the writing, so I'd opt for it.

Region: Based on the selected provider you will get a list of available regions to select from. Select a free region that's closer to you. you need to upgrade to have access to all available regions.

Upon filling out the form to your taste, click on the Create Database button to create your database. Afterward, you will be redirected to your databases dashboard. Wait until the pending status turns active.

You've successfully created an active NoSQL database with Astra DB.

Connection Bundle and Secret Token

To connect the database to your FastAPI application, you need to use the secure connection bundle and a secret token provided by Astra DB.

To obtain these, go to the "Connect" tab on your database dashboard. Here, you will find options to generate a secure token and download the secure connection bundle. These measures will allow you to establish a secure connection between your FastAPI application and your Astra DB database.

Click on the Generate Token button,

Download or copy the generated token to a safe and secure location. You'll be using it soon

Click on the Get Bundle button,

Select your region and download the connection bundle to your local machine.

If you scroll further below you'll see that Astra DB provides different ways and instructions on how to connect based on a specific programming language. In the next section, you'll see how to adapt the instruction for Python to function specifically for FastAPI using the Python Cassandra driver and FastAPI Lifespan Mechanism.

Configure FastAPI and ASTRA DB

Now that you have a secure connection bundle and a secret token let's see how to use it in our FastAPI application.

Setup Basic FastAPI Project

Using your preferred IDE create a project directory for a blog application. The folder should contain the following files, main.py, database.py, models.py, config.py, requirement.txt and .env

Next, create a connect-bundle folder and copy the connection bundle zipped file provided by ASTRA DB into it.

Install requirements

Add the following text to the requirements.txt file and then run the command below.

fastapi[all]
uvicorn
cassandra-driver

Command;

$ pip install -r requirements.txt

Set Environment Variables

It's bad software practice to hardcode sensitive data and secret variables into your code. Hence, .env files contain credentials in key-value format for services used by the program. They're meant to be stored locally and not uploaded to code repositories online.

Add the following text to the .env file;

ASTRADB_KEYSPACE=<keyspace>
ASTRADB_CLIENT_SECRET=<secret>
ASTRADB_CLIENT_ID=<clientId>
ASTRADB_TOKEN=<token>

replace the following with the actual value given to you after token generation in the "Connection Bundle and Secret Token" section above.

Project Configuration

Config files are used to store key-value pairs or some configurable information that could be read or accessed in the code at some point.

Add the following text to the config.py file;

import os
from functools import lru_cache
from pathlib import Path
from pydantic import BaseSettings, Field

os.environ['CQLENG_ALLOW_SCHEMA_MANAGEMENT'] = "1"

class Settings(BaseSettings):
    base_dir: Path = Path(__file__).resolve().parent
    keyspace: str = Field(..., env='ASTRADB_KEYSPACE')
    db_client_id: str = Field(..., env='ASTRADB_CLIENT_ID')
    db_client_secret: str = Field(..., env='ASTRADB_CLIENT_SECRET')

    class Config:
        env_file = '.env'

@lru_cache
def get_settings():
    return Settings()
  • os: provides a way of using operating system-dependent functionality.

  • functools: provides a lru_cache decorator to cache the function results.

  • pathlib: provides an object-oriented approach to work with files and directories.

  • pydantic: provides data validation and settings management using Python type annotations.

The code does the following:

  • Sets the environment variable CQLENG_ALLOW_SCHEMA_MANAGEMENT to 1.

  • Defines a class Settings that inherits from BaseSettings, a class provided by pydantic.

  • The class has three attributes:

    • base_dir: a Path object representing the directory containing the script file.

    • keyspace: a required string attribute that can be set through the environment variable ASTRADB_KEYSPACE.

    • db_client_id: a required string attribute that can be set through the environment variable ASTRADB_CLIENT_ID.

    • db_client_secret: a required string attribute that can be set through the environment variable ASTRADB_CLIENT_SECRET.

  • The class also defines a nested Config class that sets the path to the environment variables file .env.

  • Defines a function get_settings decorated with lru_cache that returns an instance of the Settings class. The lru_cache decorator caches the result of the function, avoiding expensive computations if the function is called with the same arguments again.

Database Connection

Add the following code to the database.py file;

import pathlib
from cassandra.cluster import Cluster
from cassandra.auth import PlainTextAuthProvider
from cassandra.cqlengine import connection

import config

BASE_DIR = pathlib.Path(__file__).resolve().parent  

SOURCE_DIR = BASE_DIR / 'connect-bundle'

settings = config.get_settings() 

ASTRADB_CONNECT_BUNDLE = BASE_DIR / SOURCE_DIR / "secure-connect-blog-db.zip" 
ASTRADB_CLIENT_ID = settings.db_client_id
ASTRADB_CLIENT_SECRET = settings.db_client_secret

def get_session():
    cloud_config= {
            'secure_connect_bundle': ASTRADB_CONNECT_BUNDLE
    }
    auth_provider = PlainTextAuthProvider(ASTRADB_CLIENT_ID, ASTRADB_CLIENT_SECRET)
    cluster = Cluster(cloud=cloud_config, auth_provider=auth_provider)
    session = cluster.connect()
    connection.register_connection(str(session), session=session)
    connection.set_default_connection(str(session))
    return session

This is a Python code that defines a function get_session() and sets up a connection to the database using the following libraries:

  • cassandra.cluster: provides the Cluster class to connect to a Cassandra cluster.

  • cassandra.auth: provides the PlainTextAuthProvider class for authentication.

  • cassandra.cqlengine.connection: provides the connection module for the CQL Engine.

The code does the following:

  • Defines a constant BASE_DIR that points to the directory containing the script file.

  • Defines a constant SOURCE_DIR that points to a directory named connect-bundle in the BASE_DIR.

  • Calls config.get_settings() to retrieve the database client ID and client secret from the environment variables.

  • Defines a constant ASTRADB_CONNECT_BUNDLE that points to a file named secure-connect-blog-db.zip in the SOURCE_DIR.

  • Defines constants ASTRADB_CLIENT_ID and ASTRADB_CLIENT_SECRET using the values obtained from get_settings().

  • Defines a function get_session() that returns a session connected to the database:

    • Sets up a dictionary cloud_config containing the location of the secure connect bundle file.

    • Creates an instance of PlainTextAuthProvider using the client ID and client secret.

    • Creates a Cluster object passing the cloud configuration and authentication provider objects.

    • Creates a session object using the connect() method of the Cluster object.

    • Registers the session with cqlengine.connection module.

    • Sets the default connection to the created session.

    • Returns the session object

Database Model

A database model is a way to represent the structure and relationships of data in a database. Database models provide a blueprint for creating tables and establishing relationships between them, which can help ensure data integrity and efficiency.

Add the following code to the models.py to create a database model for your application.

from datetime import datetime
import uuid
from cassandra.cqlengine import columns
from cassandra.cqlengine.models import Model

from config import get_settings

settings = get_settings()

class Post(Model):
    __keyspace__ = settings.keyspace
    post_id = columns.UUID(primary_key=True, default=uuid.uuid1)
    title = columns.Text()
    body = columns.Text()
    created_at = columns.DateTime(primary_key=True, default=datetime.utcnow())

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) ->  str:
        return f"Post(title={self.title})"

The code defines a class Post that inherits from cassandra.cqlengine.models.Model and represents a model for a post object in the database.

The Post model has the following attributes:

  • post_id: a UUID field that serves as the primary key of the table.

  • title: a text field representing the title of the post.

  • body: a text field representing the body of the post.

  • created_at: a datetime field representing the creation time of the post.

The __keyspace__ attribute of the model specifies the name of the keyspace where the table for this model will be created.

The __repr__ method returns a string representation of the object and the __str__ method calls the __repr__ method.

Synchronize Model and Create Table

Now, you need a way to sync the Post Model and create a database table on Astra DB.

FastAPI provides a Lifespan mechanism to do so, According to FastAPI docs, "You can define logic (code) that should be executed before the application starts taking requests, and right after it finishes handling requests, it covers the whole application lifespan (the word "lifespan" will be important in a second ๐Ÿ˜‰)."

Using the Lifespan mechanism you'll execute the Python Cassandra driver sync_table functionality.

Using the sync_table method the Cassandra CQL engine inspects the models defined in the application and creates or updates corresponding tables and columns in the database. This process is referred to as schema management and ensures that the database schema stays up-to-date with the application's model definitions. By synchronizing the database schema with the application's models, the application can reliably read and write data to the database using the CQL engine. This makes it very easy to add new database columns on the go.

Add the following code to the main.py file,

from contextlib import asynccontextmanager
from fastapi import FastAPI
from cassandra.cqlengine.management import sync_table

from models import Post
from database import get_session

@asynccontextmanager
async def lifespan(app: FastAPI):
    global DB_SEESSION
    DB_SEESSION = get_session()
    sync_table(Post)
    yield

app = FastAPI(lifespan=lifespan)

@app.get("/")
def homepage():
    return {"meaasage": "Rugged man FastAPI featuring clergey man ASTRA DB"}
  • asynccontextmanager is a decorator used to create a context manager that can be used in an asynchronous (async/await) context.

  • lifespan is an async context manager that initializes a global DB_SESSION variable, calls sync_table to create or synchronize the Post table with the database and yield control to the context in which it is called.

  • app is an instance of FastAPI framework, which is initialized with the lifespan async context manager.

  • @app.get("/") is a decorator that registers a route for the application to handle GET requests at the root URL.

  • The homepage function is the request handler for the "/" route. It returns a dictionary with a message key-value pair.

Start the FastAPI application

Now that you have everything set up and configured properly. It's time to start your FastAPI application.

Open your terminal and run the following command; uvicorn main:app --reload

uvicorn is a Python ASGI (Asynchronous Server Gateway Interface) web server, used to run asynchronous web applications built with frameworks such as FastAPI.

main:app specifies the module and the application instance to run.

--reload specifies that the server should automatically reload the application when code changes are detected. This is useful for development purposes, as it allows you to see the changes you make to your code without having to manually restart the server.

Review the State of Your Database

Upon starting the FastAPI application. The Post Model will be synced and the database table created automatically. This process is handled by the Python Cassandra driver in the Lifespan Mechanism.

Visit your database console on Astra DB and Navigate to the CQL Console tab.

Execute the following command in the console; SELECT * FROM blog.post ;

And there you have it. A ready-to-use database solution hosted on the cloud.

Basic CRUD Operations

In this section, you're going to build a very basic CRUD API that allows you to create, read, update, and delete items in the database.

The Python Cassandra Driver ORM is a tool that offers a user-friendly interface for developers to interact with a Cassandra database. It simplifies the process of working with Cassandra data by providing an object-relational mapping layer that allows developers to use Python classes and objects to perform database operations instead of writing raw CQL queries.

The ORM provides a straightforward API that facilitates CRUD (create, read, update, delete) operations and CQL data queries. It takes care of the technicalities of establishing connections with a Cassandra cluster, managing connections, and executing CQL queries, allowing developers to focus on their application logic.

Edit the main.py file and add the following code to the file.

from datetime import datetime
from fastapi import FastAPI, HTTPException

# Create some hard-coded data for demonstration purposes
sample_posts = [
    {
        "title": "First post",
        "body": "This is the first post.",
        "created_at": datetime(2022, 12, 25, 12, 30)
    },
    {
        "title": "Second post",
        "body": "This is the second post.",
    }
]

# Define the CRUD operations
@app.post("/posts")
def create_post():
    for post in sample_posts:
        post_data = Post.create(**post)
    return "Posts created successfully!"

@app.get("/posts")
def get_posts():
    posts = [dict(x) for x in Post.objects.all()]
    return posts

@app.put("/posts/{post_id}")
def update_post(post_id: str, title: str, body: str):
    try:
        post = Post.objects(post_id=post_id).get()
        post.title = title
        post.body = body
        post.save()
        return "Post updated successfully!"
    except:
        raise HTTPException(status_code=404, detail="Post not found")

@app.delete("/posts/{post_id}")
def delete_post(post_id: str):
    try:
        post = Post.objects(post_id=post_id).get()
        post.delete()
        return "Post deleted successfully!"
    except:
        raise HTTPException(status_code=404, detail="Post not found")

endpoints:

  • GET /posts: Returns a list of all posts.

  • POST /posts: Creates a new post.

  • PUT /posts/{id}: Updates the post with the specified ID.

  • DELETE /posts/{id}: Deletes the post with the specified ID.

Test API Endpoints

Restart your FastAPI application if it's not running already. Using your favorite tool send HTTP requests to each API endpoint.

Visit the CQL Console tab on your Astra DB dashboard and execute the SELECT command as shown below.

Your database is being populated with data sent via the API endpoints.

Conclusion

In conclusion, connecting Astra DB with FastAPI is a powerful combination for building high-performance APIs that can handle large volumes of data and requests. Astra DB's NoSQL database and FastAPI's asynchronous framework provide a robust and efficient solution for building scalable APIs that can meet the demands of today's digital landscape. By following the comprehensive guide outlined in this article, developers can easily set up a connection between Astra DB and FastAPI, and leverage the benefits of both technologies to create fast and reliable APIs. With the ability to handle complex queries, integrate with other services, and easily scale to meet changing requirements, Astra DB and FastAPI are a winning combination for any API project.

Source Code: https://github.com/princewilling/FastAPI-X-Astra-DB

Reference and Further Readings

https://fastapi.tiangolo.com/

https://docs.datastax.com/en/drivers/python/2.5/index.html

https://docs.datastax.com/en/home/docs/index.html

ย