backend / graphql
Serveur GraphQL en pratique : Strawberry + FastAPI
Explication
Ce que vous allez apprendre
- Assembler un schéma Strawberry complet (
Query,Mutation, types,input) dans un module dédié - Brancher ce schéma sur une application FastAPI via
GraphQLRouter - Réutiliser le système
Dependsde FastAPI pour construire lecontextGraphQL - Comprendre l'intérêt et le risque de
graphiql=Trueselon l'environnement - Écrire un test d'intégration simple sur le endpoint
/graphql
Dans quel contexte ?
Une équipe backend a déjà une API FastAPI en production, avec ses routes REST, son système d'authentification par dépendances (Depends) et sa connexion base de données. Elle souhaite ajouter GraphQL progressivement, sans dupliquer toute la logique d'authentification et d'accès aux données déjà écrite pour REST. C'est un scénario extrêmement courant : GraphQL vient rarement remplacer une API existante d'un coup, il s'ajoute à côté.
D'abord, organiser le schéma dans son propre module
Un schéma Strawberry complet rassemble les types de sortie (Produit), les types d'entrée (CreerProduitInput), et les deux racines Query et Mutation, typiquement dans un fichier app/graphql/schema.py. Cette séparation garde le code GraphQL lisible et indépendant du reste de l'application, un peu comme un routeur FastAPI dédié à un domaine métier.
Une fois le schéma prêt, il faut le brancher sur l'application
strawberry.fastapi.GraphQLRouter transforme ce schéma en un routeur FastAPI classique, qu'on inclut avec app.include_router(graphql_app, prefix="/graphql") exactement comme n'importe quel autre routeur de l'application. Le endpoint GraphQL devient alors un endpoint FastAPI parmi d'autres, cohabitant naturellement avec les routes REST existantes.
Ensuite, la vraie question : d'où viennent l'utilisateur et la base de données dans le resolver ?
C'est le rôle du context_getter : une fonction, elle-même une dépendance FastAPI, qui construit le dictionnaire context transmis à chaque resolver. En réutilisant les mêmes Depends que les routes REST (get_db_session, obtenir_utilisateur_optionnel), l'équipe évite de dupliquer la logique d'authentification pour GraphQL : le même code sert les deux mondes.
| Élément | Rôle | Réutilisé depuis REST ? |
|---|---|---|
context_getter | Construit le context pour chaque requête GraphQL | Oui, via les mêmes Depends |
GraphQLRouter | Expose le schéma comme routeur FastAPI | Nouveau, mais s'intègre nativement |
graphiql=True | Console interactive de test dans le navigateur | À désactiver en production |
Il reste un réglage de sécurité à ne jamais oublier
graphiql=True expose une interface graphique interactive très pratique en développement pour explorer le schéma et tester des requêtes. En production, elle doit être désactivée explicitement (graphiql=False), car elle révèle la structure complète du schéma à n'importe qui accède au endpoint, y compris à un attaquant en reconnaissance.
Piège fréquent
Oublier de désactiver graphiql en production est une erreur de configuration fréquente et facile à automatiser dans un pipeline de déploiement : vérifie que la variable qui contrôle ce paramètre dépend bien de l'environnement (dev/staging/production), jamais d'une valeur codée en dur à True.
Bonne pratique
Teste ton endpoint GraphQL avec le TestClient de FastAPI exactement comme une route REST classique : un simple client.post("/graphql", json={"query": "..."}) suffit pour écrire des tests d'intégration rapides, sans dépendance externe.
Maintenant que le serveur tourne, la prochaine leçon regarde l'autre bout de la chaîne : comment consommer cette API GraphQL depuis un client JavaScript, avec du fetch simple ou avec Apollo Client pour une application React plus riche.
Commandes & code
Serveur GraphQL en pratique : Strawberry + FastAPI
# app/graphql/schema.py : assembler Query, Mutation et types en un schéma complet
import strawberry
from typing import Optional
@strawberry.type
class Produit:
id: strawberry.ID
nom: str
prix: float
@strawberry.input
class CreerProduitInput:
nom: str
prix: float
categorie_id: strawberry.ID
@strawberry.type
class Query:
@strawberry.field
async def produit(self, info: strawberry.Info, id: strawberry.ID) -> Optional[Produit]:
service = info.context["produit_service"]
return await service.trouver(id)
@strawberry.field
async def produits(self, info: strawberry.Info, limite: int = 20) -> list[Produit]:
service = info.context["produit_service"]
return await service.lister(limite)
@strawberry.type
class Mutation:
@strawberry.mutation
async def creer_produit(self, info: strawberry.Info, input: CreerProduitInput) -> Produit:
utilisateur = info.context.get("utilisateur")
if utilisateur is None:
raise Exception("Authentification requise")
service = info.context["produit_service"]
return await service.creer(input)
schema = strawberry.Schema(query=Query, mutation=Mutation)# app/main.py : brancher le schéma GraphQL sur une app FastAPI existante
from fastapi import FastAPI, Depends, Request
from strawberry.fastapi import GraphQLRouter
from app.graphql.schema import schema
from app.services.produit_service import ProduitService
from app.core.database import get_db_session
from app.core.security import obtenir_utilisateur_optionnel
async def get_context(
request: Request,
db=Depends(get_db_session),
utilisateur=Depends(obtenir_utilisateur_optionnel),
):
return {
"produit_service": ProduitService(db),
"utilisateur": utilisateur,
"request": request,
}
graphql_app = GraphQLRouter(
schema,
context_getter=get_context,
graphiql=True, # console interactive GraphiQL en dev (désactiver en prod)
)
app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")# Le context_getter réutilise les mêmes dépendances FastAPI (Depends) que le reste de l'API
# -> cohérence totale entre les routes REST existantes et le endpoint GraphQL ajouté à côté
# Test du endpoint avec le client de test FastAPI
from fastapi.testclient import TestClient
def test_query_produit(client: TestClient):
reponse = client.post("/graphql", json={
"query": "{ produit(id: \"1\") { nom prix } }"
})
assert reponse.status_code == 200
assert reponse.json()["data"]["produit"]["nom"] == "Clavier"uvicorn app.main:app --reload
# GraphiQL accessible sur http://localhost:8000/graphql en devRésumé
strawberry.fastapi.GraphQLRouters'intègre comme n'importe quel routeur FastAPI (include_router).context_getterréutilise le systèmeDependsde FastAPI : mêmes dépendances DB/auth que le reste de l'API.graphiql=Trueexpose une console interactive de test en dev, à désactiver explicitement en production.- Le endpoint GraphQL cohabite naturellement avec des routes REST existantes dans la même application FastAPI.
Exercices pratiques
Mission : brancher GraphQL sur une API FastAPI existante sans dupliquer l'auth
Objectif : Réutiliser les dépendances FastAPI existantes dans le context_getter GraphQL, et sécuriser le réglage graphiql selon l'environnement.
Contexte
L'équipe backend a déjà des routes REST en production, avec Depends(get_db_session) et Depends(obtenir_utilisateur_optionnel) largement testées. Le tech lead refuse catégoriquement de dupliquer cette logique d'authentification pour le nouveau endpoint /graphql : le context_getter doit réutiliser exactement les mêmes dépendances.