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

--

--

Simform Engineering
Simform Engineering

Published in Simform Engineering

Our Engineering blog gives an inside look at our technologies from the perspective of our engineers.

Deepak Patidar
Deepak Patidar

Written by Deepak Patidar

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

No responses yet