Building a GraphQL Endpoints with FastAPI and Strawberry: An Exciting Adventure with Test-Driven Development! 🚀

Deepak Patidar
Simform Engineering
6 min readApr 8, 2024

--

Learn how to build a GraphQL API to manage students and colleges using FastAPI and Strawberry.

GraphQL is an effective query language for APIs, allowing customers to query their desired records. FastAPI, alternatively, is a modern-day internet framework for building APIs in Python, recognized for its velocity and simplicity of use.

Strawberry is a Python library for GraphQL, which presents a safe manner for defining GraphQL schemas.

This article will show you how to build a GraphQL API to manage students and colleges using FastAPI and Strawberry. We'll use SQLAlchemy to define our records model, Strawberry to create a GraphQL schema, and FastAPI to set up API endpoints.

Along the way, we can use pytest to ensure our API works perfectly. and introduce a subscription to real-time updates.

Prerequisites

Before we begin, ensure you have the subsequent setup:

  • Python 3.6+
  • Pip (Python package manager)

Setup

1. Create a New Directory

Start by growing a new directory for our project and navigating it into:

mkdir student-api && cd student-api

2. Set Up a Virtual Environment

Next, create a virtual environment and activate it:

python3 -m venv venv && source venv/bin/activate

3. Install Required Packages

Install the necessary packages using pip:

pip install fastapi sqlalchemy strawberry-graphql pytest uvicorn[standard] httpx

Define the Data Models

We’ll start by defining our data models for students and colleges using SQLAlchemy. Create a models.py file and define the models as below:

# models.py
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class College(Base):
__tablename__ = "colleges"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
location = Column(String)

class Student(Base):
__tablename__ = "students"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
age = Column(Integer)
college_id = Column(Integer, ForeignKey("colleges.id"))

Connect to the Database

Now, connect to the database and define a session to run queries. Create a database.py file and add the following code:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base


DATABASE_URI = "sqlite:///./students.db"
engine = create_engine(DATABASE_URI)
Base.metadata.create_all(bind=engine)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
db = SessionLocal()
try:
return db
finally:
db.close()

Define the GraphQL Schema

Define the GraphQL schema using Strawberry. Create a schema.py file and add the following code:

import asyncio
from typing import List, AsyncGenerator
import strawberry
from strawberry import Schema

from models import College, Student
from database import get_db

@strawberry.type
class CollegeType:
id: int
name: str
location: str

@strawberry.type
class StudentType:
id: int
name: str
age: int
college_id: int

@strawberry.type
class Query:
@strawberry.field
async def colleges(self) -> List[CollegeType]:
db=get_db()
colleges = db.query(College).all()
return [CollegeType(id=college.id, name=college.name, location=college.location) for college in colleges]

@strawberry.field
async def students(self) -> List[StudentType]:
db=get_db()
students = db.query(Student).all()
return [StudentType(id=student.id, name=student.name, age=student.age, college_id=student.college_id) for student in students]

Implement GraphQL Mutations

After that, add mutations to create, update, and delete students and colleges. Update the schema.py file with the following code:

@strawberry.type
class Mutation:
@strawberry.mutation
async def create_college(self, name: str, location: str) -> CollegeType:
db=get_db()
college = College(name=name, location=location)
db.add(college)
db.commit()
db.refresh(college)
return CollegeType(id=college.id, name=college.name, location=college.location)

@strawberry.mutation
async def create_student(self, name: str, age: int, college_id: int) -> StudentType:
db=get_db()
college = db.query(College).filter(College.id == college_id).first()
if not college:
raise ValueError("College not found")
student = Student(name=name, age=age, college_id=college_id)
db.add(student)
db.commit()
db.refresh(student)
return StudentType(id=student.id, name=student.name, age=student.age, college_id=student.college_id)

Create the FastAPI Application

Now, implement the FastAPI application and integrate it with Strawberry. Create a main.py file and add the following code:

from fastapi import FastAPI
from strawberry.asgi import GraphQL

from schema import schema

app = FastAPI()

@app.get("/")
async def index():
return {"message": "Welcome to the Student API"}

app.add_route("/graphql", GraphQL(schema))

Test-Driven Development (TDD) Approach

We will use Test-Driven Development (TDD) methodologies to ensure our API functions correctly and bug-free. Write the following test cases in a test_student.py file within a tests directory:

import pytest
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from models import Base, College, Student
from main import app


DATABASE_URL = "sqlite+aiosqlite:///./test.db"

@pytest.fixture
async def async_session() -> AsyncSession:
engine = create_async_engine(DATABASE_URL, echo=True)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
async with engine.begin() as connection:
await connection.run_sync(Base.metadata.create_all)
async with async_session() as session:
async with session.begin():
yield session
await session.execute("DELETE FROM students")
await session.execute("DELETE FROM colleges")

@pytest.fixture
async def async_client(async_session: AsyncSession):
async with AsyncClient(app=app, base_url="http://testserver") as ac:
yield ac

@pytest.mark.asyncio
async def test_graphql_queries(async_session: AsyncSession, async_client: AsyncClient):
async with async_session() as session:
async with session.begin():
# Create some test data
college = College(name="Test College", location="Test Location")
session.add(college)
await session.commit()

# Test query
query = """
query {
colleges {
id
name
location
}
}
"""
response = await async_client.post("/graphql", json={"query": query})
assert response.status_code == 200
data = response.json()
assert "data" in data
assert "colleges" in data["data"]
assert len(data["data"]["colleges"]) == 1

@pytest.mark.asyncio
async def test_graphql_mutations(async_session: AsyncSession, async_client: AsyncClient):
# Test mutation
mutation = """
mutation {
create_college(name: "New College", location: "New Location") {
id
name
location
}
}
"""
response = await async_client.post("/graphql", json={"query": mutation})
assert response.status_code == 200
data = response.json()
assert "data" in data
assert "create_college" in data["data"]
assert data["data"]["create_college"]["name"] == "New College"
assert data["data"]["create_college"]["location"] == "New Location"

@pytest.mark.asyncio
async def test_graphql_subscription(async_session: AsyncSession, async_client: AsyncClient):
# Test subscription
subscription = """
subscription {
studentAdded(collegeId: 1) {
id
name
age
collegeId
}
}
"""
async with async_client.websocket_connect("/graphql") as ws:
await ws.send_json({"type": "connection_init"})
await ws.send_json({"id": "1", "type": "start", "payload": {"query": subscription}})
response = await ws.receive_json()
assert response["type"] == "data"
assert "studentAdded" in response["payload"]["data"]
assert response["payload"]["data"]["studentAdded"]["id"] is not None

Implement Subscriptions

1. Define the Subscription

Add the following code to schema.py:

@strawberry.type
class Subscription:
@strawberry.subscription
async def student_added(self, college_id: int) -> StudentType:
async for student in student_stream(college_id):
yield student

2. Define the Stream Function

Add the following code to schema.py:

async def student_stream(college_id: int) -> AsyncGenerator[StudentType, None]:
while True:
await asyncio.sleep(5) # Simulate real-time updates
db=get_db()
student = db.query(Student).filter(Student.college_id == college_id).order_by(Student.id.desc()).first()
if student:
yield StudentType(id=student.id, name=student.name, age=student.age, college_id=student.college_id)

3. Update the Schema

Update the schema definition in schema.py to include the Subscription:

schema = Schema(query=Query, mutation=Mutation, subscription=Subscription)

Run GraphQL API Over Client

When you run the below command on the terminal:

 uvicorn main:app --reload

The app will enable localhost with port 8000

http://127.0.0.1:8000/graphql

You can directly go to the web page and see the GraphQL webpage, where you can run GraphQL queries, mutations, and subscriptions.

GraphQL Interface

Or else, use a third-party tool like Altair. Download it from here.

4. Test the Mutation

You can test the mutation using a GraphQL client by running the following mutation query:

mutation {
createStudent(name: "deepak", age: 18, collegeId: 5) {
id
name
age
collegeId
}
}

It will create data in the database as given in the query.

GraphQL Mutation
Mutation

5. Test the Query

You can test the Query using a GraphQL client, Query the things or columns, or do whatever is needed.

Query to the Collage by running the following query:

query {
colleges {
id
name
location
}
}

It will provide a list of data that includes defined query columns like id, name, and location.

GraphQL Query
Query

6. Test the Subscription

You can test the subscription using a GraphQL client that supports subscriptions, like GraphQL Altair.

Subscribe to the student_added subscription by running the following subscription query:

subscription {
studentAdded(collegeId: 1) {
id
name
age
}
}

Whenever a new student is added to college with collegeId 1, you should receive real-time updates.

Subscribe
Subscription

Conclusion

Congratulations on completing this journey of constructing a GraphQL API with FastAPI and Strawberry! 🎉 Now, it’s your turn to create your API. Share your ideas and projects in the comments below!

Keep coding, and may your APIs be swift and your queries precise!😉

For more updates on the latest tools and technologies, follow the Simform Engineering blog.

Follow us: Twitter | LinkedIn

--

--

Deepak Patidar
Simform Engineering

Memory updated "Passionate Python Developer Crafting Code and Sharing it with the World"