Criando API GraphQL com Python — 2025 | Python Brasil

Aprenda a criar APIs GraphQL com Python usando Strawberry e FastAPI. Tutorial com queries, mutations, subscriptions e boas praticas.

6 min de leitura Equipe Python Brasil

GraphQL e uma linguagem de consulta para APIs que permite ao cliente solicitar exatamente os dados que precisa. Diferente de REST, onde cada endpoint retorna uma estrutura fixa, GraphQL oferece flexibilidade total nas consultas. Neste guia, a gente vai construir uma API GraphQL completa com Python usando Strawberry e FastAPI.

Por Que GraphQL?

Em APIs REST tradicionais, dois problemas sao comuns: overfetching (receber dados demais) e underfetching (precisar de multiplas requisicoes). GraphQL resolve ambos com uma unica requisicao flexivel.

Por exemplo, para obter o nome de um usuario e seus ultimos posts, em REST voce precisaria de duas requisicoes. Com GraphQL, uma so basta.

Configurando o Projeto

Instale as dependencias:

pip install strawberry-graphql[fastapi] fastapi uvicorn

Estrutura do projeto:

graphql-api/
    main.py
    schema.py
    models.py
    resolvers.py

Definindo os Tipos

No GraphQL, tudo comeca com a definicao dos tipos. Com Strawberry, usamos classes Python com decoradores:

# models.py
import strawberry
from typing import Optional
from datetime import datetime


@strawberry.type
class Usuario:
    id: int
    nome: str
    email: str
    bio: Optional[str] = None
    criado_em: datetime = strawberry.field(default_factory=datetime.now)


@strawberry.type
class Post:
    id: int
    titulo: str
    conteudo: str
    publicado: bool
    autor_id: int
    criado_em: datetime = strawberry.field(default_factory=datetime.now)


@strawberry.type
class Comentario:
    id: int
    texto: str
    autor_id: int
    post_id: int
    criado_em: datetime = strawberry.field(default_factory=datetime.now)

Banco de Dados Simulado

Para o tutorial, vamos usar dados em memoria:

# resolvers.py
from models import Usuario, Post, Comentario
from datetime import datetime

# Banco de dados em memoria
USUARIOS = [
    Usuario(id=1, nome="Ana Silva", email="ana@email.com", bio="Desenvolvedora Python"),
    Usuario(id=2, nome="Bruno Costa", email="bruno@email.com", bio="Engenheiro de dados"),
    Usuario(id=3, nome="Carla Souza", email="carla@email.com", bio=None),
]

POSTS = [
    Post(id=1, titulo="Introducao ao GraphQL", conteudo="GraphQL e uma linguagem...",
         publicado=True, autor_id=1),
    Post(id=2, titulo="Python e Dados", conteudo="Python e excelente para...",
         publicado=True, autor_id=2),
    Post(id=3, titulo="Rascunho", conteudo="Trabalho em progresso...",
         publicado=False, autor_id=1),
]

COMENTARIOS = [
    Comentario(id=1, texto="Otimo artigo!", autor_id=2, post_id=1),
    Comentario(id=2, texto="Muito util!", autor_id=3, post_id=1),
]

Criando Queries

Queries sao operacoes de leitura no GraphQL:

# schema.py
import strawberry
from typing import Optional
from models import Usuario, Post, Comentario
from resolvers import USUARIOS, POSTS, COMENTARIOS


@strawberry.type
class Query:
    @strawberry.field
    def usuarios(self) -> list[Usuario]:
        """Retorna todos os usuarios."""
        return USUARIOS

    @strawberry.field
    def usuario(self, id: int) -> Optional[Usuario]:
        """Busca usuario por ID."""
        for u in USUARIOS:
            if u.id == id:
                return u
        return None

    @strawberry.field
    def posts(self, publicado: Optional[bool] = None) -> list[Post]:
        """Retorna posts, opcionalmente filtrados por status."""
        if publicado is not None:
            return [p for p in POSTS if p.publicado == publicado]
        return POSTS

    @strawberry.field
    def post(self, id: int) -> Optional[Post]:
        """Busca post por ID."""
        for p in POSTS:
            if p.id == id:
                return p
        return None

    @strawberry.field
    def buscar_posts(self, termo: str) -> list[Post]:
        """Busca posts por termo no titulo ou conteudo."""
        termo_lower = termo.lower()
        return [
            p for p in POSTS
            if termo_lower in p.titulo.lower() or termo_lower in p.conteudo.lower()
        ]

Relacionamentos entre Tipos

A forca do GraphQL esta nos relacionamentos. Vamos conectar usuarios com seus posts:

import strawberry
from typing import Optional
from datetime import datetime
from resolvers import USUARIOS, POSTS, COMENTARIOS


@strawberry.type
class ComentarioType:
    id: int
    texto: str
    criado_em: datetime

    @strawberry.field
    def autor(self) -> "UsuarioType":
        for u in USUARIOS:
            if u.id == self.autor_id:
                return UsuarioType(
                    id=u.id, nome=u.nome, email=u.email, bio=u.bio
                )

    autor_id: strawberry.Private[int]
    post_id: strawberry.Private[int]


@strawberry.type
class PostType:
    id: int
    titulo: str
    conteudo: str
    publicado: bool
    criado_em: datetime

    @strawberry.field
    def autor(self) -> "UsuarioType":
        for u in USUARIOS:
            if u.id == self.autor_id:
                return UsuarioType(
                    id=u.id, nome=u.nome, email=u.email, bio=u.bio
                )

    @strawberry.field
    def comentarios(self) -> list[ComentarioType]:
        return [
            ComentarioType(
                id=c.id, texto=c.texto, autor_id=c.autor_id,
                post_id=c.post_id, criado_em=c.criado_em
            )
            for c in COMENTARIOS if c.post_id == self.id
        ]

    autor_id: strawberry.Private[int]


@strawberry.type
class UsuarioType:
    id: int
    nome: str
    email: str
    bio: Optional[str] = None

    @strawberry.field
    def posts(self, publicado: Optional[bool] = None) -> list[PostType]:
        resultado = [p for p in POSTS if p.autor_id == self.id]
        if publicado is not None:
            resultado = [p for p in resultado if p.publicado == publicado]
        return [
            PostType(
                id=p.id, titulo=p.titulo, conteudo=p.conteudo,
                publicado=p.publicado, autor_id=p.autor_id, criado_em=p.criado_em
            )
            for p in resultado
        ]

Criando Mutations

Mutations sao operacoes de escrita:

@strawberry.input
class CriarPostInput:
    titulo: str
    conteudo: str
    autor_id: int
    publicado: bool = False


@strawberry.input
class AtualizarPostInput:
    titulo: Optional[str] = None
    conteudo: Optional[str] = None
    publicado: Optional[bool] = None


@strawberry.type
class Mutation:
    @strawberry.mutation
    def criar_post(self, input: CriarPostInput) -> PostType:
        """Cria um novo post."""
        novo_id = max(p.id for p in POSTS) + 1
        novo_post = Post(
            id=novo_id,
            titulo=input.titulo,
            conteudo=input.conteudo,
            publicado=input.publicado,
            autor_id=input.autor_id
        )
        POSTS.append(novo_post)
        return PostType(
            id=novo_post.id, titulo=novo_post.titulo,
            conteudo=novo_post.conteudo, publicado=novo_post.publicado,
            autor_id=novo_post.autor_id, criado_em=novo_post.criado_em
        )

    @strawberry.mutation
    def atualizar_post(self, id: int, input: AtualizarPostInput) -> Optional[PostType]:
        """Atualiza um post existente."""
        for p in POSTS:
            if p.id == id:
                if input.titulo is not None:
                    p.titulo = input.titulo
                if input.conteudo is not None:
                    p.conteudo = input.conteudo
                if input.publicado is not None:
                    p.publicado = input.publicado
                return PostType(
                    id=p.id, titulo=p.titulo, conteudo=p.conteudo,
                    publicado=p.publicado, autor_id=p.autor_id,
                    criado_em=p.criado_em
                )
        return None

    @strawberry.mutation
    def deletar_post(self, id: int) -> bool:
        """Deleta um post por ID."""
        for i, p in enumerate(POSTS):
            if p.id == id:
                POSTS.pop(i)
                return True
        return False

Montando a Aplicacao

# main.py
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

# Importar Query e Mutation
schema = strawberry.Schema(query=Query, mutation=Mutation)

# Criar app FastAPI com GraphQL
app = FastAPI(title="API GraphQL com Python")
graphql_app = GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")

@app.get("/")
def index():
    return {"mensagem": "Acesse /graphql para usar a API GraphQL"}

Execute com:

uvicorn main:app --reload

Acesse http://localhost:8000/graphql para abrir o GraphiQL, a interface interativa do GraphQL.

Exemplos de Consultas

# Buscar usuario com seus posts
query {
  usuario(id: 1) {
    nome
    email
    posts(publicado: true) {
      titulo
      comentarios {
        texto
        autor {
          nome
        }
      }
    }
  }
}

# Criar um novo post
mutation {
  criarPost(input: {
    titulo: "Novo Post"
    conteudo: "Conteudo do post..."
    autorId: 1
    publicado: true
  }) {
    id
    titulo
    autor {
      nome
    }
  }
}

Tratamento de Erros

import strawberry

@strawberry.type
class ErroType:
    mensagem: str
    codigo: str

PostResultado = strawberry.union("PostResultado", [PostType, ErroType])

@strawberry.type
class MutationComErros:
    @strawberry.mutation
    def criar_post(self, input: CriarPostInput) -> PostResultado:
        autor = next((u for u in USUARIOS if u.id == input.autor_id), None)
        if not autor:
            return ErroType(mensagem="Autor nao encontrado", codigo="AUTOR_NAO_ENCONTRADO")
        # ... criar post normalmente

Boas Praticas

Ao construir APIs GraphQL com Python, siga estas recomendacoes:

  • Use DataLoaders para evitar o problema N+1 em relacionamentos
  • Implemente paginacao com cursor-based pagination para listas grandes
  • Adicione autenticacao e autorizacao nas mutations
  • Limite a profundidade das queries para prevenir abusos
  • Documente os tipos e campos com descricoes claras
  • Use input types para mutations com muitos parametros
  • Implemente rate limiting para proteger a API

Conclusao

GraphQL com Python usando Strawberry e FastAPI e uma combinacao moderna e produtiva para construir APIs flexiveis. Com tipos seguros, queries aninhadas e mutations bem definidas, voce oferece aos consumidores da API total controle sobre os dados que recebem. Comece com um schema simples e evolua conforme as necessidades do seu projeto.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português