Pourquoi FastAPI avec GraphQL ?
FastAPI est une fameuse bibliothèque Python pour construire des APIs rapides et efficaces, tandis que GraphQL est un langage de requêtes et d'interrogation pour les API qui permet aux clients de demander exactement ce qu'ils ont besoin. L'utilisation de FastAPI avec GraphQL offre plusieurs avantages :
- Contrôle total sur la réponse : Avec GraphQL, les clients peuvent spécifier exactement le champ dont ils ont besoin. Cela réduit le temps de réponse et économise des ressources serveur.
- Performance optimale : Les requêtes GraphQL permettent de récupérer les données nécessaires en une seule requête, ce qui améliore la performance.
- Documentation générée automatiquement : FastAPI génère automatiquement une documentation interactive pour votre API, facilitant le développement et l'utilisation.
Un cas d'usage concret serait un service e-commerce où un client peut demander des informations spécifiques sur un produit, comme son prix, sa description, et les images associées, tout en évitant de récupérer des données inutiles.
Prerequis
- Connaissances en Python (minimum 2 ans)
- Familiarité avec FastAPI
- Connaissance de base de GraphQL
- Un environnement Python configuré avec pip
Outils à installer :
- Python 3.7 ou plus récent
- FastAPI :
pip install fastapi - Uvicorn :
pip install uvicorn(pour l'exécution du serveur)
Concepts fondamentaux
Schéma GraphQL
Un schéma GraphQL définit le type de données que votre API peut retourner et comment ces types sont liés entre eux. Voici un exemple simple :
from fastapi import FastAPI
import graphene
app = FastAPI()
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return f"Hello {name}!"
schema = graphene.Schema(query=Query)
@app.get("/")
async def read_root():
query = '{ hello(name: "World") }'
result = schema.execute(query)
return {"data": str(result.data)}
Mutation GraphQL
Une mutation est une requête qui modifie le state de votre application. Voici un exemple :
class Mutation(graphene.ObjectType):
create_user = graphene.Field(User, name=graphene.String(required=True))
def mutate(self, info, name):
user = User(name=name)
return CreateUser(user=user)
schema = graphene.Schema(query=Query, mutation=Mutation)
@app.post("/")
async def create_user():
query = '''
mutation {
createUser(name: "John Doe") {
user {
name
}
}
}
'''
result = schema.execute(query)
return {"data": str(result.data)}
Resolver GraphQL
Un resolver est une fonction qui définit comment récupérer les données pour un champ spécifique dans le schéma. Voici un exemple :
def resolve_user(self, info):
# Logique pour récupérer l'utilisateur
user = User(id=1, name="John Doe")
return user
Query et Mutation en même temps
Vous pouvez combiner des queries et mutations dans une seule requête. Voici un exemple :
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
user = graphene.Field(User, id=graphene.Int(required=True))
def resolve_hello(self, info, name):
return f"Hello {name}!"
def resolve_user(self, info, id):
# Logique pour récupérer l'utilisateur
user = User(id=id, name="John Doe")
return user
class Mutation(graphene.ObjectType):
create_user = graphene.Field(User, name=graphene.String(required=True))
def mutate(self, info, name):
user = User(name=name)
return CreateUser(user=user)
schema = graphene.Schema(query=Query, mutation=Mutation)
@app.post("/")
async def execute_query():
query = '''
{
hello(name: "World"),
user(id: 1) {
id
name
}
}
'''
result = schema.execute(query)
return {"data": str(result.data)}
Mise en pratique : projet fil rouge
Nous allons créer un simple API de blog avec FastAPI et GraphQL. L'application permettra d'afficher les articles, de les créer, de les mettre à jour et de les supprimer.
Étape 1 : Configuration du projet
Créer un nouveau répertoire pour le projet :
mkdir fastapi-graphql-blog
cd fastapi-graphql-blog
Initialiser un environnement virtuel et installer les dépendances :
python -m venv venv
source venv/bin/activate # Sous Windows : `venv\Scripts\activate`
pip install fastapi uvicorn graphene graphene-sqlalchemy sqlalchemy
Étape 2 : Structure du projet
Créer la structure de fichiers suivante :
fastapi-graphql-blog/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── models.py
│ └── schema.py
└── requirements.txt
Étape 3 : Configuration des modèles SQLAlchemy
Créer le fichier app/models.py :
from sqlalchemy import create_engine, Column, Integer, String, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Article(Base):
__tablename__ = 'articles'
id = Column(Integer, primary_key=True, index=True)
title = Column(String(255), index=True)
content = Column(Text)
## Connexion à la base de données SQLite
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base.metadata.create_all(bind=engine)
Étape 4 : Configuration du schéma GraphQL
Créer le fichier app/schema.py :
from fastapi import FastAPI
import graphene
from sqlalchemy.orm import Session
from .models import Article, engine, SessionLocal
app = FastAPI()
## Définition des types de données
class ArticleType(graphene.ObjectType):
id = graphene.ID()
title = graphene.String()
content = graphene.String()
class Query(graphene.ObjectType):
article = graphene.Field(ArticleType, id=graphene.Int(required=True))
articles = graphene.List(ArticleType)
def resolve_article(self, info, id):
db = SessionLocal()
article = db.query(Article).filter(Article.id == id).first()
return article
def resolve_articles(self, info):
db = SessionLocal()
articles = db.query(Article).all()
return articles
class CreateArticleInput(graphene.InputObjectType):
title = graphene.String(required=True)
content = graphene.String(required=True)
class CreateArticle(graphene.Mutation):
class Arguments:
input = CreateArticleInput(required=True)
article = graphene.Field(ArticleType)
def mutate(self, info, input):
db = SessionLocal()
new_article = Article(title=input.title, content=input.content)
db.add(new_article)
db.commit()
return CreateArticle(article=new_article)
class Mutation(graphene.ObjectType):
create_article = CreateArticle.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
@app.post("/")
async def execute_query():
query = '''
mutation {
createArticle(input: {title: "GraphQL with FastAPI", content: "This is a sample article."}) {
article {
id
title
content
}
}
}
'''
result = schema.execute(query)
return {"data": str(result.data)}
Étape 5 : Exécution de l'application
Lancer le serveur FastAPI :
uvicorn app.main:app --reload
Accéder à l'interface d'exploration du schéma GraphQL via http://127.0.0.1:8000/graphql.
Erreurs frequentes et debugging
1. Erreur : MissingSchemaError
Code incorrect :
schema = graphene.Schema(query=Query)
Code correct :
schema = graphene.Schema(query=Query, mutation=Mutation)
2. Erreur : TypeError: 'NoneType' object is not callable
Code incorrect :
class Mutation(graphene.ObjectType):
create_article = CreateArticle.Field()
Code correct :
class Mutation(graphene.ObjectType):
create_article = CreateArticle.Field()
3. Erreur : AttributeError: module 'sqlalchemy.orm' has no attribute 'Session'
Code incorrect :
from sqlalchemy.orm import SessionLocal, Session
Code correct :
from sqlalchemy.orm import sessionmaker, Session
Pour aller plus loin
- Optimisation des requêtes GraphQL : Explorer les directives
@deferet@streampour améliorer les performances des requêtes complexes. - Intégration avec une base de données PostgreSQL : Utiliser SQLAlchemy pour connecter votre API à une base de données PostgreSQL plutôt que SQLite.
- Ajout d'authentification et d'autorisation : Implémenter des mécanismes d'authentification et d'autorisation pour sécuriser votre API.
Défi pratique : Ajouter une fonctionnalité qui permet de mettre à jour et de supprimer des articles via des mutations GraphQL.