# Curso de GraphQL
## ¿Qué es GraphQL?
GraphQL es un lenguaje de consultas que te permite definir qué datos pedirle a un API. Se trata de una tecnología que Facebook empezó a desarrollar en 2012, aunque fue anunciada en 2015. Según Facebook, proporciona una descripción completa y comprensible de los datos de su API, ofrece a los clientes la posibilidad de pedir exactamente lo que necesitan, facilita la evolución de las API con el paso del tiempo (escalabilidad) y habilita potentes herramientas para desarrolladores.
Tiene un solo endpoint, no se conecta directamente a la BD.
*Ejemplo*
[Query de Ejemplo](https://graphql.org/swapi-graphql)
```graphql=
{
allFilms {
films {
id
title
created
producers
}
}
allPeople {
people {
name
birthYear
}
}
}
```
## Uso en Node
Para crear un servidor GraphQL con Node debemos instalar las siguientes dependencias:
```shell=
$npm install @apollo/server graphql
```
Además debemos instalar el playground
```shell=
$npm install @apollo/server-plugin-landing-page-graphql-playground
```
*Ejemplo (graphql.js)*
```javascript=
const { ApolloServer } = require('@apollo/server');
const { ApolloServerPluginLandingPageGraphQLPlayground } = require('@apollo/server-plugin-landing-page-graphql-playground');
const { expressMiddleware } = require('@apollo/server/express4');
const typeDefs = `
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => 'hola mundo',
}
}
const useGraphql = async (app) => {
const server = new ApolloServer({
typeDefs,
resolvers,
playground: true,
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground
]
});
await server.start();
app.use(expressMiddleware(server, {
context: async ({ req }) => ({ token: req.headers.token }),
}));
}
module.exports = useGraphq
```
*Ejemplo (app.js)*
```javascript=
const express = require('express');
const useGraphql = require('./graphql');
const createApp = async () => {
const app = express();
app.use(express.json());
app.get('/', (req, res) => {
res.send('Hola mi server en express');
});
app.get('/nueva-ruta', (req, res) => {
res.send('Hola, soy una nueva ruta');
});
await useGraphql(app);
return app;
}
module.exports = createApp;
```
*Ejemplo (index.js)*
```javascript=
const createApp = require('./app');
(async () => {
const port = process.env.PORT || 3000;
const app = await createApp();
app.listen(port, () => {
console.log(`Mi port ${port}`);
});
})();
```
Para ver el playground de graphql iremos a localhost:3000/graphql
## Sistema de Tipado

GraphQL nos permite definir un tipado, un contrato que debe cumplir el cliente a la hora de hacer peticiones, como el cualquier otro lenguaje que utilice tipos.
A diferencia de la arquitectura REST, **en GraphQL toda petición se manda por POST**, y retorna un código http 201, aunque haya sucedido un error.
En GraphQL vienen dos tipos por defecto, que envuelven las diferentes acciones:
- **Query**: Todas aquellas funciones que soliciten datos
- **Mutation**: Son todas aquellas funciones que modifican o mutan los datos
- **Subscriptions**: Para soportar real-time
Si buscamos un simil con REST, a la hora de realizar las diferentes acciones, GraphQL las maneja de la siguiente forma:
- **GET: Query**
- **POST, PUT, PATCH y DELETE: Mutations**
La definición de tipos se definen dentro de la propiedad typeDefs:
```javascript=
const typeDefs = `
type Query {
hello: String
}
`;
```
Las funciones que se aplican dentro de GraphQL se definen en la propiedad resolvers:
```javascript=
const resolvers = {
Query: {
hello: () => 'hola mundo',
}
}
```
Podemos pasar argumentos dentro de dichos resolvers, como parámetros de funciones. Debemos definir sus tipos correspondientes y en el resolver los parámetros será pasados dentro de la propiedad args:
```javascript=
const typeDefs = `
type Query {
hello: String,
getPerson(name: String, age: Int): String
}
`;
const resolvers = {
Query: {
hello: () => "hola mundo",
getPerson: (_, args) => `Hola, mi nombre es ${args.name} y tengo ${args.age} años!`
},
};
```
Cabe resaltar que por defecto los parametros de la query son opcionales. Si queremos que sean obligatorios y no devuelvan un nulo sus types deben tener un signo !:
```javascript=
getPerson(name: String!, age: Int!): String!
```
### Tipos Escalares
GraphQL viene con un conjunto de tipos escalares predeterminados listos para usar:
- **Int**: Un entero de 32 bits con signo.
- **Float**: Un valor de punto flotante de precisión doble con signo.
- **String**: Una secuencia de caracteres UTF‐8.
- **Boolean**: true o false.
- **ID**: El tipo escalar de ID representa un identificador único, que a menudo se usa para recuperar un objeto o como clave para un caché. El tipo de ID se serializa de la misma manera que una Cadena; sin embargo, definirlo como ID significa que no está destinado a ser legible por humanos.
*Ejemplo*
```javascript=
const typeDefs = `
type Query {
hello: String,
getPerson(name: String, age: Int): String,
getInt: Int,
getFloat: Float,
getString: String,
getBoolean: Boolean,
getID: ID
}
`;
const resolvers = {
Query: {
hello: () => "hola mundo",
getPerson: (_, args) => `Hola, mi nombre es ${args.name} y tengo ${args.age} años!`,
getInt: () => 3000,
getFloat: () => 3000.2,
getString: () => "3000",
getBoolean: () => true,
getID: () => "121212"
},
};
```
### Listas y non-null types
Para definir listas de un tipo concreto, solo debemos envolver el tipo entre "[" "]".
*Ejemplo*
```javascript=
// [String]
const typeDefs = `
type Query {
getNumbers(numbers: [Int]!): [Int]
}
`;
const resolvers = {
Query: {
getNumbers: (_, args) => args.numbers
},
};
```
### Object types and fields
De está forma vamos a crear nuestros propios tipos.
Para ello vamos a crear un nuevo tipo dentro de la propiedad typeDefs, al igual que teníamos el tipo por defecto Query, podemos crear los nuestros de la misma forma:
```javascript=
const typeDefs = `
type Query {
getProduct: Product
}
type Product {
id: ID!
name: String!
price: Float!
description: String!
image: String!
createdAt: String!
}
`;
```
En el resolver añadimos la función y debemos devolver un objeto, en formato JSON.
```javascript=
const resolvers = {
Query: {
getProduct: () => {
return {
id: "12345",
name: "Producto 1",
price: 3000.2,
description: "bla bla bla",
image: "http://image.png",
createdAt: new Date().toISOString()
}
}
},
};
```
A la hora de hacer la query debemos seleccionar entre llaves los campos que queremos obtener

## Archivos GraphQL
Podemos abstraer la definición de tipos a archivos graphql, para ello necesitamos instalar la siguiente dependencia:
```shell=
$npm i @graphql-tools/load-files
```
A continuación creamos un fichero con la extensión .graphql y añadimos la definición de tipos.
```graphql=
type Query {
hello: String!
getPerson(name: String, age: Int): String
getInt: Int
getFloat: Float
getString: String
getBoolean: Boolean
getID: ID
getNumbers(numbers: [Int!]!): [Int]
getProduct: Product
}
type Product {
id: ID!
name: String!
price: Float!
description: String!
image: String!
createdAt: String!
categoryId: Int!
}
```
Por último modificamos el fichero graqhql.js y añadimos la carga del schema en la creación del servidor graphql, importando primero la función loadFiles.
```javascript=
const { loadFiles } = require("@graphql-tools/load-files")
const useGraphql = async (app) => {
const server = new ApolloServer({
typeDefs: await loadFiles("./api/**/*.graphql"),
resolvers,
playground: true,
plugins: [ApolloServerPluginLandingPageGraphQLPlayground],
});
await server.start();
app.use(
expressMiddleware(server, {
context: async ({ req }) => ({ token: req.headers.token }),
})
);
};
```
IMPORTANTE: La ruta que pasemos en la función loadFiles debe ser absoluta.
## Product resolvers
Para separar responsabilidades en diferentes ficheros.
*product.resolvers.js*
```javascript=
const getProduct = (_, { id }) => {
return {
id,
name: "Producto 1",
price: 3000.2,
description: "bla bla bla",
image: "http://image.png",
createdAt: new Date().toISOString(),
categoryId: 1
}
}
const getProducts = () => {
return []
}
const addProduct = () => {
// codigo
}
module.exports = { getProduct, getProducts }
```
*resolvers.js*
```javascript=
const { getProduct, getProducts } = require("./product.resolvers")
const resolvers = {
Query: {
hello: () => "hola mundo",
getPerson: (_, args) => `Hola, mi nombre es ${args.name} y tengo ${args.age} años!`,
getInt: () => 3000,
getFloat: () => 3000.2,
getString: () => "3000",
getBoolean: () => true,
getID: () => "121212",
getNumbers: (_, args) => args.numbers,
// Products
product: getProduct,
allProducts: getProducts
},
};
module.exports = resolvers
```
*index.js*
```javascript=
const { ApolloServer } = require('@apollo/server');
const { ApolloServerPluginLandingPageGraphQLPlayground } = require('@apollo/server-plugin-landing-page-graphql-playground');
const { expressMiddleware } = require('@apollo/server/express4');
const resolvers = require("./resolvers");
...
```
*schema.graphql*
```graphql=
type Query {
...
# Products
product(id: ID!): Product
allProducts: [Product!]!
}
...
```
## Mutations e Inputs
Son cosas que cambian el estado o la información (Crear, Eliminar y Actualizar).
Para agrupar varios argumentos que necesitamos en una mutation debemos usar input.
```graphql
type Mutation {
addProduct(input: CreateProductInput!): Product
}
input CreateProductInput {
name: String!
price: Float!
description: String!
image: String!
}
```
Añadimos un nuevo método a los Resolver, dentro de la propiedad Mutation.
```javascript=
import { getProduct, getProducts, addProduct } from "./product.resolvers.js"
const resolvers = {
Query: {
// Products
product: getProduct,
allProducts: getProducts
},
Mutation: {
addProduct
}
};
```
*Ejemplo*
```graphql=
mutation {
addProduct(input: {
name: "Producto 1",
description: "Esto es Producto 1",
price: 1500.1,
image: "http://image.com/producto1.png",
categoryId: 1
}){
id,
name,
description
}
}
```
## Variables y Alias
La persona que haga la cosulta puede decidir como recibirla.
```graphql=
# Alias
{
p1: product(id: "1"){
id
name
}
p2: product(id: "2"){
id
name
}
}
```
Podemos crear variables dentro de GraphQL, por ejemplo, para pasarle a los mutaciones los datos de una manera más cómoda.
Podemos añadirle un nombre a las queries y mutaciones, para identificarlas.
En el playground de GraphQL, o en softwares como Postman podemos añadir esas variables.
*Query Variable*
```jsonld=
{
"input": {
"name": "Producto 1",
"description": "Esto es Producto 1",
"price": 1500.1,
"image": "http://image.com/producto1.png",
"categoryId": 1
}
}
```
*Ejemplo de Mutation con variables*
```graphql=
mutation createProduct($input: CreateProduct) {
addProduct(input: $input){
id,
name,
description
}
}
```
## Prisma (ORM)
Para interactuar con la BD haremos uso de Prisma un ORM.
Un ORM (Object-Relational-Mapping) es una herramienta que nos permite mapear los modelos de datos, definidos en un esquema SQL, mediante código definido en servidor.
https://www.prisma.io/express
Para instalar prisma usaremos:
```bash=
$npm install prisma --save-dev
```
Inicializamos prisma:
```bash=
$npx prisma init --datasource-provider sqlite
```
Iniciamos la migración:
```bash=
$npx prisma migrate dev --name init
```
## Autenticación
Podemos implementar autenticación con la librería **graphql-passport**, está nos permite utilizar las estrategias de passport (Arquitectura REST) dentro de GraphQL.
Primeramente creamos los tipos correspondientes en nuestro esquema:
*schema.js*
```graphql=
type AuthResponse{
access_token: String!
user:User!
}
type User{
id:ID!
email:String!
role:String!
createdAt:String!
}
type Mutation {
# login auth
login(email:String! password:String!):AuthResponse
}
```
Instalamos las dependencias:
```bash=
$npm i passport graphql-passport
```
Crearemos una estrategia local:
*gql-local.strategy.js*
```javascript=
import { GraphQLLocalStrategy } from 'graphql-passport'
const GQLLocalStrategy = new GraphQLLocalStrategy(async(email,password,done)=>{
try {
const user = ...
done(null,user)
} catch (error) {
done(error, false)
}
})
export default GQLLocalStrategy
```
Añadimos esa estrategia a la configuración de passport.
```javascript=
import passport from 'passport'
import GQLLocalStrategy from './strategies/local-gql.strategy'
passport.use(GQLLocalStrategy)
```
Añadimos un contexto para poder usar dicha estrategia en los resolvers.
*graphql.js*
```javascript=
import { loadFiles } from "@graphql-tools/load-files"
import { buildContext } from "graphql-passport"
const useGraphql = async (app) => {
const server = new ApolloServer({
typeDefs: await loadFiles("./api/**/*.graphql"),
resolvers,
context:({req,res})=> buildContext({req,res}),
playground: true,
plugins: [ApolloServerPluginLandingPageGraphQLPlayground],
});
await server.start();
app.use(
expressMiddleware(server, {
context: async ({ req }) => ({ token: req.headers.token }),
})
);
};
```
Por último en el resolver aplicamos la autenticación.
*auth.resolver.js*
```javascript=
const login = async(root, {email,password},context)=>{
// logeo del usuario
...
}
```
[GraphQL Shield](https://the-guild.dev/graphql/shield/docs)
## Custom Scalars
Podemos añadir una capa extra de validación de tipos con la librería GraphQL Scalars. Está librería nos ofrece multitud de Scalars para validar tipos especifícos como pueden ser un email, una url o un color hexadecimal.
Para poder hacer uso de ella debemos instalar la dependencia.
```bash=
$npm i graphql-scalars
```
Lo siguiente que debemos hacer es configurar nuestro servidor de GraphQL para que reciba nuestros tipos y resolvers, junto a los que proporciona GraphQL Scalars.
```javascript!
import { ApolloServer } from 'apollo-server-express'
import { ApolloServerPluginLandingPageLocalDefault } from 'apollo-server-core'
import { loadFiles } from '@graphql-tools/load-files'
import { typeDefs: typeDefsScalars,resolvers:resolversScalars} from 'graphql-scalars'
import { buildContext } from 'graphql-passport'
import resolversApp from './resolvers'
// el ! significa un campo obligatorio
const useGraphql= async (app)=>{
const resolvers = [ resolversApp , resolversScalars];
const typeDefs = [ ...await loadFiles('./src/**/*.graphql')/*typesDefs Locals*/ ,typeDefsScalars]
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginLandingPageLocalDefault]
});
await server.start();
server.applyMiddleware({app})
}
export default useGraphql
```
De está forma ya podemos usar dichos Scalars en nuestros schemas.
### RegularExpression
Existe una clase muy potente dentro de GraphQL Scalar, que es RegularExpression. Este clase nos permite crear nuevos Scalars basados en expresiones regulares.
```javascript
import { RegularExpression } from 'graphql-scalars'
// creamos el type con una nueva instancia de RegularExpression
const CategoryNameType= new RegularExpression('CategoryNameType', /^[a-zA-Z0-9]{3,8}$/);
const resolvers= {
Query:{
saludo: ()=> 'hola mundillo de internet',
...
},
Mutation:{
...
},
CategoryNameType // add new type
}
export default resolvers
```
Lo último que nos quedaría por hacer es declarar dicho Scalar dentro del Schema, una vez hecho esto podremos usarlo sin problema.
```graphql!
scalar CategoryNameType
input CreateCategory {
name: CategoryNameType!
image: URL!
}
```
## Anidamiento Dinámico
En casos en los que varias entidades están relacionadas y queramos devolver en una query datos de otra entidad, podríamos ejecutar una consulta en la base de datos que nos haga el join oportuno para así traer los datos relacionados.
Sin embargo GraphQL responderá siempre los datos que nosotros le solicitemos, y puede llegar el caso en que nosotros en dicha query no pidamos los datos relacionados, por lo que a nivel de base de datos estaremos ejecutando un join innecesario.
Para esto existe el anidamiento dinámico, que nos permitirá convertir esa propiedad que definimos en nuestro schema como un resolver más.
Veámos un ejemplo en el que sacaremos los datos de una categoría a la que pertenece un producto.
Lo que debemos hacer es crear un nuevo resolver que haga dicha consulta, los datos que necesitaremos los sacaremos esta vez del primer parámetro que recibe el resolver. Este parámetro tiene los datos de nuestro objeto raíz.
*category.resolver.js*
```javascript!
...
const getCategoryInProduct = async ({ categoryId: id }) => {
const category = await prisma.category.findUnique({
where: {
id: parseInt(id)
}
})
return category
};
...
```
Una vez tenemos el resolver, sólo debemos declarar que el tipo Product obtendrá las categorías de este resolver. Para ello modificamos el fichero en el que se encuentran todos los resolvers.
*resolvers.js*
```javascript!
import {
getCategoryInProduct,
} from "./category.resolver.js";
const resolvers = {
Query: {
...
},
Mutation: {
...
},
Product: {
category: getCategoryInProduct
}
};
export { resolvers };
```
De está forma cada vez que necesitemos los datos de una categoría se llamará al nuevo resolver, evitándo así realizar el join dentro de la Base de Datos, y mejorando el performance del servicio.
## GraphQL VS REST
A grandes rasgos GraphQL es una mejora bastante grande con respecto a REST, por lo que claramente está última no tiene nada que hacer contra GraphQL. Analizemos las ventajas que tiene GraphQL con respecto a REST:
1. **El control de los datos lo tiene el cliente**: El cliente es el que decide que datos necesita, mientras que en REST se mandan los datos definidos por el servidor, sean o no necesarios.
2. **Nº de Solicitudes**: En GraphQL solo realizamos una petición, mientras en REST para obtener datos que dependen de otros tenemos que realizar n peticiones.
3. **Nº de EndPoints**: En GraphQL solo tenemos un endpoint (Generalmente /graphql), mientras en REST tenemos más endpoint, uno por cada consulta de datos que necesitemos realizar, por lo que es mucho menos flexible y más difícil de mantener.
4. **Dependencia de Equipos**: Gracias a las ventajas y flexibilidad que tiene GraphQL la dependencia de equipos de desarrollo con respecto al Backend será mayor, ya que ningún equipo dependerá del backend para un endpoint específico que necesiten. El único obstáculo será la curva de aprendiza del lenguaje de consultas que nos provee GraphQL, pero una vez superada dispondremos de una agilidad de desarrollo mucho mayor.
###### tags: `DAW` `DSW` `Platzi`