Implementing the Singleton Pattern in FastAPI for Efficient Database Management
In modern application development, performance and resource management play a crucial role in ensuring scalable, maintainable, and efficient systems. One of the key patterns that help achieve this is the Singleton Pattern, which ensures that a class or object is instantiated only once across the lifecycle of the application. This is particularly beneficial in managing expensive resources like database connections.
In this article, we will explore how to implement the Singleton Pattern in FastAPI, a modern web framework for building APIs.
Why Use the Singleton Pattern?
The Singleton Pattern is a design pattern that restricts the instantiation of a class to a single instance and provides a global point of access to it. This pattern is especially useful in scenarios where initializing resources is expensive, such as:
- Database Connection Pools: Opening and closing database connections repeatedly can become a performance bottleneck. Using a Singleton ensures that only one connection pool is created and shared across requests.
- Caching: Singleton objects can be used to store and manage cache instances, improving efficiency by avoiding redundant operations.
- Logging Services: You may want a single logging service to collect logs throughout the application lifecycle without re-initializing for each request.
By leveraging the Singleton pattern, applications become more resource-efficient, improving performance and scalability.
Setting Up the FastAPI Application
Before we jump into implementing the Singleton pattern, let’s first set up a basic FastAPI application.
Install FastAPI and Uvicorn:
pip install fastapi uvicorn sqlalchemy psycopg2-binary
Here, we install FastAPI for building the web API, Uvicorn as the ASGI server, and SQLAlchemy with psycopg2-binary for database interaction.
Directory Structure:
fastapi_singleton_app/
│
├── app.py # FastAPI entry point
├── db.py # Singleton pattern implementation
└── models.py # SQLAlchemy models
Now, let’s move on to implementing the Singleton Pattern.
Implementing the Singleton Pattern for Database Connections
db.py — Singleton Database Connection Pool
We’ll create a DatabaseConnectionPool
class that ensures only one instance of the database connection pool is initialized and reused throughout the application.
import threading
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Singleton Class for Database Connection Pool
class DatabaseConnectionPool:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super(DatabaseConnectionPool, cls).__new__(cls)
cls._instance._initialize_pool()
return cls._instance
def _initialize_pool(self):
print("Initializing DB connection pool...")
self.engine = create_engine('postgresql://user:password@localhost/mydb')
self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
def get_session(self):
return self.SessionLocal()
# Dependency Injection in FastAPI
def get_db():
db_pool = DatabaseConnectionPool()
db = db_pool.get_session()
try:
yield db
finally:
db.close()
- Thread-Safe Singleton: The
_lock
ensures that the Singleton is thread-safe. If two requests try to initialize the database pool at the same time, only one instance will be created. - Connection Pooling: The
create_engine()
function initializes a connection pool that can be reused across requests. - Make sure to replace your database URL.
Defining SQLAlchemy Models
Now let’s define the database models. These models correspond to the database tables.
models.py — SQLAlchemy Models
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
# SQLAlchemy's declarative base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
def __repr__(self):
return f"<User(id={self.id}, name={self.name}, email={self.email})>"
- Base Class:
declarative_base()
is a factory function that constructs a base class for all our models. - User Model: We define a
User
model with three fields (id
,name
, andemail
). Theid
is the primary key, andemail
is marked as unique.
FastAPI Route Example Using Singleton Pattern
Now that we’ve implemented the Singleton Pattern and defined our models, let’s tie everything together in the FastAPI routes.
app.py — FastAPI Routes
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from .db import get_db
from .models import User
app = FastAPI()
@app.post("/users")
def create_user(name: str, email: str, db: Session = Depends(get_db)):
new_user = User(name=name, email=email)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user
@app.get("/users")
def get_users(db: Session = Depends(get_db)):
return db.query(User).all()
create_user
Route: Adds a new user to the database. The database session is injected using the get_db dependency, which ensures that the Singleton connection pool is reused.get_user
Route: Fetches all users from theusers
table using the shared database session.
The Request-Response Lifecycle
- Client Request: The client sends an HTTP request to the FastAPI application.
- FastAPI Application: The request is passed to the FastAPI app.
- Middleware: The app uses middleware to check if the database connection pool is already initialized via the Singleton pattern.
- Singleton DB Pool: If the pool exists, it is used; otherwise, it is created.
- Database: The query is processed by the database, and the result is sent back through the same path.
- Client Response: The result is returned to the client.
The Singleton Pattern is a powerful design pattern for managing shared resources, such as database connections, in a FastAPI application. By ensuring only one instance of the connection pool is created, we can significantly improve resource efficiency and performance.
Now that you’ve seen how to implement the Singleton Pattern and use FastAPI with SQLAlchemy, you’re well-equipped to build scalable, efficient applications.