GraphQL
Introducción
GraphQL es un lenguaje de consulta 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.
Uso
// /graphql/index.js
// Dependencia que crea un servidor
const { ApolloServer } = require('@apollo/server')
// Playground incluido en @apollo/server
const { ApolloServerPluginLandingPageLocalDefault } = require('@apollo/server/plugin/landingPage/default')
// Middleware de Express también en @apollo/server
const { expressMiddleware } = require('@apollo/server/express4')
// Definición de tipos
const typeDefs = `
type Query {
hello: String
}
`
// Resolvers
const resolvers = {
Query: {
hello: () => 'Hola mundillo'
}
}
const useGraphQL = async (app) => {
const server = new ApolloServer({
typeDefs,
resolvers,
playground: true,
plugins: [
ApolloServerPluginLandingPageLocalDefault
]
})
await server.start()
// Uso del middleware en Express
app.use(expressMiddleware(server,{
context: async ({req}) => ({
token: req.headers.token
})
}))
}
module.exports = useGraphQL
En el archivo que inicia el server
// index.js
async function startServer() {
try {
// cors
const whitelist = [
...
'http://localhost:3000'
];
const corsOptions = {
origin: (origin, callback) => {
fLog('origin -' + origin);
if (whitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
};
app.use(cors(corsOptions));
...
// GraphQL
await useGraphQL(app);
// API's
routerApi(app);
// middlewares
...
} catch (err) {
console.log(err);
throw new Error('Internal server error', err);
}
}
typeDefs y los resolvers
En GraphQL, los typeDefs
(definiciones de tipos) y los resolvers
(resolutores) son dos conceptos fundamentales que trabajan juntos para definir y ejecutar consultas y mutaciones en una API de GraphQL.
typeDefs
(Definiciones de Tipos)
typeDefs
(Definiciones de Tipos)Los typeDefs
definen la estructura del esquema de GraphQL. Incluyen tipos, consultas, mutaciones y sus respectivos campos. Es esencialmente el "contrato" que especifica qué operaciones están disponibles y qué datos se pueden consultar o modificar.
Resolvers
(Resolutores)
Resolvers
(Resolutores)Los resolvers
son funciones que proporcionan la lógica para obtener los datos definidos en los typeDefs
. Cuando se realiza una consulta o mutación, los resolvers son los que realmente "resuelven" la solicitud, ejecutando la lógica necesaria para devolver los datos o realizar cambios.
Ejemplo
Definiendo el Esquema (typeDefs
)
const typeDefs = gql`
type User {
id: ID!
name: String
email: String
}
type Query {
getUser(id: ID!): User
listUsers: [User]
}
type Mutation {
createUser(name: String!, email: String!): User
updateUser(id: ID!, name: String, email: String): User
deleteUser(id: ID!): Boolean
}
`;
Nota: El type def de tipo Query es equivalente al GET y el Mutation es esquvalente al PUT, POST, DELETE y PATCH. El de tipo User no es propio de GraphQL, son propios, es decir, creados de manera personalizada.
Definiendo los Resolutores (resolvers
)
const users = [];
const resolvers = {
Query: {
getUser: (parent, args) => users.find(user => user.id === args.id),
listUsers: () => users,
},
Mutation: {
createUser: (parent, args) => {
const newUser = {
id: String(users.length + 1),
name: args.name,
email: args.email,
};
users.push(newUser);
return newUser;
},
updateUser: (parent, args) => {
const user = users.find(user => user.id === args.id);
if (user) {
user.name = args.name || user.name;
user.email = args.email || user.email;
return user;
}
return null;
},
deleteUser: (parent, args) => {
const index = users.findIndex(user => user.id === args.id);
if (index !== -1) {
users.splice(index, 1);
return true;
}
return false;
},
},
};
En este ejemplo:
typeDefs
define los tiposUser
, las consultasgetUser
ylistUsers
, y las mutacionescreateUser
,updateUser
ydeleteUser
.resolvers
proporciona la lógica para manejar estas consultas y mutaciones, interactuando con una lista de usuarios en memoria (users
).
Consultas
En GraphQL todas las consultas son mediante POST accediendo al endpoint expuesto y toda petición retorna 201.

Sistema de tipado
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.
Nulabilidad y listas
En GraphQL, no define un nuevo tipo cuando intenta devolver una lista de elementos de un campo; simplemente aplica un modificador de lista a ese tipo, así:
type ObjectType {
singleRestaurant: Restaurant
multipleRestaurants: [Restaurant]
}
Non-null también es un modificador, y resulta que puede aplicar modificadores de listas y no nulos de forma anidada arbitrariamente, especialmente porque las listas se pueden anidar:
type ObjectType {
first: Restaurant
second: [Restaurant]
third: [Restaurant!]
fourth: [Restaurant!]!
fifth: [[Restaurant!]]!
}
Entonces, ¿qué significa tener el no nulo dentro o fuera de una lista? Bueno, decide si el no nulo se aplica al elemento de la lista frente a la lista en sí.
Por ejemplo, puede tener una lista de cadenas no nulas:
drinkSizes: [String!]
Esto significa que la lista en sí puede ser nula, pero no puede tener ningún miembro nulo. Por ejemplo, en JSON:
drinkSizes: nulo // válido
drinkSizes: [] // válido
drinkSizes: ["pequeño", "grande"] // válido
drinkSizes: ["pequeño", nulo, "grande"] // error
Ahora, digamos que definimos una lista de cadenas no nulas:
drinkSizes: [String]!
Esto significa que la lista en sí no puede ser nula, pero puede contener valores nulos:
drinkSizes: nulo // error
drinkSizes: [] // válido
drinkSizes: ["pequeño", "grande"] // válido
drinkSizes: ["pequeño", nulo, "grande"] // válido
Finalmente, podemos combinar los dos:
drinkSizes: [String!]!
Este es el más restrictivo:
drinkSizes: nulo // error
drinkSizes: [] // válido
drinkSizes: ["pequeño", "grande"] // válido
drinkSizes: ["pequeño", nulo, "grande"] // error
Una conclusión interesante aquí es que no hay forma de especificar que la lista no puede estar vacía: una lista vacía [ ] siempre es válida, independientemente de si la lista o los elementos no son nulos.
Object types and fields
Tipos de objetos propios
type Book {
title: String
author: Author
}
type Author {
name: String
books: [Book]
}
Last updated