Retour au cours

backend / graphql

Serveur GraphQL en pratique : Strawberry + FastAPI

Leçon 121 exercice

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 Depends de FastAPI pour construire le context GraphQL
  • Comprendre l'intérêt et le risque de graphiql=True selon 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émentRôleRéutilisé depuis REST ?
context_getterConstruit le context pour chaque requête GraphQLOui, via les mêmes Depends
GraphQLRouterExpose le schéma comme routeur FastAPINouveau, mais s'intègre nativement
graphiql=TrueConsole 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

python
# 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)
python
# 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")
python
# 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"
bash
uvicorn app.main:app --reload
# GraphiQL accessible sur http://localhost:8000/graphql en dev

Résumé

  • strawberry.fastapi.GraphQLRouter s'intègre comme n'importe quel routeur FastAPI (include_router).
  • context_getter réutilise le système Depends de FastAPI : mêmes dépendances DB/auth que le reste de l'API.
  • graphiql=True expose 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

1 disponible
1

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.

Résoudre l’exercice →