Convertir API RESTful en servidor GraphQL.

August 10, 2019 • ☕️ 6 minutos de lectura

Introducción

GraphQL es un lenguaje de programación creado por Facebook en 2015 y aunque es una alternativa a REST, esto no significa que lo sustituya, es más, aquí veremos un sencillo ejemplo de como consumir los datos de una api para usarlos en GraphQL.

Entre todas sus características destacan:

  • Podemos describir y validar los datos.
  • Podemos seleccionar solo la información que queremos recibir
  • Eliminamos el concepto de endpoint, en su lugar usaremos Tipos, en los que indicamos lo que necesitamos.
  • Podemos acceder a dintintos recursos en una sola llamada. (Imagina poder llamar a varios endpoint al mismo tiempo y unirlo todo en un objeto)
  • Además con REST solo nos comúnicamos con la base de datos, aquí podemos obtener la información de cualquier sitio: API, firebase, wordpress, json, ficheros…

Schema

Schema: Es la base de GraphQL, un docuemnto que describe todo el tipo de información que tendra y especificando que campo es. Aquí es donde se definen los tipos.

Los Query son las consultas que podremos realizar y las Mutations nos sirven para hacer cambios.

Tipos

Los tipos definen las entidades que vamos a consultar o modificar, sus atributos y las relaciones con otros tipos. Para esto se usa el lenguaje SDL y tiene para ello reservada la palabra type.

type Post {
    id: ID!
    title: String!
    body: String!
    author: User!
  }

La sintaxis de SDL nos permite definir tipos con los campos que queremos consultar. Cada campo en un tipo también tiene un tipo. Estos pueden ser :

  • Int: Un entero de 32 bits con signo.
  • Float: Un valor de coma flotante de doble precisión 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 la clave para un caché. El tipo de ID se serializa de la misma manera que una cadena; sin embargo, definirlo como un ID significa que no está destinado a ser legible por humanos.

GraphQL nos permite configurar nuestros propios tipos de datos, gracias a los Custom Types y deben tener la siguiente sintaxys:

type <Nombre del tipo> {
  propiedad: Tipo de dato
}

en nuestro caso podría ser:

type <Post> {
  title: String
}

El signo de exclamación que sigue al tipo de campo significa que este campo nunca puede ser null.

Las listas funcionan de manera similar, si envolvemos el tipo entre corchetes [ ]. Funciona igual para los argumentos, donde el paso de validación esperará una array para ese valor.

Los modificadores No nulo y Lista se pueden combinar. Por ejemplo, puede tener una Lista de cadenas no nulas:

myField :  [String !]

Argumentos Los argumentos nos ayudan a filtrar nuestros objetos.

En este ejemplo le indicamos que busque un post por el id ID, además indicamos que el valor no puede ser nulo con ! y devuelve dicho post.

type Query {
    post(id: ID!): Post
  }

Pero en GraphQL, cada campo y objeto anidado puede obtener su propio conjunto de argumentos y esto hace que podamos filtrar con mucho más detalle.

{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}

Mutaciones

Es igual que los query, pero nos permite insertar la información en la API.

input PostInput {
  title: String!
  body: String!
  author: User!
}
type Mutation {
  "crear un post"
  crearPost(input: PostInput!): Post
}

Igual que hicimos con los Query, ahora tenemos crear un resolver para nuesta Mutation.

module.exports = {
  createPost: async (root, { input }) => {
    // input: son los datos que vamos a recibir
    // Aquí el código para añadir
    // Debemos retornar un obteto input.id
  }
}

Empezando la aplicación

Primero definiremos el schema, podríamos dejarlo en el mismo fichero y pasarlo como una variable, pero en nuestro caso lo crearemos en el directorio src/schema.graphql

Recuerda que aquí es dónde definimos la lógica de nuestra App.

type Query {
    users: [User!]!
    user(id: ID!): User
    posts: [Post!]!
    post(id: ID!): Post
  }
  
  type User {
    id: ID!
    username: String!
    name: String!
    email: String!
    phone: String!
    posts: [Post!]!
  }
  
  type Post {
    id: ID!
    title: String!
    body: String!
    author: User!
  }
  query {
    users {
      id
      name
    }
  }
  • Post es un tipo de objeto GraphQL , lo que significa que es un tipo con algunos campos. La mayoría de los tipos en su esquema serán tipos de objetos.

Ahora necesitamos decirle a GraphQL cómo resolver las operaciones cuando se reciban peticiones de recurros. Recurriremos a funciones comúnmente llamadas resolvers.

Los resolvers es un objeto que tiene una propiedad igual al nombre de la Query, esta función se ejecutará cuando se llame esa query.

const resolvers = {
  Query: {
    posts: () => {
      return fetch(`${baseURL}/posts`).then(res => res.json());
    }
  }
}

Como podemos ver, es símplemente una llamada a nuestra API para recoger los datos. Si quisiésemos, podríamos en vez de hacer una llamada a la API, diréctamente cargar la información desde la misma base de datos o desde cualquier otra fuente de datos, como es Firebase…

Ahora instalaremos las dependencias que vamos a necesitas, para crear el servidor GraphQL, usaremos graphql-yoga y para realizas las peticiones fetch, usaremos node-fetch

npm i graphql-yoga node-fetch

Lo primero que debemos hacer es importar las librerías instaladas.

const { GraphQLServer } = require("graphql-yoga");
const fetch = require("node-fetch");

También debemos crear la base de la url.

const baseURL = proccess.ENV.ok || `https://jsonplaceholder.typicode.com`;

Definimos los resolvers, como ya hemos explicado antes

const resolvers = {
  Query: {
    users: () => {
      return fetch(`${baseURL}/users`).then(res => res.json());
    },
    user: (parent, args) => {
      const { id } = args;
      return fetch(`${baseURL}/users/${id}`).then(res => res.json());
    },
    posts: () => {
      return fetch(`${baseURL}/posts`).then(res => res.json());
    },
    post: (parent, args) => {
      const { id } = args;
      return fetch(`${baseURL}/posts/${id}`).then(res => res.json());
    }
  },
  Post: {
    author: parent => {
      const { id } = parent;
      return fetch(`${baseURL}/users/${id}/todos`).then(res =>
        res.json()
      );
    }
  },
  User: {
    posts: parent => {
      const { id } = parent;
      return fetch(`${baseURL}/posts/${id}/todos`).then(res => res.json());
    }
  }
};

Por último definimos server usando GraphQLServer que recibe como parámetros el typeDefs, que es nuestro shema.graphql y nuestros resolvers.

const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
  resolvers
});

server.start(() => console.log(`Servidor corriendo en http://localhost:4000`));

Ya hemos terminado y nuestro fichero index.js debería ser así:

// `index..js`
const { GraphQLServer } = require("graphql-yoga");
const fetch = require("node-fetch");


const baseURL = `https://jsonplaceholder.typicode.com`;

const resolvers = {
  Query: {
    users: () => {
      return fetch(`${baseURL}/users`).then(res => res.json());
    },
    user: (parent, args) => {
      const { id } = args;
      return fetch(`${baseURL}/users/${id}`).then(res => res.json());
    },
    posts: () => {
      return fetch(`${baseURL}/posts`).then(res => res.json());
    },
    post: (parent, args) => {
      const { id } = args;
      return fetch(`${baseURL}/posts/${id}`).then(res => res.json());
    }
  },
  Post: {
    author: parent => {
      const { id } = parent;
      return fetch(`${baseURL}/users/${id}/todos`).then(res =>
        res.json()
      );
    }
  },
  User: {
    posts: parent => {
      const { id } = parent;
      return fetch(`${baseURL}/posts/${id}/todos`).then(res => res.json());
    }
  }
};

const server = new GraphQLServer({
//   typeDefs: typeDefs,
typeDefs: './src/schema.graphql',
  resolvers
});

server.start(() => console.log(`Servidor corriendo en http://localhost:4000`));

Lo ejecutamos con el comando

node index.js

Y ahora podemos empezar a relizar nuestras consultas a http://localhost:4000

Aquí te dejo unos ejemplos:

Consulta graphql

Consulta graphql

Consulta graphql

Consulta graphql

⚠️Esto es solo un ejemplo, para pasarlo a producción deberías securizar todo, empezando por proteger las rutas de los usuarios con jwt. Configurar cors Desactivar graphiQL si no estamos en desarollo.

Aquí te dejo un enlace: https://www.prisma.io/tutorials/graphql-rest-authentication-authorization-basics-ct20

Llegados aquí, te abrás dado cuenta que seguimos realizando las peticiones a la API entonces y esto puede llevar a preguntarse

🤔¿Esto es ahora más rapido?… si hemos añadido una nueva capa, debería tardar más.

La explicación es simple, aunque se hacen las llamadas a la api, se realizan desde el servidor que tendrá mucha menos latencia que el cliente, ya que este está en el mismo servidor.

No obstante si tienes opción, la conexión la intentaría hacer lo más directa a los datos posible.

Si quieres puedes descargar el código completo de mi Github