# Graphql en python

# Introducción

Graphql es un lenguaje de consultas que provee una completa descripción de los datos de tu API, permitiendo al cliente recuperar solo los datos que necesita. Esto permite resolver algunos de los problemas más comunes como por ejemplo obtener menos datos de los que la aplicación necesita (underfetching) por lo que obliga al cliente realizar una segunda consulta al servidor, o recuperar más información del requerido (overfetching) por lo que los datos superfluos se descartan.

En este tutorial vamos a crear un API Graphql en que cual integraremos [Flask](https://flask.palletsprojects.com/en/2.2.x/) y [Ariadne](https://flask.palletsprojects.com/en/2.2.x/). Flask es un ligero framework para desarrollo de aplicaciones web, diseñado para comenzar rápido y fácil, por lo que se ha convertido en uno de los frameworks Python más populares. Ariadne es un framework schema-first que permite definir APIs Graphql en Python y fácil de integrar con Flask.

# Consideraciones

Este tutorial es completamente práctico, por lo que no voy a ahondar en conceptos teóricos por lo que se requiere tener algunas nociones sobre desarrollo en Python, Flask, Graphql (tipos de datos, queries, mutaciones, etc).

# Proyecto

El proyecto consiste en un API Graphql para guardar, consultar, modificar y eliminar datos sobre autores y libros. Estableceremos una relación "one to many" de forma que un autor puede tener muchos libros, y un libro pertenece a un autor. Generaremos las `queries` de forma que al consultar los autores podamos acceder a los libros que tienen y, al consultar los libros poder ver los autores al que pertenecen. Las `mutations` nos permitirán añadir autores y libros, actualizarlos y eliminarlos.

# Instalación

Comencemos creando el directorio del proyecto, yo lo llamé `tutorial-flask-ariadne`. Dentro de esta carpeta definimos un entorno virtual, para ello abre la consola e ingresa el siguiente comando `python -m venv venv`. Para activar el entorno escribe `venv\Scripts\activate`.

Instalamos Flask y Ariadne con el siguiente comando.

```javascript
pip install -U flask ariadne
```

Seguimos con la instalación de flask-sqlalchemy, flask-marshmallow y sqlalchemy-marshmallow.

```javascript
pip install -U flask-sqlalchemy flask-marshmallow marshmallow-sqlalchemy
```

Para poder conectarnos con nuestra base de datos necesitamos un driver. Lo instalamos como sigue `python -m pip install PyMySQL`.

Finalmente instalamos `python-dotenv` que nos permitirá leer nuestras variables de entorno.

# Base de datos

Abre tu gestor de base de datos y crea una para nuestra app. Yo lo llamé `tutorial_flask_ariadne`. En la raíz del proyecto añade un fichero `.env` y define las siguientes variables de entorno.

```apache
USER="root"
PASSWORD=
PORT=3306
DATABASE="tutorial_flask_ariadne"
HOST="localhost"
```

# Flask y Ariadne

A continuación integramos flask con ariadne para crear un servidor graphql. En la raíz de tu proyecto añade un fichero `main.py` y agrega el siguiente código.

```python
from ariadne import QueryType, graphql_sync, make_executable_schema
from ariadne.constants import PLAYGROUND_HTML
from flask import Flask, request, jsonify

type_defs = """
    type Query {
        hello: String!
    }
"""

query = QueryType()


@query.field("hello")
def resolve_hello(_, info):
    request = info.context
    user_agent = request.headers.get("User-Agent", "Guest")
    return "Hello, %s!" % user_agent


schema = make_executable_schema(type_defs, query)

app = Flask(__name__)


@app.route("/graphql", methods=["GET"])
def graphql_playground():
    return PLAYGROUND_HTML, 200


@app.route("/graphql", methods=["POST"])
def graphql_server():
    data = request.get_json()

    success, result = graphql_sync(
        schema,
        data,
        context_value=request,
        debug=app.debug
    )

    status_code = 200 if success else 400
    return jsonify(result), status_code


if __name__ == "__main__":
    app.run(debug=True)
```

Este código lo tomamos de la documentación oficial de Ariadne. Lo que podemos destacar son los dos `endpoint` principales. El primero captura una petición `GET` que nos da acceso al `playground` en el cual podremos testear nuestra API Graphql. El segundo captura las peticiones `POST` el cual se encarga de llevar a cabo nuestras `queries` y `mutations`.

También podemos ver una variable `type_defs` en el cual definiremos nuestra `queries` , `mutations`, `types`, etc. En este caso se define una sola consulta `hello` que retorna un `string`. La función `resolve_hello` es la que se encarga de resolver esta `Query`; en este caso retorna un saludo "Hello" y la información del navegador.

Con el comando `flask --app main run` puedes correr el servidor de desarrollo. Ingresa al `endpoint` `127.0.0.1:5000/graphql` para ver el `playground`.

![Captura de pantalla 2022-10-17 152458.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666031528053/Y53xWBxMN.png align="left")

![Captura de pantalla 2022-10-17 152602.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666031541641/LzrQfpcOP.png align="left")

A continuación seguiremos con la definición de los modelos y los esquemas de serialización.

# Modelos

Comencemos creando un nuevo directorio `models` para los modelos. Dentro de este directorio añade un fichero `__init__.py`. Este fichero le indica al interprete de Python que el directorio es un paquete en el cual estarán nuestros módulos. Usaremos este fichero para inicializar SQLAlchemy y Marshmallow.

```python
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow

db = SQLAlchemy()
ma = Marshmallow()
```

Ahora creamos los archivos `author.py` y `book.py` para los modelos autor y libro respectivamente. Definiremos el modelo `author` primero. Dentro de este fichero inserta el siguiente código.

```python
from models import db

class Author (db.Model):
    __tablename__= "authors"
    id= db.Column(db.Integer, primary_key=True)
    name= db.Column(db.String(255))
    lastname= db.Column(db.String(255))
    created_at= db.Column(db.DateTime, server_default=db.func.now())
    updated_at= db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now())
    books= db.relationship("Book", backref="author", cascade="all, delete-orphan")
```

Lo primero que podemos ver es que importamos `db` que es la instancia de `SQLAlchamy()` que definimos anteriormente. Seguimos con la declaración de la clase `Author`, los campos y la relación. Con la directiva `__tablename__` indicamos el nombre de la tabla. Los campos `id`, `name` y `lastname` se explican por sí mismos. Los campos `created_at` y `updated_at` son `timestamps` que registran la fecha en que un registro es creado y actualizado.

La última línea define la relación. El primer parámetro indica el modelo al cual se vincula, en este caso `Book`. El parámetro `backref` agrega una referencia en el modelo `Book` que se comportará como la columna que le dará acceso al autor del libro. El parámetro `cascade` significa de que si el registro de la tabla padre (`Author`) es borrado, todos los registros relacionados en la tabla hija (`Book`) serán eliminados también.

Continuamos con el fichero `book.py`.

```python
from models import db

class Book (db.Model):
    __tablename__="books"
    id= db.Column(db.Integer, primary_key=True)
    title= db.Column(db.String(255))

    created_at= db.Column(db.DateTime, server_default=db.func.now())
    updated_at= db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now())

    author_id = db.Column(db.Integer, db.ForeignKey("authors.id"))
```

La definición de este modelo no difiere del anterior. Tenemos un campo `id`, `title` para el título del libro y los campos para los `timestamps`. Lo que podemos destacar es el campo `author_id` que es la que usamos como llave foránea para vincular esta tabla con la tabla autor, usando su `id`. De esta manera queda definida la relación "one to many" entre las tablas.

Seguimos con la definición de los esquemas para serialización/deserialización.

# Esquemas

Para mantener esto simple, definiremos los ficheros para los esquemas dentro del mismo directorio `models`. Crea dos archivos `authorSchema.py` y `bookSchema.py`. Primero `authorSchema.py`.

```python
from models import ma
from models.author import Author

class AuthorSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = Author
        include_relationships = True
    books= ma.List(ma.Nested("BookSchema", exclude=("author",)))
```

Primero importamos la instancia de Marshmallow , `ma`. En la segunda línea indicamos que del módulo `models.author` queremos importar la clase `Author`. Definimos la clase `AuthorSchema` y le pasamos como parámetro `ma.SQLAlchemyAuthorSchema`. Con el atributo `model` vinculamos este esquema con el modelo `Author`. `include_relationships = True` indica que nos incluya las relaciones declaradas en el modelo.

La línea `books= ma.List(ma.Nested("BookSchema", exclude=("author",)))` indica que el atributo `books` es una arreglo de libros, el cual tiene un esquema anidado (`BookSchema`). Es importante destacar la opción `exclude`. En él declaramos que excluya este campo, para evitar recursión infinita.

Continuamos con el esquema para los libros. Abre el fichero `bookSchema.py` y añade.

```python
from models import ma
from models.book import Book

class BookSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = Book
        include_fk = True
    author= ma.Nested("AuthorSchema", only=("id", "name", "lastname"))
```

Como puedes ver no difiere mucho del esquema anterior. Mediante el atributo `model` vinculamos este esquema con el modelo `Book`. Con `include_fk` indicamos que nos incluya la llave foránea.

La línea `author= ma.Nested("AuthorSchema", only=("id", "name", "lastname"))` indica que el atributo `author` tiene un esquema anidado `AuthorSchema` para acceder a los datos del autor. Con la opción `only` señalamos los campos que queremos recuperar de `AuthorSchema`. Al igual que la opción `exclude` de `AuthorSchema`, nos sirve para evitar recursión infinita.

Con esto finalizamos con los modelos y los esquemas. Continuamos con la conexión a la base de datos.

# Base de datos

Para establecer la conexión primero importamos `import os` para interactuar con las funciones del sistema operativo. Lo utilizaremos para obtener los valores de las variables de entorno que definimos en el fichero `.env`.

También importamos los módulos del directorio `models`.

```python
from models import db, ma
from models.author import Author
from models.book import Book
from models.authorSchema import AuthorSchema
from models.bookSchema import BookSchema
```

Justo después de definir `app=Flask(__name__)` agregamos la conexión.

```python
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://{}@{}:{}/{}".format(os.environ.get("USER"), os.environ.get("HOST"), os.environ.get("PORT"), os.environ.get("DATABASE"))
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db.init_app(app)
ma.init_app(app)

with app.app_context():
    db.create_all()
```

La opción `app.config["SQLALCHEMY_DATABASE_URI"]` recibe un `string` con los valores de `USER`, `PORT` y `DATABASE`.

Luego inicializamos las extensiones de `SQLAlchemy` y `Marshmallow`, en ese orden. Finalmente la función `db.create_all()` crea las tablas en nuestra base de datos con la estructura que definimos en los modelos.

Reinicia el servidor de desarrollo para que los cambios tengan efecto.

![Captura de pantalla 2022-10-18 174612.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666126008217/vH7xez4Fo.png align="left")

Finalizamos con la conexión. Seguimos con la definición de las `queries`, `mutations`, `object types` y los `resolvers`.

# Object types, queries y mutations

Las `queries` y `mutations` los vamos a definir en un paquete propio. Crea un directorio `schemas` y añade el correspondiente fichero `__init__.py`. Crea un archivo `type_defs.py`. Comenzaremos definiendo los `object types`.

### Object types

Abre el fichero `type_defs.py` y inserta el siguiente código.

```python
from ariadne import gql, ScalarType
from datetime import datetime

datetime_scalar = ScalarType("Datetime")
@datetime_scalar.serializer
def datetime_serializer(value):
    return datetime.strptime(value,"%Y-%m-%dT%H:%M:%S")

type_defs = gql("""
    scalar Datetime

    type Author {
        id: ID
        name: String
        lastname: String
        books: [Book]
        created_at: Datetime
        updated_at: Datetime
    }

    type Book {
        id: ID
        title: String
        author: Author!
        created_at: Datetime
        updated_at: Datetime
    }
""")
```

La función `gql` nos provee una forma de validar los esquemas que definimos. `ScalarType` nos permite definir `scalar types` personalizados. Lo usaremos para definir el `scalar Datetime` para manejar fechas. La función `datetime_serializer` le dice a Ariadne el formato que queremos para las fechas.

En la variable `type_defs` que declaramos definimos los tipos `Author` y `Book`. Lo que podemos destacar en el tipo `Author` es el campo `books`, en él indicamos que retorna un arreglo de libros y puede ser nulo. En el tipo `Book` el campo `author` debe retornar un `Author` y no puede ser nulo.

### Queries

Justo a continuación del tipo `Book` añadimos.

```python
type_defs = gql("""
    scalar Datetime

    type Author {
        id: ID
        name: String
        lastname: String
        books: [Book]
        created_at: Datetime
        updated_at: Datetime
    }

    type Book {
        id: ID
        title: String
        author: Author!
        created_at: Datetime
        updated_at: Datetime
    }

    type Query {
        author(id: ID!): Author!
        authors: [Author]
        book(id: ID!): Book!
        books: [Book]
    }
""")
```

Las `queries` `author(id: ID!): Author!` y `book(id: ID!): Book!` retornan un objeto de tipo `Author` y `Book` respectivamente. Las `queries` `authors: [Author]` y `books: [Book]` retornan un arreglo de objetos de tipo `Author` y `Book`.

### Mutations

Continuamos con las mutaciones.

```python
type_defs = gql("""
    scalar Datetime

    type Author {
        id: ID
        name: String
        lastname: String
        books: [Book]
        created_at: Datetime
        updated_at: Datetime
    }

    type Book {
        id: ID
        title: String
        author: Author!
        created_at: Datetime
        updated_at: Datetime
    }

    type Query {
        author(id: ID!): Author!
        authors: [Author]
        book(id: ID!): Book!
        books: [Book]
    }

    type Mutation {
        addAuthor(name: String!, lastname: String!): Author!
        addBook(title: String!, author_id: ID): Book!

        updateAuthor(id: ID!, name: String!, lastname: String!): Author!
        updateBook(id: ID!, title: String!): Book!

        deleteAuthor(id: ID!): Author!
        deleteBook(id: ID!): Book!
    }
""")
```

Las mutaciones `addAuthor` y `addBook` nos permiten añadir un autor y un libro respectivamente. `updateAuthor` y `updateBook` nos permiten actualizarlos y finalmente tenemos dos mutaciones más `deleteAuthor` y `deleteBook` para eliminarlos.

Finalmente debemos importar los tipos definidos en `type_defs` y el scalar personalizado `datetime_scalar` al fichero `main.py`

```python
from schemas.type_defs import type_defs, datetime_scalar
```

Lo pasamos a la función `make_executable_schema`.

```python
schema = make_executable_schema(type_defs, [query, datetime_scalar])
```

# Resolvers

Antes de comenzar con los `resolvers` creamos las instancias de los esquemas que vamos a utilizar.

```python
author_schema = AuthorSchema()
authors_schema = AuthorSchema(many=True)

book_schema = BookSchema()
books_schema = BookSchema(many=True)
```

Los esquemas `author_schema` y `book_schema` retornan un único objeto, mientras que los esquemas `authors_schema` y `books_schema` los usaremos para retornar un arreglo de objetos.

Ahora comenzamos con los `resolvers` para las consultas.

```python
@query.field("authors")
def resolve_authors(_, info):
    data = Author.query.all()
    return authors_schema.dump(data)

@query.field("books")
def resolve_books(_, info):
    data = Book.query.all()
    return books_schema.dump(data)

@query.field("author")
def resolve_author(_, info, id):
    data = Author.query.get(id)
    return author_schema.dump(data)

@query.field("book")
def resolve_book(_, info, id):
    data = Book.query.get(id)
    return book_schema.dump(data)
```

El decorador `@query.field()` recibe un `string` con el nombre del `query` al que queremos vincular el resolver.

El `resolver` `resolve_authors` hace uso del modelo `Author` para recuperar todos los datos de la base de datos y los guarda en la variable `data`. Luego retornamos los datos serializado con `authors_schema.dump`.

El `resolver` para los libros hace lo mismo. El modelo `Book` recupera todos los registros y retorna los datos serializados con `books_schema.dump`.

El `resolver` `resolve_author` recibe un `id`. Mediante el modelo `Author` recuperamos el autor con ese `id` y lo guarda en `data`. Retornamos los datos serializados con `author_schema.dump`.

Los mismo para `resolve_book`. Recibe el `id` con el que el modelo `Book` obtiene el libro de la base de datos. Serializa los datos con `book_schema.dump` y los retorna.

Ahora continuamos con las mutaciones. Primero importamos las clase `MutationType` y pasamos la instancia a `make_executable_schema`.

```python
from ariadne import QueryType, MutationType,graphql_sync, make_executable_schema

mutation = MutationType()

schema = make_executable_schema(type_defs, [query, mutation, datetime_scalar])
```

Ahora definimos los `resolvers` para las mutaciones.

```python
@mutation.field("addAuthor")
def resolve_add_author(_, info, name, lastname):
    author = Author(name=name, lastname=lastname)
    db.session.add(author)
    db.session.commit()
    return author_schema.dump(author)

@mutation.field("addBook")
def resolve_add_book(_, info, title, author_id):
    author = Author.query.get(author_id)
    book = Book(title=title, author=author)
    db.session.add(book)
    db.session.commit()
    return book_schema.dump(book)

@mutation.field("updateAuthor")
def resolve_update_author(_, info, id, name, lastname):
    author = Author.query.get(id)
    author.name = name
    author.lastname = lastname
    db.session.commit()
    return author_schema.dump(author)

@mutation.field("updateBook")
def resolve_update_book(_, info, id, title):
    book = Book.query.get(id)
    book.title = title
    db.session.commit()
    return book_schema.dump(book)

@mutation.field("deleteAuthor")
def resolve_delete_author(_, info,  id):
    author = Author.query.get(id)
    db.session.delete(author)
    db.session.commit()
    return author_schema.dump(author)

@mutation.field("deleteBook")
def resolve_delete_book(_, info, id):
    book = Book.query.get(id)
    book.author = None
    db.session.delete(book)
    db.session.commit()
    return book_schema.dump(book)
```

El decorador `@mutation.field()` recibe un `string` con el nombre de la mutación al que queremos vincular el `resolver`.

`resolve_add_author` recibe `name` y `lastname`. Creamos una instancia del modelo `Author`, le pasamos los parámetros. Con `db.session.add` y `db.session.commit` añadimos el registro a la base de datos. Luego retornamos los datos del nuevo registro con `author_schema.dump(author)`.

`resolver_add_book` es similar al anterior. Recibe `title` y `author_id`. Primero recuperamos el autor del libro por medio de `Author.query.get(id)` y lo guardamos en la variable `author`. Luego creamos una instancia del modelo `Book` y le pasamos el `title` y el `author`. Añadimos el registro y luego retornamos los datos del nuevo libro con `book_schema.dump(book)`.

`resolve_update_author` recibe `id`, `name` y `lastname`. Con `Author.query.get(id)` recuperamos el autor y lo guardamos en `author`. Actualizamos los datos y luego con `db.session.commit` guardamos los cambios. Finalmente retornamos los datos del autor actualizado.

`resolve_update_book` recibe `id` y `title`. Obtenemos el libro con `Book.query.get(id)` y lo guardamos en la variable `book`. Actualizamos en título y con `db.session.commit` guardamos los cambio. Luego retornamos los datos del libro actualizado.

`resolve_delete_author` recibe un `id`. Obtenemos el autor que queremos eliminar con `Author.query.get(id)` y lo guardamos en la variable `author`. Por medio de `db.session.delete(author)` eliminamos el autor y guardamos los cambios con `db.session.commit`. Esto debe eliminar el autor y los libros que tiene. Retornamos los datos del autor eliminado.

`resolve_delete_book` recibe un `id`. Recuperamos el libro con `Book.query.get(id)` y lo guardamos en `book`. Antes de eliminar el libro debemos desvincularlo de su autor. Para ello establecemos a `None` el atributo `book.author`. Ahora sí, eliminamos el registro con `db.session.delete(book)`. Esto debe eliminar el libro, pero no el autor. Finalmente retornamos los datos del libro eliminado.

Con esto finalizamos de construir el API Graphql, ahora es tiempo de ponerlo a prueba y ver su funcionalidad.

# Probando el API Graphql

Comencemos reinicializando el servidor de desarrollo `flask --app main run`. Entra al `endpoint` `127.0.0.1:5000/graphql` para ver el "playground".

Primer añadiremos algunos autores.

![mutacion_add_author.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666290670333/h3QZFsQJp.png align="left")

Observamos que definimos la mutación que queremos ejecutar, indicamos que recibe las variables `$name` y `$lastname` ambas de tipo `String`. En la parte inferior podemos ver la sección donde pasamos los valores a la mutación. Estoy insertando a "Agatha Christie". También voy a insertar a "Wilbur Smith". Podemos ver los registros en la tabla.

![mutacion_add_author_resultado.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666291121497/GUhfaQXyw.png align="left")

Excelente, ahora probemos insertar algunos libros de los autores.

![mutacion_add_book.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666291308761/8B2bjNomE.png align="left")

Definimos otra mutación para añadir un libro, e indicamos que recibe las variables `$title` de tipo `String` y `$id` de tipo `ID`. En la sección de variables pasamos el título del libro y el `id` del autor. En este caso "Muerte en el Nilo", que pertenece a "Agatha Christie". También voy a añadir "Asesinato en el Orient Express" de la misma autora y "Muere un gorrión" de "Wilbur Smith". Podemos ver los registros en la tabla.

![tabla_add_book.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666291873791/6szDBvdKZ.png align="left")

Seguimos probando las mutaciones para actualizar datos del autor.

![mutacion_update_author.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666292324207/SC3GSKN00.png align="left")

Al igual que en los ejemplos anteriores definimos la mutación que queremos y definimos las variables que recibe. En la sección de variables indicamos que queremos modificar los valores del autor con `id` 1. Paso cualquier valor ya que es solo para verificar la funcionalidad. Lo vemos en la tabla.

![tabla_update_author.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666292552552/CC_c-pKG8.png align="left")

Todo bien hasta ahora, seguimos con la actualización de los libros.

![mutacion_update_book.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666292806234/rlR62l9bB.png align="left")

Estoy modificando el título del libro con `id` 1. En este caso es el mismo nombre pero en inglés. Lo vemos en la tabla.

![tabla_update_book.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666292864980/JL_SkzXs7.png align="left")

Antes de probar las mutaciones para eliminar datos, voy a proba las consultas. Empecemos recuperando los autores.

![query_all_authors.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666293129041/7CkrDf28y.png align="left")

![query_all_authors_resultado.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666293147117/ZmWbJHLnB.png align="left")

Definimos la consulta e indicamos que además de los datos del autor queremos ver los libros que tienen.

Ahora probamos la consulta de un autor por su `id`.

![query_author.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666293543674/hdBECLUGW.png align="left")

![query_author_resultado.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666293553844/dH4_YT-1d.png align="left")

Definimos la consulta para obtener un autor y le pasamos el correspondiente `id`. Además de los datos del autor, recuperamos los títulos de sus libros.

Probamos la funcionalidad para obtener todos los libros.

![query_all_books.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666297687069/6UHoH5tel.png align="left")

![query_all_books_resultado.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666297702171/OWdwvY-n_.png align="left")

Además del título del libro obtenemos los datos del autor.

Ahora probamos obtener un libro por su `id`.

![query_book.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666297900922/NefVKGGOQ.png align="left")

![query_book_resultado.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666297914800/263EYIrN9.png align="left")

Recuperamos los datos del libro y además los datos del autor.

Nos toca probar las mutaciones para eliminar datos. Comencemos eliminando un libro.

![mutacion_delete_book.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666293900686/HI7Bd5qlm.png align="left")

Definimos la mutación para eliminar un libro y le pasamos el `id`. Lo vemos en la tabla, eliminamos el libro con `id` 3. Recordemos que esta mutación no elimina el autor.

![tabla_delete_book.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666293918354/hcv0soLkX.png align="left")

Ahora eliminamos un autor.

![mutacion_delete_author.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666294250287/H0wN7bpI8.png align="left")

Definimos la mutación para eliminar un autor y pasamos el `id`. Lo vemos en las tablas.

![tabla_delete_author_1.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666294323763/_yMG-fCEL.png align="left")

![tabla_delete_author_2.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666294335964/u94nQKGJ2.png align="left")

Aquí podemos observar que no solo elimina el autor, sino que también elimina los libros que le pertenecen, por lo que la tabla `books` queda vacía.

Excelente, hemos concluido con las pruebas y comprobamos que todas las funcionalidades se ejecutan correctamente.

# Conclusión

Hemos finalizado con el proyecto. En este tutorial hemos repasado de manera práctica los principales conceptos para tener un API Graphql completamente funcional en python. Aprendimos a integrar Flask con Ariadne, y además, vimos como integrar Flask-SQLAlchemy con Flask-Marshmallow para generar tablas relacionales y poder serializar los datos.

Puedes encontrar proyecto completo en mi repositorio de GitHub haciendo clic [AQUÍ](https://github.com/AlegreCode/tutorial-flask-ariadne).

Espero que lo hayas encontrado entretenido, instructivo y claro. Si tienes alguna duda, puedes hacérmelo saber en los comentarios. Pronto estaré subiendo más tutoriales.

Nos vemos en la próxima. Saludos!👋😊
