DEV Community

Cover image for FastAPI Meets GraphQL: Build a Production-Ready Starter Template
Adebayo Adewumi
Adebayo Adewumi

Posted on

FastAPI Meets GraphQL: Build a Production-Ready Starter Template

Building a FastAPI + GraphQL Starter Template with PostgreSQL: Part 1

Introduction

In this tutorial, we’ll build a production-ready FastAPI application with GraphQL support and PostgreSQL integration from scratch. This first part covers:

  • Setting up a clean project structure
  • Async database connectivity with SQLAlchemy & asyncpg
  • Basic GraphQL API with Strawberry
  • Health check endpoints
  • Environment-based configuration

By the end, you’ll have a starter template ready for development and future enhancements.


Prerequisites

Make sure you have:

  • Python 3.11+
  • PostgreSQL running locally or remotely
  • Git for version control
  • Basic familiarity with Python async/await

Step 1: Project Initialization

Create your project folder and initialize git:

mkdir fastapi-graphql-starter-template
cd fastapi-graphql-starter-template
git init
Enter fullscreen mode Exit fullscreen mode

Step 2: Project Structure

A clean folder structure is key for maintainability:

fastapi-graphql-starter-template/
├── app/
│   ├── main.py                 # FastAPI entry point
│   ├── api/routes/             # REST endpoints (health checks, etc.)
│   ├── core/                   # Config, database, logging
│   ├── graphql/                # GraphQL schema, queries, mutations
│   ├── models/                 # SQLAlchemy models
│   └── services/               # Business logic
├── migrations/                  # Alembic migrations
├── tests/                       # Unit and integration tests
├── .env.example                 # Environment template
├── requirements.in / requirements.txt
├── run.sh                       # Convenience script to run server
└── README.md
Enter fullscreen mode Exit fullscreen mode

Create the directories:

mkdir -p app/{api/routes,core,graphql,models,services} migrations tests
touch app/{__init__.py,main.py}
touch app/api/routes/__init__.py
touch app/core/{__init__.py,config.py,database.py,logging.py}
touch app/graphql/{__init__.py,schema.py,queries.py,mutations.py}
touch app/models/{__init__.py,user.py}
touch app/services/__init__.py
Enter fullscreen mode Exit fullscreen mode

Step 3: Dependencies and What They Do

Here’s a brief overview of the main dependencies we use:

Dependency Purpose Link
FastAPI High-performance Python web framework with async support Docs
Uvicorn ASGI server to run FastAPI apps Docs
SQLAlchemy ORM to interact with relational databases; we use async support Docs
asyncpg Fast async PostgreSQL driver Docs
Alembic Database migrations for SQLAlchemy models Docs
Strawberry GraphQL Type-safe GraphQL library for Python Docs
Pydantic Data validation and settings management Docs
python-dotenv Load environment variables from .env files Docs

Together, these tools provide a scalable, type-safe, and async-ready FastAPI stack with proper database management and GraphQL support.

Install them with pip-tools:

pip install pip-tools
echo "
fastapi==0.109.0
uvicorn[standard]==0.27.0
sqlalchemy[asyncio]==2.0.25
asyncpg==0.29.0
alembic==1.13.1
strawberry-graphql[fastapi]==0.219.0
pydantic==2.5.3
pydantic-settings==2.1.0
python-dotenv==1.0.0
" > requirements.in

pip-compile requirements.in
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Step 4: Configuration Management

Use Pydantic Settings to load environment variables:

from pydantic_settings import BaseSettings
from pydantic import Field

class Settings(BaseSettings):
    app_name: str = Field(default="FastAPI GraphQL Starter")
    debug: bool = Field(default=True)
    environment: str = Field(default="development")
    database_url: str = Field(..., env="DATABASE_URL")

    class Config:
        env_file = ".env"

settings = Settings()
Enter fullscreen mode Exit fullscreen mode

.env.example:

APP_NAME=FastAPI GraphQL Starter
DEBUG=True
ENVIRONMENT=development
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/dbname
HOST=0.0.0.0
PORT=8000
Enter fullscreen mode Exit fullscreen mode

Step 5: Async Database Setup

from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import declarative_base
from app.core.config import settings
import ssl

Base = declarative_base()

ssl_context = None
if settings.environment == "production":
    ssl_context = ssl.create_default_context()
    ssl_context.check_hostname = False
    ssl_context.verify_mode = ssl.CERT_NONE

engine = create_async_engine(
    settings.database_url,
    echo=settings.debug,
    future=True,
    connect_args={"ssl": ssl_context} if ssl_context else {}
)

AsyncSessionLocal = async_sessionmaker(
    engine,
    class_=AsyncSession,
    expire_on_commit=False
)
Enter fullscreen mode Exit fullscreen mode
  • SQLAlchemy manages models and sessions
  • asyncpg handles fast asynchronous PostgreSQL connections
  • SSL support configurable for production environments

Step 6: Create Your First Model

from sqlalchemy import Column, Integer, String, DateTime, func
from app.core.database import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    email = Column(String, unique=True, nullable=False)
    username = Column(String, unique=True, nullable=False)
    hashed_password = Column(String, nullable=False)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())
Enter fullscreen mode Exit fullscreen mode

Step 7: Database Migrations with Alembic

alembic init migrations
Enter fullscreen mode Exit fullscreen mode

Configure env.py for async migrations (import all models for autogenerate):

from app.core.database import Base
import app.models.user
target_metadata = Base.metadata
Enter fullscreen mode Exit fullscreen mode

Create your first migration:

alembic revision --autogenerate -m "Initial migration with User model"
alembic upgrade head
Enter fullscreen mode Exit fullscreen mode

Step 8: GraphQL with Strawberry

import strawberry

@strawberry.type
class Query:
    @strawberry.field
    def hello(self) -> str:
        return "Hello from GraphQL!"
Enter fullscreen mode Exit fullscreen mode
  • Integrate with FastAPI via GraphQLRouter
  • Supports queries, mutations, and subscriptions

Learn more about Strawberry


Step 9: FastAPI Application Setup

from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
from app.core.config import settings
from app.core.database import init_db, close_db
from app.graphql.schema import schema
from app.api.routes.health import router as health_router

app = FastAPI(title=settings.app_name, debug=settings.debug)

# REST health check
app.include_router(health_router, prefix="/api", tags=["health"])

# GraphQL endpoint
graphql_app = GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")

@app.get("/")
async def root():
    return {"message": "FastAPI GraphQL Starter", "docs": "/docs", "graphql": "/graphql"}
Enter fullscreen mode Exit fullscreen mode

Step 10: Running the Application

Create run.sh:

#!/bin/bash
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
Enter fullscreen mode Exit fullscreen mode
chmod +x run.sh
./run.sh
Enter fullscreen mode Exit fullscreen mode

Visit:


Next Steps (Part 2)

  • JWT authentication (access + refresh tokens)
  • Password hashing (bcrypt)
  • User registration/login mutations
  • Role-based access control
  • Protected GraphQL queries and mutations

Conclusion

You now have a solid foundation:

  • ✅ Clean, scalable project structure
  • ✅ Async PostgreSQL integration with SQLAlchemy + asyncpg
  • Alembic migrations configured
  • GraphQL API with Strawberry
  • Health monitoring endpoint
  • Environment-based configuration

The complete code is available on GitHub.

Key Takeaways:

  1. Proper project structure prevents technical debt
  2. Async/await improves performance
  3. Type-safe GraphQL APIs with Strawberry
  4. Alembic manages database schema evolution

Stay tuned for Part 2, where we’ll add authentication, security, and protected GraphQL queries!


Top comments (0)