Construyendo una API GraphQL con Django

    Introducción

    Las API web son los motores que impulsan la mayoría de nuestras aplicaciones en la actualidad. Durante muchos años, REST ha sido la arquitectura dominante para las API, pero en este artículo exploraremos GraphQL.

    Con las API REST, generalmente crea URL para cada objeto de datos que es accesible. Digamos que estamos construyendo una API REST para películas: tendremos URL para las películas en sí, actores, premios, directores, productores … ¡ya se está volviendo difícil de manejar! Esto podría significar muchas solicitudes de un lote de datos relacionados. Imagina que eres el usuario de un teléfono móvil de baja potencia con una conexión a Internet lenta, esta situación no es ideal.

    GraphQL no es una arquitectura API como REST, es un lenguaje que nos permite compartir datos relacionados de una manera mucho más fácil. Lo usaremos para diseñar una API para películas. Luego, veremos cómo la biblioteca Graphene nos permite construir API en Python al hacer una API de película con Django.

    Que es GraphQL

    Originalmente creado por Facebook pero ahora desarrollado bajo GraphQL Foundation, GraphQL es un lenguaje de consulta y tiempo de ejecución de servidor que nos permite recuperar y manipular datos.

    Aprovechamos el sistema fuertemente tipado de GraphQL para definir los datos que queremos que estén disponibles para la API. Luego, creamos un esquema para la API: el conjunto de consultas permitidas para recuperar y modificar datos.

    Diseñar un esquema de película

    Creando Nuestros Tipos

    Los tipos describen el tipo de datos que están disponibles en la API. Ya existen tipos primitivos proporcionados que podemos usar, pero también podemos definir nuestros propios tipos personalizados.

    Considere los siguientes tipos de actores y películas:

    type Actor {
      id: ID!
      name: String!
    }
    
    type Movie {
      id: ID!
      title: String!
      actors: [Actor]
      year: Int!
    }
    

    los ID type nos dice que el campo es el identificador único para ese tipo de datos. Si el ID no es una cadena, el tipo necesita una forma de serializarse en una cadena para que funcione.

    Nota: El signo de exclamación significa que el campo es obligatorio.

    También notarías que en Movie usamos ambos tipos primitivos como String y Int así como nuestra costumbre Actor tipo.

    Si queremos que un campo contenga la lista del tipo, lo encerramos entre corchetes – [Actor].

    Crear consultas

    Una consulta especifica qué datos se pueden recuperar y qué se requiere para acceder a ellos:

    type Query {
      actor(id: ID!): Actor
      movie(id: ID!): Movie
      actors: [Actor]
      movies: [Movie]
    }
    

    Esta Query tipo nos permite obtener el Actor y Movie datos proporcionando su IDs, o podemos obtener una lista de ellos sin filtrar.

    Creando mutaciones

    Una mutación describe qué operaciones se pueden realizar para cambiar los datos en el servidor.

    Las mutaciones se basan en dos cosas:

    Te puede interesar:Obtener cadenas de consulta y parámetros en Express.js
    • Entradas – tipos especiales que solo se utilizan como argumentos en una mutación cuando queremos pasar un objeto completo en lugar de campos individuales.
    • Cargas útiles – tipos regulares, pero por convención los usamos como salidas para una mutación para que podamos extenderlos fácilmente a medida que evoluciona la API.

    Lo primero que hacemos es crear los tipos de entrada:

    input ActorInput {
      id: ID
      name: String!
    }
    
    input MovieInput {
      id: ID
      title: String
      actors: [ActorInput]
      year: Int
    }
    

    Y luego creamos los tipos de carga útil:

    type ActorPayload {
      ok: Boolean
      actor: Actor
    }
    
    type MoviePayload {
      ok: Boolean
      movie: Movie
    }
    

    Toma nota del ok campo, es común que los tipos de carga útil incluyan metadatos como un estado o un campo de error.

    los Mutation type lo reúne todo:

    type Mutation {
      createActor(input: ActorInput) : ActorPayload
      createMovie(input: MovieInput) : MoviePayload
      updateActor(id: ID!, input: ActorInput) : ActorPayload
      updateMovie(id: ID!, input: MovieInput) : MoviePayload
    }
    

    los createActor el mutador necesita un ActorInput objeto, que requiere el nombre del actor.

    los updateActor mutador requiere el ID del actor que se actualiza, así como la información actualizada.

    Lo mismo ocurre con los mutantes de la película.

    Nota: Mientras que la ActorPayload y MoviePayload no son necesarios para una mutación exitosa, es una buena práctica que las API proporcionen comentarios cuando procesan una acción.

    Definiendo el esquema

    Finalmente, asignamos las consultas y mutaciones que hemos creado al esquema:

    schema {
      query: Query
      mutation: Mutation
    }
    

    Usando la biblioteca de grafeno

    GraphQL es independiente de la plataforma, uno puede crear un servidor GraphQL con una variedad de lenguajes de programación (Java, PHP, Go), frameworks (Node.js, Symfony, Rails) o plataformas como Apollo.

    Con Grafeno, no tenemos que usar la sintaxis de GraphQL para crear un esquema, ¡solo usamos Python! Esta biblioteca de código abierto también se ha integrado con Django para que podamos crear esquemas haciendo referencia a los modelos de nuestra aplicación.

    Configuración de la aplicación

    Ambientes virtuales

    Se considera la mejor práctica para crear entornos virtuales para proyectos de Django. Desde Python 3.6, el venv Se ha incluido un módulo para crear y gestionar entornos virtuales.

    Usando la terminal, ingrese a su espacio de trabajo y cree la siguiente carpeta:

    $ mkdir django_graphql_movies
    $ cd django_graphql_movies/
    

    Ahora crea el entorno virtual:

    $ python3 -m venv env
    

    Deberías ver un nuevo env carpeta en su directorio. Necesitamos activar nuestro entorno virtual, para que cuando instalemos paquetes de Python solo estén disponibles para este proyecto y no para todo el sistema:

    Te puede interesar:Cargar archivos con Spring Boot
    $ . env/bin/activate
    

    Nota: Para salir del entorno virtual y utilizar su shell habitual, escriba deactivate. Debe hacer esto al final del tutorial.

    Instalación y configuración de Django y Graphene

    Mientras estamos en nuestro entorno virtual, usamos pip para instalar Django y la biblioteca Graphene:

    $ pip install Django
    $ pip install graphene_django
    

    Luego creamos nuestro proyecto Django:

    $ django-admin.py startproject django_graphql_movies .
    

    Un proyecto de Django puede constar de muchas aplicaciones. Las aplicaciones son componentes reutilizables dentro de un proyecto, y es una buena práctica crear nuestro proyecto con ellas. Creemos una aplicación para nuestras películas:

    $ cd django_graphql_movies/
    $ django-admin.py startapp movies
    

    Antes de trabajar en nuestra aplicación o ejecutarla, sincronizaremos nuestras bases de datos:

    # First return to the project's directory
    $ cd ..
    # And then run the migrate command
    $ python manage.py migrate
    

    Creando un modelo

    Los modelos de Django describen el diseño de la base de datos de nuestro proyecto. Cada modelo es una clase de Python que generalmente se asigna a una tabla de base de datos. Las propiedades de la clase se asignan a las columnas de la base de datos.

    Escriba el siguiente código para django_graphql_movies/movies/models.py:

    from django.db import models
    
    class Actor(models.Model):
        name = models.CharField(max_length=100)
    
        def __str__(self):
            return self.name
    
        class Meta:
            ordering = ('name',)
    
    class Movie(models.Model):
        title = models.CharField(max_length=100)
        actors = models.ManyToManyField(Actor)
        year = models.IntegerField()
    
        def __str__(self):
            return self.title
    
        class Meta:
            ordering = ('title',)
    

    Al igual que con el esquema GraphQL, el Actor modelo tiene un nombre mientras que el Movie modelo tiene un título, una relación de muchos a muchos con los actores y un año. Django genera automáticamente los ID.

    Ahora podemos registrar nuestra aplicación de películas dentro del proyecto. Ir al django_graphql_movies/settings.py y cambia el INSTALLED_APPS a lo siguiente:

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'django_graphql_movies.movies',
    ]
    

    Asegúrese de migrar su base de datos para mantenerla sincronizada con nuestros cambios de código:

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

    Cargando datos de prueba

    Después de que construyamos nuestra API, querremos poder realizar consultas para probar si funciona. Carguemos algunos datos en nuestra base de datos, guarde el siguiente JSON como movies.json en el directorio raíz de su proyecto:

    [
      {
        "model": "movies.actor",
        "pk": 1,
        "fields": {
          "name": "Michael B. Jordan"
        }
      },
      {
        "model": "movies.actor",
        "pk": 2,
        "fields": {
          "name": "Sylvester Stallone"
        }
      },
      {
        "model": "movies.movie",
        "pk": 1,
        "fields": {
          "title": "Creed",
          "actors": [1, 2],
          "year": "2015"
        }
      }
    ]
    

    Y ejecute el siguiente comando para cargar los datos de prueba:

    $ python manage.py loaddata movies.json
    

    Debería ver la siguiente salida en la terminal:

    Installed 3 object(s) from 1 fixture(s)
    

    Creando nuestro esquema con grafeno

    Realización de consultas

    En nuestra carpeta de aplicaciones de películas, cree una nueva schema.py file y definamos nuestros tipos GraphQL:

    import graphene
    from graphene_django.types import DjangoObjectType, ObjectType
    from django_graphql_movies.movies.models import Actor, Movie
    
    # Create a GraphQL type for the actor model
    class ActorType(DjangoObjectType):
        class Meta:
            model = Actor
    
    # Create a GraphQL type for the movie model
    class MovieType(DjangoObjectType):
        class Meta:
            model = Movie
    

    Con la ayuda de Graphene, para crear un tipo GraphQL simplemente especificamos qué modelo de Django tiene las propiedades que queremos en la API.

    Te puede interesar:Cómo enviar un correo electrónico con boto y SES

    En el mismo archivo, agregue el siguiente código para crear el Query tipo:

    # Create a Query type
    class Query(ObjectType):
        actor = graphene.Field(ActorType, id=graphene.Int())
        movie = graphene.Field(MovieType, id=graphene.Int())
        actors = graphene.List(ActorType)
        movies= graphene.List(MovieType)
    
        def resolve_actor(self, info, **kwargs):
            id = kwargs.get('id')
    
            if id is not None:
                return Actor.objects.get(pk=id)
    
            return None
    
        def resolve_movie(self, info, **kwargs):
            id = kwargs.get('id')
    
            if id is not None:
                return Movie.objects.get(pk=id)
    
            return None
    
        def resolve_actors(self, info, **kwargs):
            return Actor.objects.all()
    
        def resolve_movies(self, info, **kwargs):
            return Movie.objects.all()
    

    Cada propiedad del Query clase corresponde a una consulta GraphQL:

    • los actor y movie propiedades devuelven un valor de ActorType y MovieType respectivamente, y ambos requieren un ID que sea un número entero.
    • los actors y movies las propiedades devuelven una lista de sus respectivos tipos.

    Los cuatro métodos que creamos en la clase Query se llaman resolutores. Los solucionadores conectan las consultas en el esquema con las acciones reales realizadas por la base de datos. Como es estándar en Django, interactuamos con nuestra base de datos a través de modelos.

    Considera el resolve_actor función. Recuperamos el ID de los parámetros de consulta y devolvemos el actor de nuestra base de datos con ese ID como clave principal. los resolve_actors La función simplemente obtiene todos los actores de la base de datos y los devuelve como una lista.

    Haciendo mutaciones

    Cuando diseñamos el esquema, primero creamos tipos de entrada especiales para nuestras mutaciones. Hagamos lo mismo con el grafeno, agregue esto a schema.py:

    # Create Input Object Types
    class ActorInput(graphene.InputObjectType):
        id = graphene.ID()
        name = graphene.String()
    
    class MovieInput(graphene.InputObjectType):
        id = graphene.ID()
        title = graphene.String()
        actors = graphene.List(ActorInput)
        year = graphene.Int()
    

    Son clases simples que definen qué campos se pueden usar para cambiar datos en la API.

    La creación de mutaciones requiere un poco más de trabajo que la creación de consultas. Agreguemos las mutaciones para los actores:

    # Create mutations for actors
    class CreateActor(graphene.Mutation):
        class Arguments:
            input = ActorInput(required=True)
    
        ok = graphene.Boolean()
        actor = graphene.Field(ActorType)
    
        @staticmethod
        def mutate(root, info, input=None):
            ok = True
            actor_instance = Actor(name=input.name)
            actor_instance.save()
            return CreateActor(ok=ok, actor=actor_instance)
    
    class UpdateActor(graphene.Mutation):
        class Arguments:
            id = graphene.Int(required=True)
            input = ActorInput(required=True)
    
        ok = graphene.Boolean()
        actor = graphene.Field(ActorType)
    
        @staticmethod
        def mutate(root, info, id, input=None):
            ok = False
            actor_instance = Actor.objects.get(pk=id)
            if actor_instance:
                ok = True
                actor_instance.name = input.name
                actor_instance.save()
                return UpdateActor(ok=ok, actor=actor_instance)
            return UpdateActor(ok=ok, actor=None)
    

    Recuerde la firma del createActor mutación cuando diseñamos nuestro esquema:

    createActor(input: ActorInput) : ActorPayload
    
    • El nombre de nuestra clase corresponde al nombre de la consulta de GraphQL.
    • El interior Arguments las propiedades de la clase corresponden a los argumentos de entrada para el mutador.
    • los ok y actor propiedades componen el ActorPayload.

    La clave a saber al escribir un mutation El método es que está guardando los datos en el modelo Django:

    • Tomamos el nombre del objeto de entrada y creamos un nuevo Actor objeto.
    • Llamamos al save función para que nuestra base de datos se actualice y devuelva la carga útil al usuario.

    los UpdateActor La clase tiene una configuración similar con lógica adicional para recuperar el actor que se está actualizando y cambiar sus propiedades antes de guardar.

    Ahora agreguemos la mutación para películas:

    # Create mutations for movies
    class CreateMovie(graphene.Mutation):
        class Arguments:
            input = MovieInput(required=True)
    
        ok = graphene.Boolean()
        movie = graphene.Field(MovieType)
    
        @staticmethod
        def mutate(root, info, input=None):
            ok = True
            actors = []
            for actor_input in input.actors:
              actor = Actor.objects.get(pk=actor_input.id)
              if actor is None:
                return CreateMovie(ok=False, movie=None)
              actors.append(actor)
            movie_instance = Movie(
              title=input.title,
              year=input.year
              )
            movie_instance.save()
            movie_instance.actors.set(actors)
            return CreateMovie(ok=ok, movie=movie_instance)
    
    
    class UpdateMovie(graphene.Mutation):
        class Arguments:
            id = graphene.Int(required=True)
            input = MovieInput(required=True)
    
        ok = graphene.Boolean()
        movie = graphene.Field(MovieType)
    
        @staticmethod
        def mutate(root, info, id, input=None):
            ok = False
            movie_instance = Movie.objects.get(pk=id)
            if movie_instance:
                ok = True
                actors = []
                for actor_input in input.actors:
                  actor = Actor.objects.get(pk=actor_input.id)
                  if actor is None:
                    return UpdateMovie(ok=False, movie=None)
                  actors.append(actor)
                movie_instance.title=input.title
                movie_instance.year=input.year
                movie_instance.save()
                movie_instance.actors.set(actors)
                return UpdateMovie(ok=ok, movie=movie_instance)
            return UpdateMovie(ok=ok, movie=None)
    

    Como las películas hacen referencia a los actores, tenemos que recuperar los datos de los actores de la base de datos antes de guardarlos. los for loop primero verifica que los actores proporcionados por el usuario estén efectivamente en la base de datos, si no, regresa sin guardar ningún dato.

    Cuando trabajamos con relaciones de muchos a muchos en Django, solo podemos guardar datos relacionados después de que se guarde nuestro objeto.

    Por eso guardamos nuestra película con movie_instance.save() antes de poner a los actores con movie_instance.actors.set(actors).

    Para completar nuestras mutaciones, creamos el tipo de mutación:

    Te puede interesar:Git: clonar un repositorio
    class Mutation(graphene.ObjectType):
        create_actor = CreateActor.Field()
        update_actor = UpdateActor.Field()
        create_movie = CreateMovie.Field()
        update_movie = UpdateMovie.Field()
    

    Haciendo el esquema

    Como antes, cuando diseñamos nuestro esquema, asignamos las consultas y mutaciones a la API de nuestra aplicación. Agregue esto al final de schema.py:

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

    Registro del esquema en el proyecto

    Para que nuestra API funcione, necesitamos hacer que un esquema esté disponible para todo el proyecto.

    Crear un nuevo schema.py presentar en django_graphql_movies/ y agregue lo siguiente:

    import graphene
    import django_graphql_movies.movies.schema
    
    class Query(django_graphql_movies.movies.schema.Query, graphene.ObjectType):
        # This class will inherit from multiple Queries
        # as we begin to add more apps to our project
        pass
    
    class Mutation(django_graphql_movies.movies.schema.Mutation, graphene.ObjectType):
        # This class will inherit from multiple Queries
        # as we begin to add more apps to our project
        pass
    
    schema = graphene.Schema(query=Query, mutation=Mutation)
    

    Desde aquí podemos registrar el grafeno y decirle que use nuestro esquema.

    Abierto django_graphql_movies/settings.py y añadir 'graphene_django', como primer elemento de la INSTALLED_APPS.

    En el mismo archivo, agregue el siguiente código un par de líneas nuevas debajo del INSTALLED_APPS:

    GRAPHENE = {
        'SCHEMA': 'django_graphql_movies.schema.schema'
    }
    

    Las API de GraphQL se alcanzan a través de un punto final, /graphql. Necesitamos registrar esa ruta, o más bien vista, en Django.

    Abierto django_graphql_movies/urls.py y cambie el contenido del archivo a:

    from django.contrib import admin
    from django.urls import path
    from graphene_django.views import GraphQLView
    from django_graphql_movies.schema import schema
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('graphql/', GraphQLView.as_view(graphiql=True)),
    ]
    

    Probando nuestra API

    Para probar nuestra API, ejecutemos el proyecto y luego vayamos al punto final GraphQL. En el tipo de terminal:

    $ python manage.py runserver
    

    Una vez que su servidor esté funcionando, diríjase a http://127.0.0.1:8000/graphql/. Te encontrarás GraphiQL – ¡un IDE integrado para ejecutar sus consultas!

    Escribir consultas

    Para nuestra primera consulta, obtengamos todos los actores en nuestra base de datos. En el panel superior izquierdo, ingrese lo siguiente:

    query getActors {
      actors {
        id
        name
      }
    }
    

    Este es el formato para una consulta en GraphQL. Comenzamos con el query palabra clave, seguida de un nombre opcional para la consulta. Es una buena práctica dar un nombre a las consultas, ya que ayuda con el registro y la depuración. GraphQL nos permite especificar los campos que queremos también – elegimos id y name.

    Aunque solo tenemos una película en nuestros datos de prueba, probemos el movie consulta y descubre otra gran característica de GraphQL:

    query getMovie {
      movie(id: 1) {
        id
        title
        actors {
          id
          name
        }
      }
    }
    

    los movie La consulta requiere un ID, por lo que proporcionamos uno entre paréntesis. Lo interesante viene con el actors campo. En nuestro modelo de Django incluimos el actors propiedad en nuestra Movie class y especificó una relación de varios a varios entre ellos. Esto nos permite recuperar todas las propiedades de un Actor tipo que está relacionado con los datos de la película.

    Este recorrido de datos en forma de gráfico es una de las principales razones por las que GraphQL se considera una tecnología poderosa y emocionante.

    Te puede interesar:Git: crear un nuevo repositorio

    Escritura de mutaciones

    Las mutaciones siguen un estilo similar a las consultas. Agreguemos un actor a nuestra base de datos:

    mutation createActor {
      createActor(input: {
        name: "Tom Hanks"
      }) {
        ok
        actor {
          id
          name
        }
      }
    }
    

    Note como el input parámetro corresponde al input propiedades del Arguments clases que creamos anteriormente.

    También observe cómo ok y actor los valores de retorno se asignan a las propiedades de clase del CreateActor mutación.

    Ahora podemos agregar una película en la que actuó Tom Hanks:

    mutation createMovie {
      createMovie(input: {
        title: "Cast Away",
        actors: [
          {
            id: 3
          }
        ]
        year: 1999
      }) {
        ok
        movie{
          id
          title
          actors {
            id
            name
          }
          year
        }
      }
    }
    

    Desafortunadamente, cometimos un error. ¡»Cast Away» salió en el año 2000!

    Ejecutemos una consulta de actualización para solucionarlo:

    mutation updateMovie {
      updateMovie(id: 2, input: {
        title: "Cast Away",
        actors: [
          {
            id: 3
          }
        ]
        year: 2000
      }) {
        ok
        movie{
          id
          title
          actors {
            id
            name
          }
          year
        }
      }
    }
    

    ¡Ahí, todo arreglado!

    Comunicación vía POST

    GraphiQL es muy útil durante el desarrollo, pero es una práctica estándar deshabilitar esa vista en producción, ya que puede permitir que un desarrollador externo tenga demasiada información sobre la API.

    Para deshabilitar GraphiQL, simplemente edite django_graphql_movies/urls.py tal que path('graphql/', GraphQLView.as_view(graphiql=True)), se convierte en path('graphql/', GraphQLView.as_view(graphiql=False)),.

    Una aplicación que se comunica con su API enviaría solicitudes POST al /graphql punto final. Antes de que podamos realizar solicitudes POST desde fuera del sitio de Django, debemos cambiar django_graphql_movies/urls.py:

    from django.contrib import admin
    from django.urls import path
    from graphene_django.views import GraphQLView
    from django_graphql_movies.schema import schema
    from django.views.decorators.csrf import csrf_exempt # New library
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
    ]
    

    Django viene integrado con CSRF Protección (Falsificación de solicitudes entre sitios): tiene medidas para evitar que los usuarios autenticados incorrectamente del sitio realicen acciones potencialmente maliciosas.

    Si bien esta es una protección útil, evitaría que las aplicaciones externas se comuniquen con la API. Debería considerar otras formas de autenticación si pone su aplicación en producción.

    En su terminal ingrese lo siguiente para obtener todos los actores:

    $ curl 
      -X POST 
      -H "Content-Type: application/json" 
      --data '{ "query": "{ actors { name } }" }' 
      http://127.0.0.1:8000/graphql/
    

    Tu deberías recibir:

    {"data":{"actors":[{"name":"Michael B. Jordan"},{"name":"Sylvester Stallone"},{"name":"Tom Hanks"}]}}
    

    Conclusión

    GraphQL es un lenguaje de consulta fuertemente tipado que ayuda a crear API evolutivas. Diseñamos un esquema de API para películas, creando los tipos, consultas y mutaciones necesarias para obtener y cambiar datos.

    Te puede interesar:Preguntas de la entrevista de programación de listas vinculadas

    Con Graphene podemos usar Django para crear API GraphQL. Implementamos el esquema de película que diseñamos anteriormente y lo probamos usando consultas GraphQL a través de GraphiQL y una solicitud POST estándar.

    Si desea ver el código fuente de la aplicación completa, puede encontrarlo aquí.

    Rate this post

    Etiquetas: