Crie APIs GraphQL em Django com Graphene

March 26, 2021
Escrito por
Adeyemi Atoyegbe
Contribuidor
As opiniões expressas pelos colaboradores da Twilio são de sua autoria

Crie APIs GraphQL em Django com Graphene

GraphQL é uma linguagem de manipulação e consulta de dados de código aberto para APIs em tempo de execução para atender a consultas com dados existentes. Essa linguagem foi desenvolvida internamente pelo Facebook em 2012 antes de ser lançada publicamente em 2015. Ela permite que os clientes definam a estrutura dos dados solicitados, e a mesma estrutura dos dados é retornada do servidor, evitando assim que dados desnecessários sejam retornados.

O GraphQL tem três operações principais: Consultas para leitura de dados, Mutações para gravação de dados e Assinaturas para recebimento automático de atualizações de dados em tempo real. Um servidor GraphQL fornece aos clientes um esquema predefinido, um modelo dos dados que podem ser solicitados. O esquema serve como um terreno comum entre o cliente e o servidor.

Neste tutorial, usaremos Graphene, um framework GraphQL para Python, para criar uma API Django que usa consultas e mutações.

Requisitos do tutorial

Para acompanhar este tutorial, você deve ter os seguintes itens:

  • Python 3.6 ou mais recente.
  • Virtualenv, para criar um ambiente virtual para o projeto tutorial.
  • Postman, para enviar solicitações à nossa API.
  • Um conhecimento prático do framework web do Django .

Configuração do projeto

Começaremos criando um ambiente virtual e instalando os pacotes Python necessários.

Crie uma pasta para o nosso projeto:

mkdir django_graphql
cd django_graphql

Em seguida, crie um ambiente virtual Python e ative-o. Se você estiver seguindo este tutorial no Windows:

$ virtualenv env
$ env\Scripts\activate

Se você estiver usando um computador MacOs ou um Unix:

$ virtualenv env
$ source env/bin/activate

Instale as dependências necessárias para nosso projeto:

(env) $ pip install django graphene-django

Crie um novo projeto Django:

(env) $ django-admin startproject books_api

Altere seu diretório atual para o projeto:

(env) $ cd books_api

Crie um app api no projeto books_api 

(env) $ python manage.py startapp api

Em seguida, registre o app api e integre o app de terceiros graphene-django que instalamos anteriormente em nosso projeto books_api. Encontre a lista INSTALLED_APPS em books_api/settings.py e adicione api e "graphene-django" no final:

INSTALLED_APPS = [
    ...
    'api',
    'graphene_django',
]

Enquanto estiver em books_api/settings.py, vá até o final do arquivo e adicione um dicionário GRAPHENE com configurações para o pacote graphene-django:

GRAPHENE = {   
    "SCHEMA": "api.schema.schema"
}

A configuração do SCHEMA informa a Graphene onde encontrar o esquema GraphQL para o aplicativo. Definiremos o esquema depois que o banco de dados for criado.

Modelo de banco de dados

Abra o arquivo api/models.py e digite o código abaixo para adicionar o modelo de banco de dados Book:

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)
    year_published = models.CharField(max_length=10)
    review = models.PositiveIntegerField()
    
    def __str__(self):
        return self.title

Em seguida, crie e execute as migrações para nosso banco de dados:

 $ python manage.py makemigrations
 $ python manage.py migrate

Para ajudar a testar esse projeto, podemos preencher nosso banco de dados com alguns dados. Para fazer isso, crie um arquivo data.json no diretório do projeto onde está o arquivo manage.py e copie os seguintes dados nele:

    {
        "model": "api.book"

Com o arquivo data.json salvo no diretório atual, execute o comando abaixo para importar os dados para o banco de dados:

$ python manage.py loaddata data.json
Installed 5 object(s) from 1 fixture(s)

Em seguida, adicione o endpoint GraphQL ao final do dicionário urlpatterns no arquivo books_api/urls.py:

from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('graphql', GraphQLView.as_view(graphiql=True)),
]

Crie uma API Books com GraphQL

Nesta seção, criaremos uma API com Graphene usando queries (consultas) e mutations (mutações) GraphQL.

Implemente um esquema GraphQL

Crie um novo arquivo na pasta api/schema.py:

import graphene

from graphene_django import DjangoObjectType, DjangoListField 
from .models import Book 


class BookType(DjangoObjectType): 
    class Meta:
        model = Book
        fields = "__all__"


class Query(graphene.ObjectType):
    all_books = graphene.List(BookType)
    book = graphene.Field(BookType, book_id=graphene.Int())

    def resolve_all_books(self, info, **kwargs):
        return Book.objects.all()

    def resolve_book(self, info, book_id):
        return Book.objects.get(pk=book_id)

Nesta etapa, criamos duas classes, a primeira é BookType, que adapta o modelo Book a um DjangoObjectType. Definimos fields (campos) como __all__ para indicar que queremos todos os campos do modelo disponíveis em nossa API.

A classe Query define as consultas GraphQL que a API fornecerá aos clientes. A consulta all_books retornará uma lista de todas as instâncias BookType, enquanto a consulta book retornará uma instância BookType fornecida por um ID de número inteiro. A classe define dois métodos, que são os "resolvers" (resolvedores) da consulta. Cada query (consulta) no schema (esquema) é mapeada para um método resolver (resolvedor).

Os dois resolvers (resolvedores) de query (consulta) consultam o banco de dados usando o modelo Django para executar a query (consulta) e retornar os resultados.

Adicione atualizações de dados com mutações em GraphQL

Agora, vamos adicionar operações de criação, atualização e exclusão por meio de mutações. Ainda no arquivo api/schema.py, adicione o código abaixo na parte inferior:

class BookInput(graphene.InputObjectType):
    id = graphene.ID()
    title = graphene.String()
    author = graphene.String()
    year_published = graphene.String()
    review = graphene.Int() 

A classe BookInput define campos semelhantes ao nosso objeto de modelo Book para permitir que o cliente adicione ou altere os dados por meio da API. Usaremos essa classe como argumento para nossas classes de mutação.

Vamos adicionar uma mutação para criar novos livros. Adicione o seguinte código na parte inferior de api/schema.py:

class CreateBook(graphene.Mutation):
    class Arguments:
        book_data = BookInput(required=True)

    book = graphene.Field(BookType)

    @staticmethod
    def mutate(root, info, book_data=None):
        book_instance = Book( 
            title=book_data.title,
            author=book_data.author,
            year_published=book_data.year_published,
            review=book_data.review
        )
        book_instance.save()
        return CreateBook(book=book_instance)

A classe CreateBook será usada para criar e salvar novas entradas de Book no banco de dados. Para cada classe de mutação devemos ter uma classe interna Arguments e um método de classe mutate().

Definimos uma instância da classe BookInput que criamos anteriormente como nossos argumentos e a tornamos obrigatória com a opção required=True.  Depois disso, definimos o modelo com o qual estamos trabalhando com book = graphene.Field(BookType).

No método mutate estamos salvando um novo livro chamando o método save() em uma nova instância Book criada a partir dos valores book_data passados como argumento.

Abaixo, você pode ver a implementação da mutação UpdateBook. Adicione este código na parte inferior da api/schema.py:

class UpdateBook(graphene.Mutation):
    class Arguments:
        book_data = BookInput(required=True)

    book = graphene.Field(BookType)

    @staticmethod
    def mutate(root, info, book_data=None):

        book_instance = Book.objects.get(pk=book_data.id)

        if book_instance:
            book_instance.title = book_data.title
            book_instance.author = book_data.author
            book_instance.year_published = book_data.year_published
            book_instance.review = book_data.review
            book_instance.save()

            return UpdateBook(book=book_instance)
        return UpdateBook(book=None)

A classe de mutação UpdateBook é muito semelhante a CreateBook. A diferença aqui é a lógica no método mutate(), que recupera um objeto do banco de dados pelo ID de livro fornecido e, em seguida, aplica as alterações do argumento de entrada nele.

Finalmente, vamos adicionar uma mutação de exclusão. Adicione o código a seguir na parte inferior de api/schema.py:

class DeleteBook(graphene.Mutation):
    class Arguments:
        id = graphene.ID()

    book = graphene.Field(BookType)

    @staticmethod
    def mutate(root, info, id):
        book_instance = Book.objects.get(pk=id)
        book_instance.delete()

        return None

Na classe de mutação DeleteBook, temos graphene.ID como o único argumento. O método mutate() usa essa id para remover o livro referenciado do banco de dados.

Agora temos duas consultas e três mutações definidas. Para registrá-los na Graphene, adicione o código abaixo no final de api/schema.py:

class Mutation(graphene.ObjectType):
    create_book = CreateBook.Field()
    update_book = UpdateBook.Field()
    delete_book = DeleteBook.Field()


schema = graphene.Schema(query=Query, mutation=Mutation)

Teste a API GraphQL

Ainda não estamos prontos para testar a API. Vamos iniciar o servidor Django:

$ python manage.py runserver

Agora, acesse http://127.0.0.1:8000/graphql no seu navegador. Você deve ver a interface GraphIQL para testes interativos da API GraphQL.

Interface GraphQL

A seta preta no diagrama acima é onde você insere seu código GraphQL. Em seguida, clique no botão de reprodução no canto superior esquerdo da tela para executar o código e obter um resultado na área indicada com a seta azul.

Emita uma consulta

As consultas são usadas para solicitar dados do servidor. O código GraphQL abaixo está solicitando todos os livros do banco de dados. Insira-o no painel esquerdo da interface GraphIQL.

query {
  allBooks {
    id
    title
    author
    yearPublished
    review
  }
}

Pressione o botão de reprodução para executar a consulta e ver os resultados no painel do lado direito.

Em seguida, tente a consulta a seguir, que solicita um único livro por seu id:

query {
  book(bookId: 2) {
    id
    title
    author
  }
}

Observe como cada consulta pode especificar quais atributos do modelo de livro precisam ser retornados.

Crie um livro

O seguinte fragmento de GraphQL define uma mutação que adiciona um novo livro ao banco de dados:

mutation createMutation {
  createBook(bookData: {title: "Things Apart", author: "Chinua Achebe", yearPublished: "1985", review: 3}) {
    book {
      title,
      author,
      yearPublished,
      review
    }
  }
}

Atualize um livro existente

A próxima mutação GraphQL atualiza o livro com id=6:

mutation updateMutation {
  updateBook(bookData: {id: 6, title: "Things Fall Apart", author: "Chinua Achebe", yearPublished: "1958", review: 5}) {
    book {
      title,
      author,
      yearPublished,
      review
    }
  }
}

Exclua um livro

O exemplo de mutação final exclui o livro com id=6 do banco de dados:

mutation deleteMutation{
  deleteBook(id: 6) {
    book {
      id
    } 
  }
}

Teste a API Book com outros clientes GraphQL

O Django CSRF impede que usuários não autenticados no site executem ataques mal-intencionados. Diante disso, qualquer solicitação POST feita em um aplicativo externo fora do site do Django resultará em um 403 Forbidden Error.

Para evitar isso, há duas opções. A opção mais segura é adicionar o token CSRF gerado pelo aplicativo Django às solicitações POST que seu cliente faz ao endpoint GraphQL. Consulte a seção Ajax na documentação do Django para saber mais sobre essa opção.

Uma opção mais fácil, porém menos segura, é isentar o endpoint GraphQL da proteção CSRF. Para fazer isso, abra o arquivo api/urls.py e altere a definição do endpoint GraphQL da seguinte forma:

from django.urls import path 
from graphene_django.views import GraphQLView 
from django.views.decorators.csrf import csrf_exempt

urlpatterns = [
    ...
    path('graphql', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

O wrapper csrf_exempt adicionado ao GraphQLView remove a verificação de token CSRF do endpoint.

Se você quiser ter certeza de que a proteção CSRF não interfere com seu endpoint GraphQL, você pode usar o Postman para enviar solicitações de GraphQL para a API Django:

 

Interface Postman

Usando a captura de tela acima como referência, siga estas etapas para enviar uma solicitação de GraphQL com o Postman:

  • Cole seu endpoint do GraphQL http://127.0.0.1:8000/graphql na caixa com a seta roxa.
  • Clique na primeira seta branca apontando para a opção"Body" (corpo)
  • Clique nas opções de GraphQL na segunda seta branca
  • Cole o código de consulta na caixa de consulta e clique no botão azul "Send" (Enviar).
  • Você verá o resultado de sua solicitação de API na parte inferior, na área de seta verde-clara.
  • Observe a área de seta azul, onde você deve fazer uma solicitação GET para consultas, ou uma solicitação POST para mutações.

Experimente os trechos de código que usamos acima para testar nossa API por meio do Postman.

Conclusão

Neste tutorial, criamos uma API simples de GraphQL no Django usando o pacote Graphene-Django criado sobre Graphene, que facilitou a adição da funcionalidade GraphQL ao nosso aplicativo Django.

Criamos consultas para ler dados, mutações para gravar e alterar dados e testamos nossa API usando a interface GraphIQL fornecida pela biblioteca Graphene-Django e pelo popular cliente de API Postman. Você pode encontrar o código completo do projeto aqui.

Este artigo foi traduzido do original "Building GraphQL APIs in Django with Graphene". Enquanto melhoramos nossos processos de tradução, adoraríamos receber seus comentários em help@twilio.com - contribuições valiosas podem render brindes da Twilio.