El autor seleccionó el Fondo de Código Abierto y Gratuito para recibir una donación como parte del programa Escribir para Donaciones.
Introducción
GraphQL es una solución moderna para facilitar la comunicación entre un front end y una fuente de datos. Todos los detalles y capacidades de una implementación de GraphQL están delineados en el Esquema de GraphQL. Para escribir un esquema de GraphQL funcional, debes entender el Sistema de Tipos de GraphQL.
En este artículo, aprenderás sobre los tipos de GraphQL: los cinco tipos escalares integrados, las Enumeraciones, los tipos de envoltura Lista y No-Nulo, los tipos Objeto, y los tipos abstractos Interfaz y Unión que trabajan junto a ellos. Revisarás ejemplos para cada tipo y aprenderás cómo usarlos para construir un esquema completo de GraphQL.
Requisitos previos
Para sacar el máximo provecho de este tutorial, deberías tener:
- Un entendimiento de los conceptos fundamentales de GraphQL, los cuales se presentan en Una Introducción a GraphQL.
- A GraphQL environment, an example of which can be found in How to Set Up a GraphQL API Server in Node.js.
Tipos escalares
Todos los datos en un esquema GraphQL en última instancia se resuelven a varios tipos escalares, los cuales representan valores primitivos. Las respuestas de GraphQL pueden ser representadas como un árbol, y los tipos escalares son las hojas al final del árbol. Puede haber muchos niveles en una respuesta anidada, pero el último nivel siempre se resolverá a un tipo escalar (o Enum). GraphQL viene con cinco tipos escalares incorporados: Int
, Float
, String
, Boolean
y ID
.
Int
Int
es un valor numérico firmado de 32 bits sin fracciones. Es un entero firmado (positivo o negativo) que no incluye decimales. El valor máximo de un entero firmado de 32 bits es 2,147,483,647
. Este es uno de los dos escalares integrados utilizados para datos numéricos.
Float
A Float
is a signed double-precision fractional value. It is a signed (positive or negative) number that contains a decimal point, such as 1.2
. This is the other built-in scalar used for numerical data.
String
A String
is a UTF-8 character sequence. The String
type is used for any textual data. This can also include data like very large numbers. Most custom scalars will be types of string data.
Boolean
A Boolean
is a true
or false
value.
ID
Un ID
es un identificador único. Este valor siempre se serializa como una cadena, incluso si el ID
es numérico. Un tipo ID
podría representarse comúnmente con un Identificador Único Universal (UUID).
Escalares Personalizados
Además de estos escalares integrados, la palabra clave scalar
se puede usar para definir un escalar personalizado. Puedes utilizar escalares personalizados para crear tipos que tengan validación adicional a nivel de servidor, como Date
, Time
, o Url
. Aquí tienes un ejemplo que define un nuevo tipo Date
:
scalar Date
El servidor sabrá cómo manejar las interacciones con este nuevo tipo utilizando GraphQLScalarType
.
Tipo Enumerado
El tipo Enum, también conocido como tipo Enumerador, describe un conjunto de valores posibles.
Usando el tema de la API de Juegos de Fantasía de otros tutoriales en la serie Cómo Gestionar Datos con GraphQL, podrías crear un enum
para los Job
y Species
de los personajes del juego con todos los valores que el sistema aceptará para ellos. Un Enum se define con la palabra clave enum
, así:
"The job class of the character."
enum Job {
FIGHTER
WIZARD
}
"The species or ancestry of the character."
enum Species {
HUMAN
ELF
DWARF
}
De esta manera, se garantiza que el Trabajo
de un personaje sea FIGHTER
o WIZARD
y nunca pueda ser accidentalmente "purple"
u otra cadena aleatoria, lo cual sería posible si se usara un tipo String
en lugar de crear un Enum personalizado. Por convención, los Enums se escriben completamente en mayúsculas.
Los Enums también se pueden usar como valores aceptados en los argumentos. Por ejemplo, podrías crear un enum
llamado Hand
para indicar si un arma es de una mano (como una espada corta) o de dos manos (como un hacha pesada), y usarlo para determinar si se pueden equipar una o dos:
enum Hand {
SINGLE
DOUBLE
}
"A valiant weapon wielded by a fighter."
type Weapon {
name: String!
attack: Int
range: Int
hand: Hand
}
type Query {
weapons(hand: Hand = SINGLE): [Weapon]
}
El enum
Hand
se ha declarado con los valores SINGLE
y DOUBLE
, y el argumento en el campo weapons
tiene un valor predeterminado de SINGLE
, lo que significa que si no se pasa ningún argumento, se utilizará SINGLE
por defecto.
Tipo No Nulo
Puede notar que null
o undefined
, un tipo común que muchas lenguas consideran primitivo, no está en la lista de escalares integrados. Null existe en GraphQL y representa la ausencia de un valor.
Todos los tipos en GraphQL son nulos por defecto y, por lo tanto, `null
` es una respuesta válida para cualquier tipo. Para hacer que un valor sea obligatorio, debe convertirse en un tipo GraphQL No Nulo con un signo de exclamación al final. No Nulo se define como un modificador de tipo, que son tipos utilizados para modificar el tipo al que se refiere. Como ejemplo, `String
` es una cadena opcional (o nullable), y `String!
` es una cadena obligatoria (o No Nula).
Tipo Lista
A List type in GraphQL is another type modifier. Any type that is wrapped in square brackets ([]
) becomes a List type, which is a collection that defines the type of each item in a list.
Como ejemplo, un tipo definido como `[Int]
` será una colección de tipos Int
, y `[String]
` será una colección de tipos String
. No Nulo y Lista se pueden usar juntos para hacer que un tipo sea tanto obligatorio como definido como una Lista, como en `[String]!
`.
Tipo Objeto
Si los tipos escalares de GraphQL describen las “hojas” al final de la respuesta jerárquica de GraphQL, entonces los tipos Objeto describen las “ramas” intermedias, y casi todo en un esquema de GraphQL es un tipo de Objeto.
Los objetos consisten en una lista de campos nombrados (claves) y el tipo de valor al que resolverá cada campo. Los objetos se definen con la palabra clave type
. Se debe definir al menos uno o más campos, y los campos no pueden comenzar con dos guiones bajos (__
) para evitar conflictos con el sistema de introspección de GraphQL.
En el ejemplo de la API de Juego de Fantasía GraphQL, podrías crear un objeto Fighter
para representar un tipo de personaje en un juego:
"A hero with direct combat ability and strength."
type Fighter {
id: ID!
name: String!
level: Int
active: Boolean!
}
En este ejemplo, se ha declarado el tipo de objeto Fighter
, y tiene cuatro campos nombrados:
id
produce un tipoID
no nulo.name
produce un tipoString
no nulo.level
produce un tipoInt
.active
produce un tipoBoolean
no nulo.
Encima de la declaración, también puedes agregar un comentario usando comillas dobles, como en este ejemplo: "Un héroe con capacidad de combate directo y fuerza."
. Esto aparecerá como la descripción para el tipo.
En este ejemplo, cada campo se resuelve a un tipo escalar, pero los campos de objetos también pueden resolverse a otros tipos de objetos. Por ejemplo, podrías crear un tipo Weapon
, y el esquema de GraphQL puede configurarse para que el campo weapon
en el Fighter
se resuelva a un objeto Weapon
:
"A valiant weapon wielded by a fighter."
type Weapon {
name: String!
attack: Int
range: Int
}
"A hero with direct combat ability and strength."
type Fighter {
id: ID!
name: String!
level: Int
active: Boolean!
weapon: Weapon
}
Los objetos también pueden anidarse en los campos de otros objetos.
Tipos de Operaciones Raíz
Existen tres Objetos especiales que sirven como puntos de entrada en un esquema de GraphQL: Query, Mutation y Subscription. Estos son conocidos como tipos de Operaciones Raíz y siguen todas las mismas reglas que cualquier otro tipo de Objeto.
La palabra clave schema
representa el punto de entrada en un esquema de GraphQL. Sus tipos raíz Query, Mutation y Subscription estarán en el Objeto raíz de schema
:
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
El tipo Query es requerido en cualquier esquema de GraphQL y representa una solicitud de lectura, similar a un GET
en una API REST. El siguiente es un ejemplo de un Objeto raíz Query
que devuelve una Lista de tipos Fighter
:
type Query {
fighters: [Fighter]
}
Las Mutaciones representan una solicitud de escritura, que sería análoga a un POST
, PUT
, o DELETE
en una API REST. En el siguiente ejemplo, la Mutation
tiene un campo addFighter
con un argumento con nombre (input
):
type Mutation {
addFighter(input: FighterInput): Fighter
}
Finalmente, una Subscription corresponde a un flujo de eventos, que se usaría en conjunto con un Websocket en una aplicación web. En la API de GraphQL Fantasy, tal vez podría ser usado para encuentros de batallas aleatorias, así:
type Subscription {
randomBattle(enemy: Enemy): BattleResult
}
Nótese que el punto de entrada schema
a menudo se abstrae en algunas implementaciones de GraphQL.
Argumentos de Campo
Los campos de un objeto GraphQL son esencialmente funciones que devuelven un valor, y pueden aceptar argumentos como cualquier función. Los argumentos de campo se definen por el nombre del argumento seguido del tipo. Los argumentos pueden ser de cualquier tipo no objeto. En este ejemplo, el Objeto Fighter
puede filtrarse por el campo id
(que resuelve a un tipo ID
no nulo):
type Query {
fighter(id: ID!): Fighter
}
Este ejemplo en particular es útil para recuperar un solo elemento de la tienda de datos, pero los argumentos también pueden usarse para filtrar, paginar y otras consultas más específicas.
Tipo de Interfaz
Al igual que el tipo de Objeto, el tipo de interfaz abstracta consta de una lista de campos nombrados y sus tipos de valor asociados. Las interfaces se parecen y siguen todas las mismas reglas que los Objetos, pero se usan para definir un subconjunto de la implementación de un Objeto.
Hasta ahora en tu esquema, tienes un objeto Fighter
, pero quizás también quieras crear un Wizard
, un Healer
y otros objetos que compartirán muchos de los mismos campos pero tendrán algunas diferencias. En este caso, puedes usar una interfaz para definir los campos que todos comparten y crear objetos que sean implementaciones de esa interfaz.
En el siguiente ejemplo, podrías crear una interfaz BaseCharacter
utilizando la palabra clave interface
con todos los campos que cada tipo de personaje poseerá:
"A hero on a quest."
interface BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job
}
Cada tipo de personaje tendrá los campos id
, nombre
, nivel
, especie
y trabajo
.
Ahora, imagina que tienes un tipo Fighter
y un tipo Wizard
que comparten estos campos, pero los Fighters
usan un Weapon
y los Wizards
usan Spells
. Puedes usar la palabra clave implements
para indicar que cada uno es una implementación de BaseCharacter
, lo que significa que deben tener todos los campos de la interfaz creada:
"A hero with direct combat ability and strength."
type Fighter implements BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job!
weapon: Weapon
}
"A hero with a variety of magical powers."
type Wizard implements BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job!
spells: [Spell]
}
Fighter
y Wizard
son implementaciones válidas de la interfaz BaseCharacter
porque tienen el subconjunto requerido de campos.
Tipo Unión
Otro tipo abstracto que se puede utilizar con Objetos es el tipo Unión. Mediante la palabra clave union
, puedes definir un tipo con una lista de Objetos que son todos válidos como respuestas.
Utilizando las Interfaces creadas en la sección anterior, puedes crear una Unión de Character
que define un personaje como un Wizard
O un Fighter
:
union Character = Wizard | Fighter
El carácter igual (=
) establece la definición, y el carácter de barra vertical (|
) funciona como la declaración OR
. Ten en cuenta que una Unión debe consistir en Objetos o Interfaces. Los tipos escalares no son válidos en una Unión.
Ahora, si consultas una lista de personajes, podría utilizar la Unión de Character
y devolver todos los tipos de Wizard
y Fighter
.
Conclusión
En este tutorial, aprendiste sobre muchos de los tipos que definen el sistema de tipos de GraphQL. Los tipos más fundamentales son los tipos escalares, que son los valores que actúan como las hojas en el árbol del esquema, y constan de `Int`, `Float`, `String`, `Boolean`, `ID`, y cualquier tipo escalar personalizado que una implementación de GraphQL decida crear. Las enumeraciones son listas de valores constantes válidos que se pueden utilizar cuando se necesita más control sobre una respuesta que simplemente declararla como una cadena (`String`), y también son hojas en el árbol del esquema. Los tipos Lista y No-Nulo son conocidos como modificadores de tipo, o tipos de envoltura, y pueden definir otros tipos como colecciones o requeridos, respectivamente. Los objetos son las ramas del árbol del esquema, y casi todo en un esquema de GraphQL es un tipo de objeto, incluidos los puntos de entrada `query`, `mutation`, y `subscription`. Los tipos de interfaz y unión son tipos abstractos que pueden ser útiles para definir objetos.
Para aprender más, puedes practicar creando y modificando un esquema de GraphQL leyendo el tutorial Cómo Configurar un Servidor de API GraphQL en Node.js para tener un entorno de servidor GraphQL funcional.
Source:
https://www.digitalocean.com/community/conceptual-articles/understanding-the-graphql-type-system