Introducción
Contenido
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 ID
s, 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:
$ . 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 SESEn 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
ymovie
propiedades devuelven un valor deActorType
yMovieType
respectivamente, y ambos requieren un ID que sea un número entero. - los
actors
ymovies
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
yactor
propiedades componen elActorPayload
.
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 repositorioclass 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 repositorioEscritura 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 vinculadasCon 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í.