Django Ninja: a forma moderna, simples e escalável de criar APIs no Django

Published on: 2025-12-26
Post image
pt django-ninja api-django criar-api-com-django django-ninja-tutorial django-api-moderna pydantic-django django-ninja-schemas django-ninja-autenticacao django-ninja-async django-ninja-celery

Django Ninja é uma biblioteca para criar APIs (interfaces de programação que expõem dados e ações via HTTP) dentro de projetos Django. A proposta central é reduzir camadas de configuração e tornar o desenvolvimento mais direto, sem abrir mão de organização, validação e integração com recursos do próprio Django.

Além de rotas rápidas que retornam JSON, Django Ninja se destaca por unir validação baseada em esquemas, geração de documentação automática e compatibilidade com padrões modernos como autenticação modular, uploads, tarefas em segundo plano e endpoints assíncronos. O resultado é um estilo de API mais simples de ler e manter, especialmente em projetos que precisam crescer com clareza estrutural.

O que é Django Ninja e por que ele muda o estilo de APIs no Django

Django Ninja é um framework de API que se integra ao Django e usa Pydantic, uma biblioteca de validação e tipagem de dados baseada em anotações de tipos. Em vez de exigir múltiplas classes intermediárias para cada recurso, ele permite definir rotas e contratos de dados de forma direta. Isso reduz a quantidade de “cola” entre camadas, deixando a lógica do endpoint mais evidente. A integração com o Django continua forte, incluindo ORM (camada de acesso ao banco do Django), autenticação e armazenamento de arquivos.

Uma API costuma organizar responsabilidades como validação de entrada, serialização de saída e controle de acesso. Em muitos estilos tradicionais, essas responsabilidades se espalham por várias classes, o que aumenta o custo de configuração. No Django Ninja, os esquemas descrevem dados de entrada e saída com tipagem explícita, e a própria biblioteca cuida de validar e converter. Essa abordagem costuma tornar o fluxo de dados mais previsível, com menos código repetitivo.

Configuração inicial e criação de endpoints básicos

A configuração inicial do Django Ninja é composta por instalar o pacote, criar uma instância de API e registrar as URLs no Django. A instância de API centraliza rotas e configurações, funcionando como ponto de entrada para endpoints. Um endpoint é uma função associada a um método HTTP, como GET ou POST, e um caminho, como /greet. A resposta geralmente é um dicionário Python, que é convertido automaticamente em JSON.

O exemplo a seguir mostra uma configuração mínima, incluindo o registro da URL e um endpoint simples. Ele apresenta o formato típico: criar NinjaAPI, incluir api.urls nas rotas do Django e declarar uma função decorada. Esse padrão reduz a necessidade de componentes extras quando a necessidade é direta. A seguir está um exemplo completo e funcional desse início.

from django.urls import path
from ninja import NinjaAPI

api = NinjaAPI()

@api.get("/greet")
def greet(request):
    return {"message": "Hello, Django Ninja!"}

urlpatterns = [
    path("api/", api.urls),
]

Schemas e validação: a base para entradas e saídas consistentes

Schema é um “contrato” de dados que descreve campos, tipos e regras de validação. No Django Ninja, schemas geralmente são criados com Pydantic, o que permite validar automaticamente entradas de requisições e padronizar respostas. Quando um schema define um campo como e-mail, por exemplo, a validação pode rejeitar entradas inválidas antes da lógica principal. Isso reduz verificações manuais e melhora a previsibilidade do comportamento da API.

Em APIs, validação de entrada evita dados quebrados chegando à camada de banco, e serialização de saída evita expor campos indevidos. Em vez de aceitar um dicionário arbitrário, um endpoint pode receber um objeto já validado. O exemplo abaixo mostra um schema de cadastro com validação de e-mail, usando EmailStr. Ele ilustra como a API pode depender do schema para garantir formato e tipos.

from pydantic import BaseModel, EmailStr
from ninja import NinjaAPI

api = NinjaAPI()

class RegisterSchema(BaseModel):
    nome: str
    email: EmailStr
    senha: str

@api.post("/register")
def register_user(request, data: RegisterSchema):
    return {"message": f"Usuário {data.nome} registrado com sucesso"}

ModelSchema: geração de schemas diretamente a partir de modelos Django

ModelSchema é um recurso que gera um schema com base em um model do Django, evitando mapear campo por campo manualmente. Um model é a classe que representa uma tabela no banco de dados no ORM do Django. Ao declarar quais campos entram no schema, a API ganha consistência e reduz risco de exposição acidental de dados. Esse estilo acelera endpoints que listam ou detalham registros, mantendo controle do que sai como JSON.

Em um fluxo comum, o endpoint consulta o banco com o ORM e devolve o resultado para o Ninja serializar. Quando se define o parâmetro response, Django Ninja usa o schema para estruturar a resposta. Isso ajuda a manter o contrato estável mesmo quando o model evolui. O exemplo abaixo mostra um schema baseado no model de usuário e um endpoint que lista usuários.

from django.contrib.auth.models import User
from ninja import NinjaAPI, ModelSchema

api = NinjaAPI()

class UserSchema(ModelSchema):
    class Meta:
        model = User
        fields = ["id", "username", "email"]

@api.get("/users", response=list[UserSchema])
def list_users(request):
    return User.objects.all()

Meta vs Config em schemas: quando cada abordagem faz diferença

Em Django Ninja, Meta costuma ser usada para declarar vínculo com modelos Django, de modo parecido com o estilo de formulários e componentes do próprio Django. Já Config vem do Pydantic e controla comportamentos de serialização, como permitir ler atributos de objetos ORM. A opção orm_mode (em versões recentes do ecossistema Pydantic, esse conceito pode aparecer com nomes equivalentes) habilita a conversão de objetos para o schema sem transformar manualmente em dicionários. Essa distinção ajuda a equilibrar simplicidade e controle.

Em cenários puramente Django, Meta costuma ser suficiente para selecionar campos e padronizar respostas. Em cenários com objetos complexos, propriedades calculadas ou necessidade de leitura mais flexível de atributos, Config tende a ser útil. As duas abordagens podem coexistir dependendo do tipo de schema: ModelSchema para modelos e Schema para contratos personalizados. O exemplo abaixo mostra um schema com orm_mode para permitir serializar objetos ORM diretamente.

from django.contrib.auth.models import User
from ninja import NinjaAPI, Schema

api = NinjaAPI()

class UserOut(Schema):
    id: int
    username: str
    email: str

    class Config:
        orm_mode = True

@api.get("/users", response=list[UserOut])
def get_users(request):
    return User.objects.all()

Campos calculados e enriquecimento de resposta sem alterar o model

Algumas respostas precisam de dados derivados, como nome completo, status ou rótulos calculados. Em vez de modificar o model e impactar regras de domínio, é comum enriquecer a saída no schema. Um campo calculado é um valor produzido a partir de outros campos, mantendo a fonte de dados original intacta. Isso também ajuda a separar “como os dados são armazenados” de “como são apresentados pela API”.

Uma forma de fazer isso é declarar o campo no schema e preenchê-lo por propriedade ou método, desde que o schema tenha acesso aos atributos necessários. Esse padrão é útil para respostas que exigem apresentação consistente, mas sem criar colunas extras no banco. O exemplo abaixo ilustra a inclusão de nome_completo derivado de first_name e last_name. A seguir está um exemplo que demonstra esse enriquecimento com serialização baseada em ORM.

from django.contrib.auth.models import User
from ninja import NinjaAPI, Schema

api = NinjaAPI()

class UserOut(Schema):
    id: int
    username: str
    email: str
    nome_completo: str

    class Config:
        orm_mode = True

    @staticmethod
    def resolve_nome_completo(obj):
        primeiro = (obj.first_name or "").strip()
        ultimo = (obj.last_name or "").strip()
        return (primeiro + " " + ultimo).strip()

@api.get("/users", response=list[UserOut])
def get_users(request):
    return User.objects.all()

Uploads de arquivos: recebimento e leitura com integração ao ecossistema Django

Upload de arquivo é o processo de enviar um arquivo em uma requisição HTTP, geralmente como multipart/form-data. No Django tradicional, esse fluxo costuma envolver request.FILES e validações adicionais, além de tratamento de armazenamento. No Django Ninja, a tipagem com UploadedFile simplifica a leitura inicial e padroniza a interface do endpoint. A integração com o sistema de storage do Django permite salvar arquivos com backends configuráveis.

Existem diferenças importantes entre ler o conteúdo em memória e salvar diretamente em armazenamento. Ler tudo com file.read() pode ser adequado para arquivos pequenos, mas não é ideal para arquivos grandes. Em cenários reais, o uso de armazenamento e streaming tende a ser mais seguro para memória. O exemplo abaixo demonstra um endpoint de upload que lê o conteúdo e informa metadados básicos. Ele serve para ilustrar o mecanismo de recebimento e acesso a nome e tamanho.

from ninja import NinjaAPI
from ninja.files import UploadedFile

api = NinjaAPI()

@api.post("/upload")
def upload_file(request, arquivo: UploadedFile):
    conteudo = arquivo.read()
    return {"nome_arquivo": arquivo.name, "tamanho_bytes": len(conteudo)}

Autenticação: controle de acesso com esquemas simples e extensíveis

Autenticação é o mecanismo que identifica quem está fazendo a requisição, enquanto autorização decide o que pode ser acessado. Django Ninja suporta integração com padrões comuns, incluindo autenticação por sessão e mecanismos baseados em token. Um token é um valor secreto enviado pelo cliente, geralmente no cabeçalho Authorization, usado para validar acesso. A classe HttpBearer implementa o padrão Bearer token, que é amplamente usado em APIs.

Uma implementação simples pode comparar o token recebido com um valor esperado, mas em sistemas reais esse token costuma ser validado em banco, cache ou por assinatura criptográfica. Mesmo na forma básica, o padrão já separa a lógica de autenticação do endpoint. Quando o endpoint recebe um esquema de autenticação, o acesso pode ser bloqueado automaticamente se a validação falhar. O exemplo abaixo mostra um endpoint protegido por Bearer token, com validação direta para fins didáticos.

from ninja import NinjaAPI
from ninja.security import HttpBearer

api = NinjaAPI()

class AuthBearer(HttpBearer):
    def authenticate(self, request, token: str):
        return token == "meu-token-secreto"

@api.get("/protected", auth=AuthBearer())
def protected_route(request):
    return {"message": "Acesso permitido"}

Tarefas em segundo plano com Celery: desempenho e responsividade

Tarefas em segundo plano são atividades que não precisam terminar antes da API responder, como enviar e-mail, processar arquivos ou gerar relatórios. Celery é um sistema de fila de tarefas que distribui execução para “workers”, processos dedicados a rodar tarefas fora do ciclo da requisição. Um broker é o intermediário que guarda as mensagens da fila, e Redis é uma opção comum para esse papel. Esse modelo mantém a API responsiva, evitando que operações demoradas bloqueiem o tempo de resposta.

A estrutura típica inclui configuração do broker, declaração da tarefa com decorator e disparo usando .delay(). O endpoint então agenda a tarefa e retorna imediatamente uma confirmação. Isso também facilita escalabilidade, pois workers podem ser replicados conforme a demanda. O exemplo abaixo mostra uma configuração essencial no settings, uma tarefa compartilhada e um endpoint que agenda o envio. Ele foca no padrão de integração, deixando a lógica interna de envio como detalhe de domínio.

# settings.py
CELERY_BROKER_URL = "redis://localhost:6379/0"
from celery import shared_task
from django.contrib.auth.models import User

@shared_task
def enviar_email_boas_vindas(user_id: int):
    user = User.objects.get(id=user_id)
    # lógica de envio de e-mail
    return f"E-mail enviado para {user.email}"
from ninja import NinjaAPI
from .tasks import enviar_email_boas_vindas

api = NinjaAPI()

@api.post("/send-email")
def trigger_email(request, user_id: int):
    enviar_email_boas_vindas.delay(user_id)
    return {"status": "tarefa_agendada"}

Capacidades assíncronas: endpoints async e I/O não bloqueante

Assíncrono significa que uma função pode aguardar operações sem bloquear a execução do servidor, liberando recursos para outras requisições. Em APIs modernas, isso é especialmente útil para I/O, como chamadas a serviços externos e operações de rede. Django Ninja permite declarar rotas com async def, alinhando-se ao ecossistema async do Python. Esse modelo melhora a eficiência em cenários de alta concorrência, principalmente quando há muitas esperas por rede.

Um exemplo simples é simular uma espera com asyncio.sleep, demonstrando o formato assíncrono. Em cenários reais, a vantagem vem de chamadas HTTP assíncronas ou bibliotecas que suportam async. Mesmo quando o ORM do Django é usado de forma síncrona, endpoints async ainda podem ser úteis ao integrar serviços externos. O exemplo abaixo mostra um endpoint assíncrono que espera um segundo e retorna uma mensagem. Ele evidencia o formato e o fluxo básico de await.

import asyncio
from ninja import NinjaAPI

api = NinjaAPI()

@api.get("/async-data")
async def get_async_data(request):
    await asyncio.sleep(1)
    return {"message": "Resposta atrasada em 1 segundo"}

Integração com serviços externos: consumo de APIs com HTTP assíncrono

Integração com serviços externos é comum quando uma aplicação precisa consultar dados de parceiros, sistemas legados ou microserviços. Em vez de bloquear a execução durante uma requisição HTTP, uma biblioteca assíncrona como httpx permite aguardar a resposta sem travar o processamento de outras requisições. Esse padrão é importante para manter estabilidade quando múltiplas integrações acontecem ao mesmo tempo. O resultado é melhor uso de recursos do servidor, principalmente em cargas concorrentes.

O fluxo consiste em criar um cliente assíncrono, fazer a chamada e devolver o JSON recebido, quando apropriado. Também é comum tratar erros, timeouts e respostas inválidas, mas o núcleo do padrão é a chamada não bloqueante. O exemplo abaixo mostra um endpoint que busca dados e retorna o conteúdo JSON da resposta. Ele ilustra como Django Ninja se encaixa bem com rotinas async para integrações. A seguir está um exemplo de consumo de API externa usando httpx.

import httpx
from ninja import NinjaAPI

api = NinjaAPI()

@api.get("/external-data")
async def get_external_data(request):
    async with httpx.AsyncClient(timeout=10) as client:
        resposta = await client.get("https://jsonplaceholder.typicode.com/posts")
        return resposta.json()

Arquiteturas híbridas: APIs HTTP e tempo real com WebSockets via Django Channels

WebSockets são conexões persistentes que permitem troca de mensagens em tempo real, úteis para chat, notificações e painéis ao vivo. Django Ninja foca em HTTP, mas pode coexistir com Django Channels, que é a solução comum no ecossistema Django para comunicação em tempo real. Uma arquitetura híbrida separa responsabilidades: Ninja atende rotas REST/HTTP e Channels atende rotas WebSocket. Essa separação mantém o desenho do sistema claro e evita misturar padrões de comunicação diferentes no mesmo componente.

Em Channels, um consumer é uma classe que lida com eventos de conexão, recebimento e envio de mensagens. A implementação assíncrona permite lidar com múltiplas conexões de forma eficiente. A comunicação costuma envolver JSON, pois é um formato leve e compatível com navegadores e serviços. O exemplo abaixo mostra um consumer simples que aceita conexão, recebe uma mensagem e devolve uma resposta. Ele ilustra o padrão básico de tempo real ao lado de uma API HTTP.

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        await self.accept()

    async def receive(self, text_data):
        data = json.loads(text_data)
        await self.send(text_data=json.dumps({"message": data.get("message", "")}))

Conclusão: o que torna Django Ninja mais do que “um framework rápido”

Django Ninja se destaca por reduzir atrito no desenvolvimento de APIs sem romper com o ecossistema Django. O uso de schemas para validação e serialização substitui camadas repetitivas, mantendo contratos claros para entrada e saída de dados. A geração de schemas a partir de modelos, a flexibilidade entre Meta e Config e o suporte a autenticação modular fortalecem organização e segurança.

Além do básico, a combinação com tarefas em segundo plano via Celery, endpoints assíncronos e integrações com serviços externos amplia a capacidade de lidar com cargas maiores e fluxos modernos. A convivência com soluções de tempo real como Channels permite desenhar aplicações híbridas com responsabilidades bem separadas. No conjunto, Django Ninja funciona como uma abordagem completa para APIs em Django, indo além de velocidade e focando também em estrutura, integração e escalabilidade.