Django REST framework: API CRUD com Autenticação JWT
Table of Contents
Aprenda a criar uma API REST profissional utilizando Django REST framework. Construa um CRUD completo com autenticação de usuários utilizando JWT.
#
Introdução
##
O que iremos aprender?
Nosso projeto será criar uma API para gerenciar tarefas (To-Do). Utilizaremos usuários e permissões do Django para acessar os recursos da API, além de autenticação por JWT.
O código do projeto pode ser encontrado neste link.
Este artigo não é para desenvolvedores totalmente leigos, tópicos como o que é um token JWT
, o que é uma API REST
ou o que é o protocolo HTTP
não serão abordados.
##
O que é o Django REST framework?
Django REST framework é um kit de ferramentas para construir APIs Web dentro do ecossistema Django. Possui diversas implementações prontas, como Serializers, Views, autenticação e permissões.
##
Quais os benefícios em se utilizar um Framework para o desenvolvimento?
-
Segurança: proteção contra vulnerabilidades comuns, como SQL Injection, Cross-Site Scripting (XSS) e Cross-Site Request Forgery (CSRF)
-
ORM (Object-Relational Mapping): facilitando o trabalho com bancos de dados relacionais, permitindo manipular o banco de dados usando objetos prontos ao invés de queries SQL
-
Comunidade: desenvolvimento colaborativo, diversos programadores testam a ferramenta e contribuem para a evolução do framework
-
Rapidez no desenvolvimento: funcionalidades prontas podem ser usadas para implementação rápida e segura de projetos
-
Manutenção: código limpo e organizado garantem facilidade na manutenção e implementação de novas features
-
Confiabilidade: frameworks populares (como o DRF - Django REST framework) são amplamente utilizados e testados, reduzindo a probabilidade de bugs e falhas.
#
Pré-requisitos
##
Conhecimentos:
- Conhecimentos básicos sobre API REST
- Conhecimento básico em desenvolvimento web
- Python: conhecimentos básicos e orientação a objetos
- Conhecimento básico sobre o protocolo HTTP
##
Softwares:
- Python 3.8 ou superior
- Editor de código (recomendo o VSCode)
- Postman ou Insomnia (opcional)
#
Configuração inicial
Primeiro, vamos criar um ambiente virtual para o nosso projeto:
python -m venv venv
source venv/bin/activate # para usuários Linux/MacOS
.\venv\Scripts\Activate.ps1 # para usuários Windows
Com o ambiente criado, vamos instalar o Django e o Django Rest Framework (DRF):
pip install django && pip install djangorestframework
##
Iniciando e configurando o projeto
Agora que temos nosso ambiente com o Django e o DRF instalados, vamos iniciar um novo projeto Django:
django-admin startproject app .
Podemos testar o nosso novo projeto rodando o comando:
python manage.py runserver
Ao acessar http://127.0.0.1:8000/ uma página de ‘welcome’ do Django deverá ser exibida:
Podemos parar o servidor pressionando CTRL+C
.
##
Criando o nosso primeiro aplicativo
Com o nosso projeto Django criado, podemos criar um novo aplicativo, que será chamado de todo_api
:
python manage.py startapp todo_api
Importante: o nosso projeto se chama app
e nesse projeto temos uma aplicação chamada todo_api
, não confunda!
Um novo diretório chamado todo_api
foi criado em nosso projeto. Precisamos inserir o Django REST framework e o nosso aplicativo nas configurações do nosso projeto app
, para isso, acesse o arquivo app/settings.py
e adicione a todo_api
aos INSTALLED_APPS
:
# app/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'todo_api',
]
Agora temos o nosso projeto e aplicativo configurados, podemos fazer a nossa primeira operação no banco de dados, que cria um banco SQLite e as tabelas iniciais do Django:
python manage.py migrate
#
Desenvolvimento da API
Com o projeto e a aplicação criados e configurados, podemos iniciar o desenvolvimento da API de fato.
Começaremos o desenvolvimento do nosso Model, ou seja, o modelo da nossa API no banco de dados, que será simples.
##
Models e Serializers
Nosso modelo terá apenas três campos: id, task e done, sendo:
- id: criado automaticamente pelo Django, não precisamos declará-lo (que facilidade!)
- task: será o nome da nossa “tarefa”, será um dado do tipo
CharField
- done: será o status da nossa tarefa, será um
booleano
(true/false)
Abra o arquivo todo_api/models.py
e insira o seguinte código:
# todo_api/models.py
from django.db import models
class Todo(models.Model):
task = models.CharField(max_length=250)
done = models.BooleanField()
def __str__(self):
return self.task
O Django irá interpretar esse modelo e criará automaticamente a tabela no banco de dados conforme as nossas especificações, não precisamos nos preocupar com SQL, essa é uma das facilidades que o Django ORM nos traz.
Para que o Django “leia” os arquivos models
do nosso projeto, precisamos rodar o comando:
python manage.py makemigrations
Após isso, rodamos o comando migrate
para aplicar:
python manage.py migrate
Uma tabela será criada no seu banco de dados local (SQLite) com o nome todo_todo_api
, ou seja: nomemodel_nomeapp
. Os campos id, task e done também serão criados.
O nosso modelo foi criado, porém, os dados da API devem trafegar em formato JSON, e não no formato de objetos do banco de dados, para “serializar” os nossos dados de objeto para json, precisamos criar um serializer.
Como tudo no Django é simples, essa funcionalidade já está pronta, basta utilizá-la!
Crie um arquivo chamado serializers.py
dentro da sua app todo_api
: todo_api/serializers.py e inseira o código abaixo:
# todo_api/serializers.py
from rest_framework import serializers
from todo_api.models import Todo
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = '__all__'
Basicamente importamos o nosso Model Todo
, que é a representação dos nosso dados no banco de dados e utilizamos o ModelSerializer
do Django REST framework, que fará a serialização de forma automática. Também informamos que queremos serializar todos os campos com fields = __all__
##
Views e View Classes
O arquivo todo_api/views.py
é responsável pela regra de negócio da nossa API, aqui será implementa toda a lógica do nosso CRUD. Como a nossa API é simples, utilizaremos duas classes prontas do DRF para abstrair as operações, sendo elas:
-
ListCreateAPIView: responsável pelos verbos
GET
ePOST
, lista todas as tarefas e permite a criação de uma nova tarefa. -
RetrieveUpdateDestroyAPIView: trabalha com os id’s das tarefas, aqui podemos utilizar todos os verbos http:
GET
,POST
,PUT
,DELETE
. Podemos listar, alterar ou deletar as tarefas de acordo com o seu id único.
Dentro do arquivo todo_api/views.py
insira o código:
# todo_api/views.py
from rest_framework import generics
from todo_api.models import Todo
from todo_api.serializers import TodoSerializer
class TodoCreateListView(generics.ListCreateAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
class TodoRetrieveUpdateDestroyView(generics.RetrieveUpdateDestroyAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
Criamos duas classes, uma para cada View Class do DRF, agora, podemos configurar nossas URLs para enviar as requisições para as nossas views.
#
URLs e Versionamento da API
Dentro do diretório todo_api
crie um arquivo chamado urls.py
e insira o código abaixo:
# todo_api/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('todos/', views.TodoCreateListView.as_view(), name='todo-create-list-view'),
path('todos/<int:pk>', views.TodoRetrieveUpdateDestroyView.as_view(), name='todo-detail-view'),
]
Esse trecho de código informa que ao acessar a nossa api na rota /todos/ e /todos/id/ o Django deve utilizar a view configurada anteriormente.
Não é uma boa prática acessar diretamente as rotas, a boa prática é utilizar o versionamento de urls, ou seja, antes de acessar o caminho /todos/, por exemplo, acessamos /api/v1/todos/
Isso garante que em uma nova implementação, no caso uma v2, os clientes que utilizam o endpoint /v1/ não sejam afetados.
Para implementar o versionamento, acesse o arquivo app/urls.py
e inseira o código:
# app/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('todo_api.urls')),
]
Utilizamos o include do Django para criar um versionamento, agora, nossa api será acessada por: http://127.0.0.1:8000/api/v1/todos/
##
API funcionando, primeiro teste!
Nossa API já está funcional, podemos testá-la.
python manage.py runserver
Acesse http://127.0.0.1:8000/api/v1/todos/ e crie algumas tarefas.
Ao acessar a url passando algum id, podemos ver a RetrieveUpdateDestroyAPIView
em ação, com as opções de visualizar, alterar ou deletar um recurso específico.
##
Django ADMIN - Criação e gerenciamento de usuários
O Django já nos traz pronta uma plataforma para gerenciar usuários, grupos de usuários e Models do banco de dados.
Para criar o primeiro usuário (root) digite no terminal:
python manage.py createsuperuser
Para adicionarmos nosso model no Django Admin, acesse o arquivo todo_api/admin
e insira o código:
# todo_api/admin.py
from django.contrib import admin
from todo_api.models import Todo
@admin.register(Todo)
class TodoAdmin(admin.ModelAdmin):
list_display = ('id', 'task', 'done')
Execute o servidor:
python manage.py runserver
Acesse a url do django admin: http://127.0.0.1:8000/admin/
Insira as suas credenciais e acesse a dashboard.
Crie pelo menos um usuário, sem credenciais de admin para prosseguirmos.
##
Segurança - Autenticação com JWT e proteção dos endpoints
Com o nosso CRUD pronto e com o django admin fazendo o gerenciamento de usuários, podemos começar a autenticação da nossa API.
Iremos exigir que apenas usuários autenticados tenham acesso à API, além de protegeremos os nossos endpoints, onde apenas usuários autenticados e com permissões poderão acessar a API e executar ações.
- Instale o Django REST framework Simple JWT
pip install djangorestframework-simplejwt
- Na configuração do projeto,
app/settings.py
adicione o código abaixo no final do arquivo:
# app/settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(days=1),
"REFRESH_TOKEN_LIFETIME": timedelta(days=7),
}
No começo do arquivo, importe o timedelta
:
from datetime import timedelta
Nesse código configuramos que a autenticação será feita pelo Simple JWT e qual será o tempo de vida do Token antes dele expirar.
- Criando o endpoint para gerar o token
Precisamos criar um endpoint para gerar o nosso token jwt. Criaremos um novo aplicativo para ser responsável pela autenticação:
python manage.py startapp authentication
Adicione o novo app no arquivo app/settings.py
:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'todo',
'authentication',
]
Dentro dessa nova app, crie um arquivo chamado urls.py
e insira o código:
# authentication/urls.py
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
urlpatterns = [
path('authentication/token/', TokenObtainPairView.as_view(), name='token-obtain-pair-view'),
path('authentication/token/refresh/', TokenRefreshView.as_view(), name='token-refresh-vier'),
path('authentication/token/verify', TokenVerifyView.as_view(), name='token-verify-view')
]
Estamos criando endpoints para gerar o nosso token JWT, seu refresh e verify.
Tudo isso é abstraido pelo DRF, não precisamos criar tudo na mão.
Agora, no arquivo app/urls.py
vamos importar nossos novos endpoints, o código deverá ficar assim:
# app/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('authentication.urls')), # endpoint para autenticação
path('api/v1/', include('todo_api.urls')),
]
Agora, vamos proteger nossas views. No arquivo todo/views.py
vamos adicionar permission_classes
em nossas rotas, esse é um recurso do DRF utilizado para proteger as rotas. O arquivo deverá ficar assim:
# todo/views.py
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from todo_api.models import Todo
from todo_api.serializers import TodoSerializer
class TodoCreateListView(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated, )
queryset = Todo.objects.all()
serializer_class = TodoSerializer
class TodoRetrieveUpdateDestroyView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthenticated, )
queryset = Todo.objects.all()
serializer_class = TodoSerializer
Agora, ao tentar acessar algum recurso da nossa API, não será possível. Veja:
Para acessar, será necessário gerar um Token JWT na rota api/v1/authentication/token/
.
A partir daqui, utilizaremos o Postman para fazer requisições à API, pois teremos que informar o token em nossas requisições.
Acesse o Postman e faça uma requisição POST
na url 127.0.0.1:8000/api/v1/authentication/token/
passando o username/password no corpo da requisição body
:
{
"username": "yourusername",
"password": "yourpassword"
}
Ao fazer a requisição informando o usuário e senha que você criou no Django Admin, será retornado um Access Token e um Verify Token, porém, nesse tutorial utilizaremos apenas o access token:
Copie o access token
e faça uma nova requisição na rota 127.0.0.1:8000/api/v1/todos
passando o token copiado em Authorization > Bearer Token
, conforme imagem abaixo:
Com isso, apenas usuários autenticados podem acessar nossas tarefas. Mas, e se eu quiser que algum usuário tenha permissão apenas de leitura, mas não de alteração dos dados? ou seja, pode fazer um GET
, mas não um PUT
? Para resolver esse problema, utilizaremos as permissões de usuários.
##
Permissões de Usuários
Nesse ponto apenas usuários autenticados podem utilizar a API, porém, queremos restringir o acessos desses usuários. Por exemplo, determinado usuário pode ter apenas permissão de leitura GET
, enquanto outro usuário pode ler e criar novas tarefas GET
e POST
, enquanto outro tipo de usuário pode fazer todas as ações. Para isso, utilizaremos as permissões de usuários do Django.
Vamos criar dois exemplos, o primeiro será um grupo de usuários que apenas pode fazer GET
e o segundo com todas as permissões.
- Acesse o Django Admin
- Clique em “Groups”
- Crie um grupo chamado
"read_only"
e dê a permissãoTodo_Api | todo | Can view todo
- Salve
- Repita o processo, agora crie um crupo chamado
"full_access"
e dê as permissões:Todo_Api | todo | Can view todo
Todo_Api | todo | Can add todo
Todo_Api | todo | Can delete todo
Todo_Api | todo | Can change todo
Exemplo de criação do grupo “full_access”:
Agora temos dois grupos de usuários com permissões distintas, essa é a melhor prática para gerenciamento de permissões de usuários.
Agora, precisamos informar no código que os usuários além de estarem autenticados devem ter permissão para executar as ações na API.
Dentro de app
crie um arquivo chamado permissions.py
e insira o código abaixo:
# app/permissions.py
from rest_framework import permissions
class GlobalDefaultPermission(permissions.BasePermission):
def has_permission(self, request, view):
model_permission_codename = self.__get_model_permission_codename(
method=request.method,
view=view,
)
if not model_permission_codename:
return False
return request.user.has_perm(model_permission_codename)
def __get_model_permission_codename(self, method, view):
try:
model_name = view.queryset.model._meta.model_name
app_label = view.queryset.model._meta.app_label
action = self.__get_action_sufix(method)
return f'{app_label}.{action}_{model_name}'
except AttributeError:
return None
def __get_action_sufix(self, method):
method_actions = {
'GET': 'view',
'POST': 'add',
'PUT': 'change',
'PATCH': 'change',
'DELETE': 'delete',
'OPTIONS': 'view',
'HEAD': 'view',
}
return method_actions.get(method, '')
Esse código sobrescreve a função has_permission
herdada de permissions.BasePermission
. Essa função deve retornar true
ou false
. O código verifica se o usuário tem permissão atrelada e retorna true
ou false
.
Por fim, vamos adicionar essa classe de permissões em nosso arquivo de views
. No arquivo todo_api/views.py
importe a classe GlobalDefaultPermission
criada anteriormente e a inclua nas permission_classes
. O código deverá ficar assim:
# todo_api/views.py
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from todo_api.models import Todo
from todo_api.serializers import TodoSerializer
from app.permissions import GlobalDefaultPermission
class TodoCreateListView(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated, GlobalDefaultPermission)
queryset = Todo.objects.all()
serializer_class = TodoSerializer
class TodoRetrieveUpdateDestroyView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthenticated, GlobalDefaultPermission)
queryset = Todo.objects.all()
serializer_class = TodoSerializer
#
Testes
No Django Admin, crie um usuário e insira ele no grupo read_only
, ele funcionará normalmente, depois, tente cadastrar uma nova tarefa, você não terá permissão. Após isso, adicione o grupo full_access
no usuário e tente novamente, você conseguirá criar a tarefa.
Exemplo de tentativa de acesso com um usuário sem permissão:
Agora com o usuário fazendo parte do grupo read_only
:
Assim, nossa API está completa. Fazemos operações de CRUD com usuários autenticados e rotas protegidas.
#
Conclusão
Neste artigo aprendemos a utilizar o Django REST framework para criar uma API REST de forma simples, segura e profissional. O trabalho de um desenvolvedor Backend está muitas vezes atrelado a operações de CRUD no banco de dados, além de modelagem de dados e segurança da aplicação. Esta API não visa esgotar todas as melhores práticas de desenvolvimento, mas orientar desenvolvedores iniciantes sobre práticas utilizadas no mercado.
#
Próximos passos
Sempre podemos melhorar o nosso projeto, no futuro iremos implementar algumas funcionalidades, como:
- Configuração do linter Flake8 para padronização do código conforme a PEP 8
- Criar a documentação da API com Swagger
- Criar um Dockerfile para rodar a aplicação como um container
- Fazer o Deploy da API na AWS com uma Pipeline completa (CodeCommit + CodePipeline + CodeBuild + ECR + ECS com Fargate)
Esses conteúdos serão publicados em breve aqui no blog.
Aguardo vocês. 😁