Creación de su primera API GraphQL
Publicado: 2022-03-11Prefacio
Hace unos años, Facebook introdujo una nueva forma de crear API de back-end llamada GraphQL, que básicamente es un lenguaje específico de dominio para la consulta y manipulación de datos. Al principio, no le presté mucha atención, pero finalmente me encontré comprometido con un proyecto en Toptal, donde tenía que implementar API de back-end basadas en GraphQL. Fue entonces cuando seguí adelante y aprendí cómo aplicar el conocimiento que aprendí para REST a GraphQL.
Fue una experiencia muy interesante y, durante el período de implementación, tuve que repensar los enfoques y metodologías estándar utilizados en las API REST de una manera más amigable con GraphQL. En este artículo, trato de resumir los problemas comunes que se deben tener en cuenta al implementar las API de GraphQL por primera vez.
Bibliotecas requeridas
GraphQL fue desarrollado internamente por Facebook y publicado públicamente en 2015. Posteriormente, en 2018, el proyecto GraphQL se trasladó de Facebook a la Fundación GraphQL recientemente establecida, alojada por la Fundación Linux sin fines de lucro, que mantiene y desarrolla la especificación del lenguaje de consulta GraphQL y una referencia. implementación para JavaScript.
Dado que GraphQL aún es una tecnología joven y la implementación de referencia inicial estaba disponible para JavaScript, la mayoría de las bibliotecas maduras existen en el ecosistema Node.js. También hay otras dos empresas, Apollo y Prisma, que proporcionan herramientas y bibliotecas de código abierto para GraphQL. El proyecto de ejemplo de este artículo se basará en una implementación de referencia de GraphQL para JavaScript y bibliotecas proporcionadas por estas dos empresas:
- Graphql-js: una implementación de referencia de GraphQL para JavaScript
- Servidor Apollo: servidor GraphQL para Express, Connect, Hapi, Koa y más
- Apollo-graphql-tools: crea, simula y une un esquema de GraphQL con SDL
- Prisma-graphql-middleware: divida sus resolutores GraphQL en funciones de middleware
En el mundo GraphQL, usted describe sus API usando esquemas GraphQL, y para estos, la especificación define su propio lenguaje llamado The GraphQL Schema Definition Language (SDL). SDL es muy simple e intuitivo de usar y, al mismo tiempo, extremadamente potente y expresivo.
Existen dos formas de crear esquemas de GraphQL: el enfoque de código primero y el enfoque de esquema primero.
- En el enfoque de código primero, describe sus esquemas de GraphQL como objetos de JavaScript basados en la biblioteca graphql-js, y el SDL se genera automáticamente a partir del código fuente.
- En el enfoque de esquema primero, usted describe sus esquemas de GraphQL en SDL y conecta su lógica de negocios usando la biblioteca de herramientas Graphql de Apollo.
Personalmente, prefiero el enfoque de esquema primero y lo usaré para el proyecto de muestra en este artículo. Implementaremos un ejemplo de librería clásica y crearemos un back-end que proporcionará API CRUD para crear autores y libros, además de API para la administración y autenticación de usuarios.
Creación de un servidor GraphQL básico
Para ejecutar un servidor GraphQL básico, debemos crear un nuevo proyecto, inicializarlo con npm y configurar Babel. Para configurar Babel, primero instale las bibliotecas requeridas con el siguiente comando:
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
Después de instalar Babel, crea un archivo con el nombre .babelrc
en el directorio raíz de nuestro proyecto y copia allí la siguiente configuración:
{ "presets": [ [ "@babel/env", { "targets": { "node": "current" } } ] ] }
Edite también el archivo package.json
y agregue el siguiente comando a la sección de scripts
:
{ ... "scripts": { "serve": "babel-node index.js" }, ... }
Una vez que hayamos configurado Babel, instale las bibliotecas GraphQL requeridas con el siguiente comando:
npm install --save express apollo-server-express graphql graphql-tools graphql-tag
Después de instalar las bibliotecas requeridas, para ejecutar un servidor GraphQL con una configuración mínima, copie este fragmento de código en nuestro archivo index.js
:
import gql from 'graphql-tag'; import express from 'express'; import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'; const port = process.env.PORT || 8080; // Define APIs using GraphQL SDL const typeDefs = gql` type Query { sayHello(name: String!): String! } type Mutation { sayHello(name: String!): String! } `; // Define resolvers map for API definitions in SDL const resolvers = { Query: { sayHello: (obj, args, context, info) => { return `Hello ${ args.name }!`; } }, Mutation: { sayHello: (obj, args, context, info) => { return `Hello ${ args.name }!`; } } }; // Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolvers maps const schema = makeExecutableSchema({ typeDefs, resolvers }); // Build Apollo server const apolloServer = new ApolloServer({ schema }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); });
Después de esto, podemos ejecutar nuestro servidor usando el comando npm run serve
, y si navegamos en un navegador web a la URL http://localhost:8080/graphql
, se abrirá el shell visual interactivo de GraphQL, llamado Playground, donde podemos ejecute consultas y mutaciones de GraphQL y vea los datos de resultados.
En el mundo de GraphQL, las funciones de la API se dividen en tres conjuntos, llamados consultas, mutaciones y suscripciones:
- El cliente utiliza las consultas para solicitar los datos que necesita del servidor.
- El cliente utiliza las mutaciones para crear/actualizar/eliminar datos en el servidor.
- El cliente utiliza las suscripciones para crear y mantener una conexión en tiempo real con el servidor. Esto permite al cliente obtener eventos del servidor y actuar en consecuencia.
En nuestro artículo, discutiremos solo consultas y mutaciones. Las suscripciones son un tema muy importante: merecen su propio artículo y no son necesarias en todas las implementaciones de API.
Tipos de datos escalares avanzados
Poco después de jugar con GraphQL, descubrirá que SDL solo proporciona tipos de datos primitivos y que faltan tipos de datos escalares avanzados como Fecha, Hora y Fecha y hora, que son una parte importante de cada API. Afortunadamente, tenemos una biblioteca que nos ayuda a resolver este problema y se llama graphql-iso-date. Después de instalarlo, necesitaremos definir nuevos tipos de datos escalares avanzados en nuestro esquema y conectarlos a las implementaciones proporcionadas por la biblioteca:
import { GraphQLDate, GraphQLDateTime, GraphQLTime } from 'graphql-iso-date'; // Define APIs using GraphQL SDL const typeDefs = gql` scalar Date scalar Time scalar DateTime type Query { sayHello(name: String!): String! } type Mutation { sayHello(name: String!): String! } `; // Define resolvers map for API definitions in SDL const resolvers = { Date: GraphQLDate, Time: GraphQLTime, DateTime: GraphQLDateTime, Query: { sayHello: (obj, args, context, info) => { return `Hello ${ args.name }!`; } }, Mutation: { sayHello: (obj, args, context, info) => { return `Hello ${ args.name }!`; } } };
Junto con la fecha y la hora, también existen otras implementaciones interesantes de tipos de datos escalares, que pueden serle útiles según su caso de uso. Por ejemplo, uno de ellos es graphql-type-json, que nos brinda la capacidad de usar escritura dinámica en nuestro esquema GraphQL y pasar o devolver objetos JSON sin tipo usando nuestra API. También existe la biblioteca graphql-scalar, que nos brinda la capacidad de definir escalares GraphQL personalizados con sanitización/validación/transformación avanzada.
Si es necesario, también puede definir su tipo de datos escalares personalizados y usarlos en su esquema, como se muestra arriba. Esto no es difícil, pero su discusión está fuera del alcance de este artículo; si está interesado, puede encontrar información más avanzada en la documentación de Apollo.
Esquema de división
Después de agregar más funciones a su esquema, comenzará a crecer y entenderemos que es imposible mantener todo el conjunto de definiciones en un archivo, y necesitamos dividirlo en partes pequeñas para organizar el código y hacerlo más escalable para un tamaño más grande Afortunadamente, la función de creación de esquemas makeExecutableSchema
, proporcionada por Apollo, también acepta definiciones de esquemas y mapas de resolución en forma de matriz. Esto nos da la capacidad de dividir nuestro esquema y mapa de resolución en partes más pequeñas. Esto es exactamente lo que hice en mi proyecto de muestra; He dividido la API en las siguientes partes:
-
auth.api.graphql
: API para la autenticación y el registro de usuarios -
author.api.graphql
– API CRUD para entradas de autor -
book.api.graphql
– API CRUD para entradas de libros -
root.api.graphql
: raíz del esquema y definiciones comunes (como tipos escalares avanzados) -
user.api.graphql
: API CRUD para la gestión de usuarios
Durante el esquema de división, hay una cosa que debemos considerar. Una de las partes debe ser el esquema raíz y las otras deben extender el esquema raíz. Esto suena complejo, pero en realidad es bastante simple. En el esquema raíz, las consultas y las mutaciones se definen así:
type Query { ... } type Mutation { ... }
Y en las demás, se definen así:
extend type Query { ... } extend type Mutation { ... }
Y eso es todo.
Autenticacion y autorizacion
En la mayoría de las implementaciones de API, existe el requisito de restringir el acceso global y proporcionar algún tipo de política de acceso basada en reglas. Para ello tenemos que introducir en nuestro código: Autenticación —para confirmar la identidad del usuario— y Autorización , para hacer cumplir las políticas de acceso basadas en reglas.
En el mundo GraphQL, como el mundo REST, generalmente para la autenticación usamos JSON Web Token. Para validar el token JWT pasado, debemos interceptar todas las solicitudes entrantes y verificar el encabezado de autorización en ellas. Para esto, durante la creación del servidor Apollo, podemos registrar una función como enlace de contexto, que se llamará con la solicitud actual que crea el contexto compartido entre todos los resolutores. Esto se puede hacer así:
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); // Build Apollo server const apolloServer = new ApolloServer({ schema, context: ({ req, res }) => { const context = {}; // Verify jwt token const parts = req.headers.authorization ? req.headers.authorization.split(' ') : ['']; const token = parts.length === 2 && parts[0].toLowerCase() === 'bearer' ? parts[1] : undefined; context.authUser = token ? verify(token) : undefined; return context; } }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); });
Aquí, si el usuario pasa un token JWT correcto, lo verificamos y almacenamos el objeto de usuario en contexto, al que podrán acceder todos los resolutores durante la ejecución de la solicitud.
Verificamos la identidad del usuario, pero nuestra API sigue siendo accesible globalmente y nada impide que nuestros usuarios la llamen sin autorización. Una forma de evitar esto es verificar el objeto de usuario en contexto directamente en cada resolución, pero este es un enfoque muy propenso a errores porque tenemos que escribir una gran cantidad de código repetitivo y podemos olvidar agregar la verificación al agregar una nueva resolución. . Si echamos un vistazo a los marcos de API REST, generalmente este tipo de problemas se resuelven utilizando interceptores de solicitudes HTTP, pero en el caso de GraphQL, no tiene sentido porque una solicitud HTTP puede contener múltiples consultas GraphQL, y si aún agregamos solo tenemos acceso a la representación de cadena sin procesar de la consulta y tenemos que analizarla manualmente, lo que definitivamente no es un buen enfoque. Este concepto no se traduce bien de REST a GraphQL.
Entonces, necesitamos algún tipo de forma de interceptar las consultas de GraphQL, y esta forma se llama prisma-graphql-middleware. Esta biblioteca nos permite ejecutar código arbitrario antes o después de que se invoque un resolutor. Mejora nuestra estructura de código al permitir la reutilización de código y una clara separación de preocupaciones.
La comunidad de GraphQL ya ha creado un montón de middleware increíble basado en la biblioteca de middleware de Prisma, que resuelve algunos casos de uso específicos, y para la autorización del usuario, existe una biblioteca llamada graphql-shield, que nos ayuda a crear una capa de permisos para nuestra API.
Después de instalar graphql-shield, podemos introducir una capa de permisos para nuestra API como esta:
import { allow } from 'graphql-shield'; const isAuthorized = rule()( (obj, args, { authUser }, info) => authUser && true ); export const permissions = { Query: { '*': isAuthorized, sayHello: allow }, Mutation: { '*': isAuthorized, sayHello: allow } }
Y podemos aplicar esta capa como middleware a nuestro esquema de esta manera:
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); const schemaWithMiddleware = applyMiddleware(schema, shield(permissions, { allowExternalErrors: true })); // Build Apollo server const apolloServer = new ApolloServer({ schemaWithMiddleware }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); })
Aquí, al crear un objeto de escudo, establecemos allowExternalErrors
en verdadero porque, de forma predeterminada, el comportamiento del escudo es detectar y manejar los errores que ocurren dentro de los resolutores, y este no era un comportamiento aceptable para mi aplicación de muestra.

En el ejemplo anterior, solo restringimos el acceso a nuestra API para usuarios autenticados, pero el escudo es muy flexible y, al usarlo, podemos implementar un esquema de autorización muy completo para nuestros usuarios. Por ejemplo, en nuestra aplicación de muestra, tenemos dos roles: USER
y USER_MANAGER
, y solo los usuarios con el rol USER_MANAGER
pueden llamar a la funcionalidad de administración de usuarios. Esto se implementa así:
export const isUserManager = rule()( (obj, args, { authUser }, info) => authUser && authUser.role === 'USER_MANAGER' ); export const permissions = { Query: { userById: isUserManager, users: isUserManager }, Mutation: { editUser: isUserManager, deleteUser: isUserManager } }
Una cosa más que quiero mencionar es cómo organizar las funciones de middleware en nuestro proyecto. Al igual que con las definiciones de esquema y los mapas de resolución, es mejor dividirlos por esquema y mantenerlos en archivos separados, pero a diferencia del servidor Apollo, que acepta conjuntos de definiciones de esquema y mapas de resolución y los une para nosotros, la biblioteca de middleware de Prisma no hace esto y acepta solo un objeto de mapa de middleware, por lo que si los dividimos, tenemos que volver a unirlos manualmente. Para ver mi solución a este problema, consulte la clase ApiExplorer
en el proyecto de ejemplo.
Validación
GraphQL SDL proporciona una funcionalidad muy limitada para validar la entrada del usuario; solo podemos definir qué campo es obligatorio y cuál es opcional. Cualquier requisito de validación adicional, debemos implementarlo manualmente. Podemos aplicar reglas de validación directamente en las funciones de resolución, pero esta funcionalidad realmente no pertenece aquí, y este es otro gran caso de uso para el usuario de middleware GraphQL. Por ejemplo, usemos los datos de entrada de la solicitud de registro de usuario, donde tenemos que validar si el nombre de usuario es una dirección de correo electrónico correcta, si las entradas de contraseña coinciden y si la contraseña es lo suficientemente segura. Esto se puede implementar así:
import { UserInputError } from 'apollo-server-express'; import passwordValidator from 'password-validator'; import { isEmail } from 'validator'; const passwordSchema = new passwordValidator() .is().min(8) .is().max(20) .has().letters() .has().digits() .has().symbols() .has().not().spaces(); export const validators = { Mutation: { signup: (resolve, parent, args, context) => { const { email, password, rePassword } = args.signupReq; if (!isEmail(email)) { throw new UserInputError('Invalid Email address!'); } if (password !== rePassword) { throw new UserInputError('Passwords don\'t match!'); } if (!passwordSchema.validate(password)) { throw new UserInputError('Password is not strong enough!'); } return resolve(parent, args, context); } } }
Y podemos aplicar la capa de validadores como middleware a nuestro esquema, junto con una capa de permisos como esta:
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); const schemaWithMiddleware = applyMiddleware(schema, validators, shield(permissions, { allowExternalErrors: true })); // Build Apollo server const apolloServer = new ApolloServer({ schemaWithMiddleware }); apolloServer.applyMiddleware({ app })
N + 1 Consultas
Otro problema a considerar, que ocurre con las API de GraphQL y que a menudo se pasa por alto, son las consultas N + 1. Este problema ocurre cuando tenemos una relación de uno a muchos entre los tipos definidos en nuestro esquema. Para demostrarlo, por ejemplo, usemos la API de libros de nuestro proyecto de muestra:
extend type Query { books: [Book!]! ... } extend type Mutation { ... } type Book { id: ID! creator: User! createdAt: DateTime! updatedAt: DateTime! authors: [Author!]! title: String! about: String language: String genre: String isbn13: String isbn10: String publisher: String publishDate: Date hardcover: Int } type User { id: ID! createdAt: DateTime! updatedAt: DateTime! fullName: String! email: String! }
Aquí, vemos que el tipo User
tiene una relación de uno a varios con el tipo de Book
, y esta relación se representa como un campo de creador en el Book
. El mapa de resolución para este esquema se define así:
export const resolvers = { Query: { books: (obj, args, context, info) => { return bookService.findAll(); }, ... }, Mutation: { ... }, Book: { creator: ({ creatorId }, args, context, info) => { return userService.findById(creatorId); }, ... } }
Si ejecutamos una consulta de libros usando esta API y miramos el registro de sentencias SQL, veremos algo como esto:
select `books`.* from `books` select `users`.* from `users` where `users`.`id` = ? select `users`.* from `users` where `users`.`id` = ? select `users`.* from `users` where `users`.`id` = ? select `users`.* from `users` where `users`.`id` = ? select `users`.* from `users` where `users`.`id` = ? ...
Es fácil de adivinar: durante la ejecución, primero se llamó al solucionador para la consulta de libros, que devolvió una lista de libros y luego a cada objeto de libro se le llamó resolver del campo del creador, y este comportamiento provocó N + 1 consultas a la base de datos. Si no queremos explotar nuestra base de datos, ese tipo de comportamiento no es realmente bueno.
Para resolver el problema de las consultas N + 1, los desarrolladores de Facebook crearon una solución muy interesante llamada DataLoader, que se describe en su página LÉAME así:
“DataLoader es una utilidad genérica que se utiliza como parte de la capa de obtención de datos de su aplicación para proporcionar una API simplificada y coherente sobre varias fuentes de datos remotas, como bases de datos o servicios web, mediante procesamiento por lotes y almacenamiento en caché”
No es muy sencillo entender cómo funciona DataLoader, así que primero veamos el ejemplo que resuelve el problema demostrado anteriormente y luego expliquemos la lógica detrás de él.
En nuestro proyecto de muestra, DataLoader se define así para el campo del creador:
export class UserDataLoader extends DataLoader { constructor() { const batchLoader = userIds => { return userService .findByIds(userIds) .then( users => userIds.map( userId => users.filter(user => user.id === userId)[0] ) ); }; super(batchLoader); } static getInstance(context) { if (!context.userDataLoader) { context.userDataLoader = new UserDataLoader(); } return context.userDataLoader; } }
Una vez que hayamos definido UserDataLoader, podemos cambiar el campo de resolución del creador de esta manera:
export const resolvers = { Query: { ... }, Mutation: { ... }, Book: { creator: ({ creatorId }, args, context, info) => { const userDataLoader = UserDataLoader.getInstance(context); return userDataLoader.load(creatorId); }, ... } }
Después de los cambios aplicados, si volvemos a ejecutar la consulta de libros y observamos el registro de sentencias SQL, veremos algo como esto:
select `books`.* from `books` select `users`.* from `users` where `id` in (?)
Aquí, podemos ver que las consultas de la base de datos N + 1 se redujeron a dos consultas, donde la primera selecciona la lista de libros y la segunda selecciona la lista de usuarios presentados como creadores en la lista de libros. Ahora expliquemos cómo DataLoader logra este resultado.
La función principal de DataLoader es el procesamiento por lotes. Durante la fase de ejecución única, DataLoader recopilará todos los identificadores distintos de todas las llamadas a funciones de carga individuales y luego llamará a la función por lotes con todos los identificadores solicitados. Una cosa importante para recordar es que las instancias de DataLoaders no se pueden reutilizar, una vez que se llama a la función por lotes, los valores devueltos se almacenarán en caché en la instancia para siempre. Debido a este comportamiento, debemos crear una nueva instancia de DataLoader para cada fase de ejecución. Para lograr esto, hemos creado una función estática getInstance
, que verifica si la instancia de DataLoader se presenta en un objeto de contexto y, si no la encuentra, crea una. Recuerde que se crea un nuevo objeto de contexto para cada fase de ejecución y se comparte entre todos los resolutores.
Una función de carga por lotes de DataLoader acepta una matriz de distintos ID solicitados y devuelve una promesa que se resuelve en una matriz de objetos correspondientes. Al escribir una función de carga por lotes, debemos recordar dos cosas importantes:
- La matriz de resultados debe tener la misma longitud que la matriz de ID solicitados. Por ejemplo, si solicitamos los ID
[1, 2, 3]
, la matriz de resultados devuelta debe contener exactamente tres objetos:[{ "id": 1, “fullName”: “user1” }, { “id”: 2, “fullName”: “user2” }, { “id”: 3, “fullName”: “user3” }]
- Cada índice en la matriz de resultados debe corresponder al mismo índice en la matriz de ID solicitados. Por ejemplo, si la matriz de ID solicitados tiene el siguiente orden:
[3, 1, 2]
, la matriz de resultados devuelta debe contener objetos exactamente en el mismo orden:[{ "id": 3, “fullName”: “user3” }, { “id”: 1, “fullName”: “user1” }, { “id”: 2, “fullName”: “user2” }]
En nuestro ejemplo, nos aseguramos de que el orden de los resultados coincida con el orden de las ID solicitadas con el siguiente código:
then( users => userIds.map( userId => users.filter(user => user.id === userId)[0] ) )
Seguridad
Y por último, pero no menos importante, quiero mencionar la seguridad. Con GraphQL, podemos crear API muy flexibles y brindar al usuario capacidades ricas sobre cómo consultar los datos. Esto otorga bastante poder al lado del cliente de la aplicación y, como dijo el tío Ben, "un gran poder conlleva una gran responsabilidad". Sin la seguridad adecuada, un usuario malintencionado puede enviar una consulta costosa y provocar un ataque DoS (denegación de servicio) en nuestro servidor.
Lo primero que podemos hacer para proteger nuestra API es deshabilitar la introspección del esquema GraphQL. De forma predeterminada, un servidor de la API de GraphQL expone la capacidad de realizar una introspección de todo su esquema, que generalmente se usa en shells visuales interactivos como GraphiQL y Apollo Playground, pero también puede ser muy útil para que un usuario malintencionado construya una consulta compleja basada en nuestra API. . Podemos deshabilitar esto configurando el parámetro de introspection
en falso al crear el servidor Apollo:
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); // Build Apollo server const apolloServer = new ApolloServer({ schema, introspection: false }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); })
Lo siguiente que podemos hacer para proteger nuestra API es limitar la profundidad de la consulta. Esto es especialmente importante si tenemos una relación cíclica entre nuestros tipos de datos. Por ejemplo, en nuestra muestra, el tipo de proyecto Author
tiene libros de campo y el tipo Book
tiene autores de campo. Esta es claramente una relación cíclica, y nada impide que un usuario malintencionado escriba una consulta como esta:
query { authors { id, fullName books { id, title authors { id, fullName books { id, title, authors { id, fullName books { id, title authors { ... } } } } } } } }
Está claro que con suficiente anidamiento, dicha consulta puede explotar fácilmente nuestro servidor. Para limitar la profundidad de las consultas, podemos usar una biblioteca llamada graphql-depth-limit. Una vez que lo hayamos instalado, podemos aplicar una restricción de profundidad al crear Apollo Server, así:
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); // Build Apollo server const apolloServer = new ApolloServer({ schema, introspection: false, validationRules: [ depthLimit(5) ] }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); })
Aquí, limitamos la profundidad máxima de las consultas a cinco.
Post Scriptum: Pasar de REST a GraphQL es interesante
En este tutorial, traté de demostrar los problemas comunes que encontrará cuando comience a implementar las API de GraphQL. Sin embargo, algunas partes proporcionan ejemplos de código muy superficiales y solo rascan la superficie del problema discutido, debido a su tamaño. Debido a esto, para ver ejemplos de código más completos, consulte el repositorio de Git de mi proyecto API GraphQL de muestra: graphql-example.
Al final, quiero decir que GraphQL es una tecnología realmente interesante. ¿Reemplazará a REST? Nadie lo sabe, tal vez mañana en el mundo de TI que cambia rápidamente, aparecerá un mejor enfoque para desarrollar API, pero GraphQL realmente entra en la categoría de tecnologías interesantes que definitivamente vale la pena aprender.