Esta API, faz parte do curso de Python ministrado por mim no Hacker Space Beco do exploit.
O intuito de desenvolver uma API, faz parte do mindset
de que “se você sabe construir, também sabe desconstruir”. Além de que, o conhecimento do desenvolvimento de APIs, soma com a habilidade de construir ferramentas para o dia-a-dia de uma exploração.
A idéia não é despejar todo o código pronto, mas mostrar passo a passo como é a linha de pensamento ao criar uma API, ao final, deixarei o projeto disponível no Github
.
O que você vai precisar:
- Conhecer um pouco de métodos HTTP;
- Conhecer banco de dados;
- Conhecer o basico de Python;
- Algum editor de código fonte de sua preferência (vou usar o VSCode);
- Alguma plataforma para testar a API de sua preferência (vou usar o Postman).
Iniciando o projeto
A princípio, precisamos criar nosso projeto em algum diretório, pois, para fins de organização do código, vamos montar toda a estrutura de diretórios que irá conter nossas bibliotecas e arquivos.
Dentro do diretório do projeto, vamos criar uma pasta chamada api
, pois é onde trabalharemos os códigos. Dentro da pasta api, vamos criar a pasta app
.
Codando
Com a estrutura inicial pronta, vamos começar a codar. Algumas pessoas costumam criar uma venv
para instalar pacotes, a fim de não “sujar” a instalação principal do python, no meu caso vou carregar a biblioteca na instalação principal com o comando pip install flask
(porém, desde já recomendo fortemente utilizar um ambiente virtual).
Após instalado, vamos criar um arquivo chamado __init__.py
dentro do diretório app
. Este arquivo vai conter nossa instancia inicial da API.
Somente com este script, é possível iniciar nosso servidor da API, porém, como vamos fazer um projeto organizado
e de simples manutenção, vamos criar um script chamado run.py
dentro do diretório api
, que é a raiz do nosso projeto.
Este script irá conter as variáveis para iniciarmos o servidor.
Note que colocamos as variáveis de inicialização dentro do arquivo run.py
, logo, ela deve ser retirada do script __init__.py
.
Ainda no diretório raiz, vamos criar o arquivo config.py
que irá conter todas as nossas configurações da API, a princípio, este arquivo deve conter somente a linha DEBUG = True
isso permitirá que o servidor reconheça as alterações realizadas e reinicie automaticamente a cada alteração no código que fizermos.
Agora precisamos voltar ao nosso arquivo __init__.py
e passar as configurações, ficando desta forma:
Com estas atualizações, nossa API irá carregar todas as nossas configurações deste arquivo.
Criando rotas
Nossa API precisa de rotas para carregar, atualizar, e alterar os dados, pra isso precisamos criar rotas. O microframework Flask, nos permite criar rotas de forma muito fácil.
Para isso, vamos criar um diretório dentro da pasta app
chamado routes
, e dentro dele criar o arquivo routes.py
que ficará desta forma:
Agora precisamos importar as rotas em nosso arquivo __init__.py
desta forma:
Agora, podemos executar nosso script run.py
, ele vai iniciar o servidor no localhost na porta 5000 (esta porta também pode ser alterada).
Precisamos de uma plataforma para testar a API, no meu caso vou utilizar o Postman
. Ao fazer uma requisição via GET
no endereço http://127.0.0.1:5000
temos o retorno da API com nosso Hello World!
.
Conexão com banco de dados
A conexão com banco de dados pode ser feito com qualquer SGBD
, nesta API do tutorial vou utilizar sqlite3
, porém vou deixar uma configuração comentada caso queira utilizar MySQL
.
Para dar continuidade, algumas bibliotecas precisam ser instaladas, são elas:
- flask_marshmallow;
- marshmallow_sqlalchemy;
- marshmallow;
- Flask-SQLAlchemy;
- mysqlclient;
Todas elas podem ser instaladas com pip
.
Agora precisamos atualizar nosso config.py
com as variáveis de conexão, ficando desta forma:
Agora precisamos atualizar nosso __init__.py
criando uma instância do SQLAlchemy que fará todas as iterações com o banco de dados e uma instância Marshmallow que facilitará nossa vida convertendo consultas no banco de dados no formato JSON
. Ficando desta forma:
Neste momento, podemos ver nossa API tomando forma.
Criando o model
Neste momento, vamos criar nosso primeiro model
que será de usuários, para isto, vamos criar um diretório chamado models
dentro do diretório app
e dentro dele o arquivo users.py
.
Dentro deste arquivo, precisamos importar as instâncias do SQLAlchemy e Marshmallow que criamos em nosso __init__.py
. Após isto, podemos criar uma classe que irá conter nossas configurações do database, ficando desta forma:
Conforme observado, criamos a instância no “__init__.py” com a variável “db” contendo o SQLAlchemy. Com ela podemos configurar o database de forma muito fácil com Flask passando argumentos para as configurar as colunas da tabela.
Com o model criado, podemos utilizar o Marshmallow para serializar nossas consultas e facilitar o retorno no formato JSON
. Dentro do meusmo arquivo users.py
, podemos adicionar a seguinte classe:
Por padrão os schemas do Marshmallow não precisam de uma base com uma classe, porém como estamos utilizando o SQLAlchemy, precisamos utilizar a class Meta, que vai utilizar os campos da classe criada anteriormente. Também criamos duas variáveis que serão utilizadas para retornar o JSON, sendo que o parâmetro many, informa que será retornado uma array.
Com nosso users.py
criado, precisamos referenciá-lo em nosso arquivo __init__.py
desta forma:
Com o model criado, precisamos navegar com o SHELL
(CMD
se você estiver no Windows) até o diretório api
que é a raiz do nosso projeto e rodar os seguintes comandos:
Como estou utilizando sqlite3
, já é possível ver o database criado nos diretórios do projeto.
Caso estaja utilizando MySQL
, verá que a tabela de usuários foi criada.
Operações de CRUD
Neste ponto nossa API já tem vários diretórios e pode parecer um emaranhado de scripts, porém as coisas farão um pouco mais de sentido agora, montando nosso primeiro CRUD
.
Antes de mais nada, vamos criar uma pasta views
dentro da pasta app
para montarmos toda a lógica e conexão com o banco de dados. Dentro desta pasta, vamos criar um novo arquivo users.py
(sim, temos dois scripts com o mesmo nome, porém em diretórios diferentes) para realizar as operações.
Neste script, precisamos importar o banco de dados do nosso app, nosso model e algumas funçõs da biblioteca Flask. As ferramentas devem ficar parecido com isso:
A primeira função que criaremos em nosso CRUD, será a de adicionar um usuário, como estamos trabalhando com requisições no formato JSON, vamos utilizar o objeto request
que importamos da biblioteca flask
, este objeto possui vários atributos que podem ser aprendidos através da própria biblioteca do Flask. A função fica desta forma:
Note que utilizamos a biblioteca “werkzeug.security” para criar uma hash com a senha do usuário, pois em situação nenhuma devemos gravar senhas em texto claro em banco de dados. Em seguida, criamos um objeto “user” que contém todas as variáveis que criamos.
Agora precisamos inserir as informações recebidas no banco de dados, para organizar bem a API, vamos inserir numa estrutura try/except para que a API retorne uma mensagem diferente caso ocorra algum erro no insert. Vamos utilizar a variável db
que importamos do app. Ficando desta forma:
Nesta função, utilizamos uma sessão do nosso banco de dados para registrar um usuário e logo em seguida retornamos estes dados no formato JSON. Para isto utilizamos nosso objeto user_schema
que utiliza o Marshmallow que transforma nossa Instância de Users em JSON.
Para que este script esteja disponível em nossa API, precisamos criar uma rota para ela, portanto precisamos incluí-la em nosso routes.py
utilizando o método POST
. A nova rota fica desta maneira:
Com o script atualizado, podemos criar uma requisição via POST
com o Postman para testar o CRUD. A requisição deve conter os dados no formato JSON conforme abaixo:
Ao enviar a requisição POST
temos a resposta:
E nossa primeira função do CRUD funcionou com sucesso!!!
Agora podemos seguir com o upgrade de nossa API com uma função que atualizará os dados do usuário. Para issom, vamos continuar editando o script users.py
na pasta views
acicionando uma nova função conforme abaixo:
Aqui criamos um objeto “user” que recebe o parâmetro “id”, pois a requisição receberá o id do usuário e consultará no banco de dados. Caso este usuário exista, as informações passadas na requisição serão atualizadas em banco, mas caso não exista o id informado, uma mensagem deve ser passada ao usuário.
Agora precisamos criar uma rota para este update de usuários em um novo endpoint
lá em nosso script routes.py
desta forma:
Note que após o endpoint “/users” existe o parâmetro “/
". Isso significa que a API vai capturar o que vier após o /users/ como um parâmetro e usá-lo na função.
Agora podemos testá-lo no Postman alterando os dados do primeiro usuário que criamos:
E temos o retorno da requisição com os dados de email alterados:
Agora vamos criar uma função para listar todos os usuários cadastrados em banco, ficando desta forma:
Note que para chamar um usuário pelo id, foi utilizada a função “Users.query.get(id)” , pois o .get() procura por um campo, já para listar tosos os usuários, utilizamos “Users.query.all()”, pos o .all() captura toda a tabela.
Agora precisamos adicionar uma rota para esta consulta, desta forma:
Antes de testar a requisição, adicionei mais 2 usuários para o resultado ficar melhor. Ao requisitar esta rota, temos a seguinte resposta:
Tudo irganizado e respondendo como esperado!
Agora que listamos todos
os usuários, vamos montar uma função que retorna somente um usuário através do id
. Para isto vamos criar mais uma função em nosso users.py
desta forma:
E como já fizemos com outras funções, vamos adicionar uma rota:
E vamos testá-lo:
Com isso nosso CRUD está quase pronto, faltando somente uma função para remover um usuário, para isso, vamos utilizar o método HTTP DELETE
.
Esta função será bem parecida com a função update, porém vamos utilizar o delete
, desta forma:
E como de costume, vamos criar a seguinte rota:
Agora podemos testar no Postman, deletando um dos usuários:
E isso finaliza todo o CRUD de usuários da nossa API. O próximo passo é fazer a autenticação via token.
JSON Tokens
Até o momento, nossa API já tem um método de autenticação, porém todos os métodos estão liberados, precisamos fornecer um modelo de segurança no qual somente um usuário autenticado na API consiga efetuar consultas e alterações.
Para isso, vamos utilizar a biblioteca PyJWT
que pode ser instalada com o comando pip install PyJWT
e sua documentação pode ser encontrada aqui.
O token vai consistir numa hash criptografada contendo o username e uma chave secreta
randômica gerada pela API.
A primeira parte, é criar essa chave secreta, vamos para nosso arquivo config.py
para gerar esta hash, o arquivo ficará desta forma:
Note que a variável chave_randomica recebe vários atributos da biblioteca string que ainda não recebe nenhum valor, mas quando juntamos esta variável com a função choice da biblioteca random, temos uma sequência totalmente aleatória através de um loop de 12, que será o tamanho da string gerada.
Agora podemos criar uma função em nosso arquivo users.py
que vai nos ajudar a fazer uma query no banco através do username, a função fica desta forma:
Agora podemos criar um novo script dentro da pasta views
que fará a autenticação, vamos chamá-lo de authenticator.py
. Nele vamos importar o nosso jwt
, e mais algumas bibliotecas para criarmos decorators que seráo utilizados em nosso script. A princípio, as importações ficam desta forma:
Vamos começar criando a função auth
que será utilizada para autenticar o usuário e gerar o token criptografado, nesta função, iremos criar uma variável que receberá o header com basic authorization
, desta forma:
Neste ponto, já fizemos uma validação básico do usuário, mas ainda é preciso validar o usuário no banco de dados.
Para isto, vamos utilizar a ultima funcã́o que criamos no arquivo users.py
, desta forma:
Até o momento, já validamos se o usuário existe no banco, mas também é preciso validar se a senha está correta, porém, a senha gravada em banco está criptografada, para comparar a senha informada com a que está gravada em banco, é preciso criptografar o que vem no request para fazer a comparação e gerar o token se tudo estiver ok. Uma vez que tudo foi validado, podemos gerar o token criptografado com um tempo de expiração, sendo a chave de criptografia, nossa SECRET_KEY
. O script fica desta forma:
Agora é preciso criar um endpoint para autenticação, vamos criar a rota em nosso routes.py
:
Não se esqueça de importar o arquivo authenticator no routes.py
Agora precisamos testar, para isso, vamos fazer a requisição POST
para o endpoint, passando as credenciais no Basic authorization
do Postman, se os dados de usuário e senha foram passados corretamente, esta é a resposta:
Caso uma senha, ou usuário inválidos foram passados, teremos esta resposta:
Neste momento, está quase tudo pronto, porém, precisamos fazer com que nossos endpoints entendam que uma autenticação é necessária para que possam operar. Para isso vamos criar a função token_required
que utilizará wraps da biblioteca functools
que importamos. Ficando inicialmente assim:
Com isso, validamos se um token foi enviado no request, mas ainda precisamos validar se este token é valido. Para isso, nossa API precisa decodificar este token e verificar se a SECRET_KEY
é valida. Uma vez que o token foi validado vamos criar uma variável que fará uma pesquisa no banco de dados a partir da função user_by_userneme
que criamos no arquivo users.py
da pasta views
e iremos retornar este usuário nos argumentos do decorator
. Ficando desta forma:
Com a função pronta, precisamos atualizar nossa rota para exigir o token, vamos testar com a primeira rota que criamos utilizando o decorator
desta forma:
Agora é a hora da verdade onde testaremos nossa autenticação, primeiro vamos fazer uma requisição sem nenhum token:
Ótimo, a API já identificou que é necessário um token para operar. Agora vamos fazer uma requisição com um token inválido:
Excelente, a API já reconheceu que o token é inválido. Agora vamos gerar um token no endpoint /auth
e passar este token na requisição:
E a API funcionou perfeitamente!!! Agora podemos passar o decorator
em qualquer endpoint que desejarmos.
Isso finaliza toda a construção da API com autenticação. A partir daí, mais funcionalidades e consultas ao banco de dados podem ser efetuadas para dar sentido à API.
Conforme prometido, o projeto completo está disponível no GitHub.
Espero que o artigo e a ferramenta tenham sido úteis para os seus estudos, até a próxima!!!