Projeto: API web simples
LidoNesta aula, você construirá uma API web simples usando Flask, um framework leve em Python. Aprenderá a definir rotas, retornar dados em JSON e tratar erros de forma adequada, criando uma base sólida para projetos mais complexos.
Nesta aula, vamos colocar em prática os conceitos de desenvolvimento web com Python criando uma API simples, mas funcional. Uma API (Application Programming Interface) é um conjunto de regras que permite que diferentes sistemas se comuniquem. No contexto web, uma API geralmente expõe endpoints (URLs) que retornam dados em formatos como JSON, permitindo que clientes (como aplicativos front-end ou outros servidores) consumam esses dados.
Utilizaremos o Flask, um micro-framework Python que permite criar aplicações web de forma rápida e elegante. Flask é leve, fácil de aprender e amplamente utilizado em projetos pequenos e médios. Ao final desta aula, você terá uma API que gerencia uma lista de itens, com operações básicas de leitura e escrita, além de tratamento de erros personalizado.
Framework leve
O Flask é chamado de micro-framework porque mantém o núcleo simples e extensível. Ele não inclui muitas funcionalidades por padrão, como autenticação ou ORM, mas permite que você adicione apenas o que precisa através de extensões. Isso o torna ideal para APIs pequenas e para aprender os fundamentos do desenvolvimento web.
Para começar, você precisa instalar o Flask. Isso pode ser feito via pip, o gerenciador de pacotes do Python. Recomenda-se criar um ambiente virtual para isolar as dependências do projeto. No terminal, execute:
python -m venv venv
source venv/bin/activate # Linux/macOS
venv\Scripts\activate # Windows
pip install flaskDepois de instalado, crie um arquivo chamado app.py e importe o Flask. O código mínimo para uma aplicação é:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return 'Olá, mundo!'
if __name__ == '__main__':
app.run(debug=True)Quando você executa python app.py, o servidor de desenvolvimento é iniciado em http://localhost:5000. A função home é associada à rota raiz / e retorna uma string simples. O parâmetro debug=True ativa o modo de depuração, que recarrega o servidor automaticamente a cada alteração no código e exibe mensagens de erro detalhadas.
Rotas
Rotas são os caminhos que definem como a aplicação responde a diferentes URLs. No Flask, usamos o decorador @app.route() para associar uma função a uma URL específica. Além de rotas estáticas, podemos definir rotas dinâmicas, capturando partes da URL como variáveis.
Por exemplo, para uma API de itens, podemos ter as seguintes rotas:
from flask import Flask, request, jsonify
app = Flask(__name__)
# Dados em memória (simulando um banco de dados)
itens = []
@app.route('/itens', methods=['GET'])
def listar_itens():
return jsonify(itens)
@app.route('/itens/<int:id>', methods=['GET'])
def obter_item(id):
# Implementação posterior
pass
@app.route('/itens', methods=['POST'])
def criar_item():
# Implementação posterior
pass
@app.route('/itens/<int:id>', methods=['PUT'])
def atualizar_item(id):
# Implementação posterior
pass
@app.route('/itens/<int:id>', methods=['DELETE'])
def deletar_item(id):
# Implementação posterior
passAo especificar <int:id>, estamos dizendo que o parâmetro id deve ser um inteiro. O Flask converte automaticamente o valor da URL para o tipo indicado. Isso facilita a validação básica e evita erros de tipo. Além disso, o parâmetro methods permite restringir os métodos HTTP aceitos (GET, POST, PUT, DELETE, etc.).
Para testar as rotas, você pode usar ferramentas como Postman, Insomnia ou até mesmo o curl no terminal. Por exemplo, para listar os itens, você faria uma requisição GET para http://localhost:5000/itens. Inicialmente, a lista estará vazia, mas logo implementaremos a criação de itens.
JSON
JSON (JavaScript Object Notation) é o formato padrão para troca de dados em APIs web. O Flask fornece o método jsonify que converte um dicionário ou lista Python em uma resposta JSON com o cabeçalho Content-Type: application/json adequado.
Vamos implementar a criação e listagem de itens. Cada item será um dicionário com id, nome e descricao. Para gerar IDs únicos, podemos usar um contador simples:
from flask import Flask, request, jsonify
app = Flask(__name__)
itens = []
proximo_id = 1
@app.route('/itens', methods=['GET'])
def listar_itens():
return jsonify(itens)
@app.route('/itens', methods=['POST'])
def criar_item():
global proximo_id
dados = request.get_json()
if not dados or not 'nome' in dados:
return jsonify({'erro': 'O campo nome é obrigatório'}), 400
item = {
'id': proximo_id,
'nome': dados.get('nome'),
'descricao': dados.get('descricao', '')
}
itens.append(item)
proximo_id += 1
return jsonify(item), 201A função request.get_json() obtém o corpo da requisição e tenta interpretá-lo como JSON. Se o conteúdo não for JSON válido, o Flask retornará um erro 400 automaticamente. Adicionamos uma validação manual para garantir que o campo nome esteja presente. Se não estiver, retornamos uma mensagem de erro com status 400 (Bad Request).
Quando criamos um item, retornamos o item criado com status 201 (Created), que é o código padrão para criação bem-sucedida. O método jsonify também define o cabeçalho Content-Type e serializa o dicionário para JSON.
Para testar, envie uma requisição POST para http://localhost:5000/itens com o corpo JSON:
{
"nome": "Teclado",
"descricao": "Teclado mecânico"
}A resposta será algo como:
{
"id": 1,
"nome": "Teclado",
"descricao": "Teclado mecânico"
}Assim, conseguimos enviar e receber dados estruturados de forma padronizada.
Tratamento de erros
Em uma API, é essencial tratar erros de forma consistente. O Flask permite personalizar as respostas de erro usando o decorador @app.errorhandler. Podemos definir handlers para erros HTTP comuns, como 404 (Not Found) e 500 (Internal Server Error), além de erros personalizados.
Vamos implementar o tratamento para item não encontrado (404) e para erros genéricos:
from flask import Flask, request, jsonify
app = Flask(__name__)
itens = []
proximo_id = 1
# ... rotas anteriores ...
@app.route('/itens/<int:id>', methods=['GET'])
def obter_item(id):
item = next((item for item in itens if item['id'] == id), None)
if item is None:
return jsonify({'erro': 'Item não encontrado'}), 404
return jsonify(item)
@app.errorhandler(404)
def nao_encontrado(erro):
return jsonify({'erro': 'Recurso não encontrado'}), 404
@app.errorhandler(500)
def erro_interno(erro):
return jsonify({'erro': 'Erro interno do servidor'}), 500Na rota obter_item, usamos a função next para encontrar o item com o ID especificado. Se não encontrar, retornamos uma mensagem de erro com status 404. Caso contrário, retornamos o item.
Os handlers de erro globais garantem que qualquer erro não tratado retorne uma resposta JSON em vez de uma página HTML padrão. Isso é importante para que o cliente saiba que houve um erro e possa lidar com isso de forma programática.
Além disso, podemos criar exceções personalizadas para situações específicas. Por exemplo, se tentarmos criar um item com dados inválidos, podemos lançar uma exceção que será capturada por um handler:
class DadosInvalidos(Exception):
pass
@app.errorhandler(DadosInvalidos)
def dados_invalidos(erro):
return jsonify({'erro': str(erro)}), 400Dessa forma, em qualquer rota, podemos fazer raise DadosInvalidos('Mensagem específica') e o Flask responderá com status 400 e a mensagem em JSON.
Boas práticas e observações finais
Ao desenvolver APIs, é importante seguir algumas boas práticas:
- Use nomes claros e consistentes: rotas e campos devem ser intuitivos.
- Valide as entradas: sempre verifique se os dados recebidos são válidos antes de processá-los.
- Retorne códigos de status apropriados: 200 para sucesso, 201 para criação, 400 para erros do cliente, 404 para não encontrado, 500 para erros do servidor.
- Documente sua API: use ferramentas como Swagger/OpenAPI para descrever os endpoints.
- Nunca confie em dados do cliente: sempre sanitize e valide.
Nesta aula, você construiu uma API simples com Flask, aprendendo a criar rotas, manipular JSON e tratar erros. Esses são os fundamentos para desenvolver APIs mais complexas, com autenticação, banco de dados e deploy em produção. Continue explorando e pratique implementando novos endpoints e melhorias.
Referências
- Documentação oficial do Flask
- Introdução a APIs (MDN)
- Trabalhando com JSON em Python (W3Schools)
- Códigos de status HTTP (MDN)
- Flask by Example (Real Python)
- OpenAPI Specification (Swagger)
Exercícios
Implemente a rota
PUT /itens/<int:id>para atualizar um item existente. Se o item não existir, retorne 404. Se os dados forem inválidos (sem nome), retorne 400.✓ Resposta:@app.route('/itens/<int:id>', methods=['PUT']) def atualizar_item(id): global itens item = next((item for item in itens if item['id'] == id), None) if item is None: return jsonify({'erro': 'Item não encontrado'}), 404 dados = request.get_json() if not dados or not 'nome' in dados: return jsonify({'erro': 'O campo nome é obrigatório'}), 400 item['nome'] = dados.get('nome', item['nome']) item['descricao'] = dados.get('descricao', item.get('descricao', '')) return jsonify(item)Adicione uma rota
DELETE /itens/<int:id>que remove o item da lista. Retorne 204 (No Content) em caso de sucesso e 404 se não existir.✓ Resposta:@app.route('/itens/<int:id>', methods=['DELETE']) def deletar_item(id): global itens item = next((item for item in itens if item['id'] == id), None) if item is None: return jsonify({'erro': 'Item não encontrado'}), 404 itens = [item for item in itens if item['id'] != id] return '', 204Crie um endpoint
GET /itensque aceite um parâmetro de consultaqpara filtrar itens pelo nome (busca parcial, case-insensitive).✓ Resposta:@app.route('/itens', methods=['GET']) def listar_itens(): q = request.args.get('q', '').lower() if q: filtrados = [item for item in itens if q in item['nome'].lower()] return jsonify(filtrados) return jsonify(itens)Implemente um handler para o método HTTP não permitido (405) retornando JSON com a mensagem "Método não permitido".
✓ Resposta:@app.errorhandler(405) def metodo_nao_permitido(erro): return jsonify({'erro': 'Método não permitido'}), 405Crie uma rota
GET /itens/<int:id>que retorne o item, mas com um pequeno atraso simulado (ex.:time.sleep(1)) para testar o comportamento assíncrono. (Não se preocupe com performance agora.)✓ Resposta:import time @app.route('/itens/<int:id>', methods=['GET']) def obter_item(id): time.sleep(1) item = next((item for item in itens if item['id'] == id), None) if item is None: return jsonify({'erro': 'Item não encontrado'}), 404 return jsonify(item)