Creación de API de GraphQL con Vue.js y Apollo Client

GraphQL es un lenguaje de consulta orientado a gráficos escrito por Facebook. A diferencia de las API REST, GraphQL presenta funciones que hacen que el desarrollo de API sea más eficiente...

Introducción

GráficoQL es un lenguaje de consulta orientado a gráficos escrito por Facebook. A diferencia de las API REST, GraphQL presenta características que hacen que el desarrollo de API sea más eficiente y esté en sintonía con los modelos de bases de datos.

Funciones de GraphQL

  • A diferencia de REST, solo hay un punto final al que se enviarán todas las solicitudes. Entonces, en lugar de consultar /users para obtener una lista de usuarios, o /user/:id para obtener un usuario en particular, el punto final se verá como /graphql para todas las solicitudes.
  • En GraphQL, los datos que regresan de una respuesta los establece la biblioteca de consultas indicada y se puede configurar para enviar solo algunas propiedades de datos, por lo tanto, las consultas en GraphQL tienen un mejor rendimiento.
  • No es necesario establecer verbos de método en GraphQL. Las palabras clave como Consulta o Mutación decidirán qué realizará la solicitud.
  • Las rutas de la API REST generalmente son manejadas por un controlador de ruta. En GraphQL, puede hacer que una sola consulta desencadene múltiples mutaciones y obtenga una respuesta compuesta de múltiples fuentes.

Consultas

Una consulta es un método de GraphQL que nos permite OBTENER datos de nuestra API. Aunque puede recibir parámetros para filtrar, ordenar o simplemente buscar un documento en particular, una consulta no puede mutar estos datos.

Mutaciones

Las mutaciones son todo lo que no es lo que se referiría a un verbo GET en las API normales. La actualización, creación o eliminación de datos de nuestra API se realiza mediante mutaciones

Suscripciones

Con el uso de sockets web, una suscripción se refiere a una conexión entre el cliente y el servidor.

El servidor está constantemente atento a las mutaciones o consultas que se adjuntan a una suscripción en particular y comunica cualquier cambio al cliente en tiempo real. Las suscripciones se utilizan principalmente para widgets/aplicaciones en tiempo real.

Tipos y entradas

Para asegurarnos de que nuestras consultas y mutaciones puedan procesar los datos para consultar una base de datos, los “tipos” funcionan de manera muy similar a un ORM modelo para bases de datos. Al configurar tipos, podemos definir el tipo de variable que devolverán nuestros resolutores.

Del mismo modo, necesitamos establecer tipos de entrada para que los reciban nuestros resolutores.

Por ejemplo, definiremos un par de tipos y entradas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type User {
  id: ID
  name: String!
  age: Int!
  address: Address
  followers: [ID]
}

type Address {
  street: String
  city: String
  country: String
}

input UserInput {
  name: String!
  age: Int!
}

type Query {
  getAllUsers: [User]
}

type Mutation {
  createUser(user: UserInput!): ID
}

Las propiedades pueden tener un tipo personalizado como su tipo además de los primitivos, como:

  • Cuerda
  • En t
  • Flotar
  • Booleano
  • IDENTIFICACIÓN

Y también pueden ser una matriz de cierto tipo determinada por los corchetes, que se muestra en el ejemplo anterior.

Además, el estado obligatorio de una propiedad se puede establecer con !, lo que significa que la propiedad debe estar presente.

Resolvedores

Estas son las acciones que se realizan al llamar consultas y mutaciones.

getAllUsers y createUser se conectarán a un resolver que realizará los cálculos reales y las consultas a la base de datos.

Creando nuestro Proyecto

Para este tutorial, crearemos un proyecto Vue.js utilizando Vue CLI 3.0, que iniciará un proyecto con una estructura de carpetas que se ve así:

{.img-responsive}

Si necesita ayuda para configurar el proyecto, puede consultar este tutorial para la interfaz de línea de comandos.

Podemos comenzar a servir nuestra aplicación con el comando:

1
$ npm run serve

Cliente Apollo

Cliente Apolo trae una herramienta para el desarrollo front-end para facilitar las consultas/mutaciones de GraphQL. Actúa como un cliente HTTP que se conecta a una API de GraphQL y proporciona capacidades de almacenamiento en caché, manejo de errores e incluso administración de estado.

Para este tutorial, se usará Vue-Apollo, que es la integración de Apollo especialmente diseñada para Vue.js.

Configuración de Apolo

Para iniciar nuestra configuración de Apollo, será necesario instalar algunos paquetes:

1
$ npm install apollo-client apollo-link-http apollo-cache-inmemory vue-apollo graphql graphql-tag

Dentro de una carpeta /graphql en nuestro proyecto, crearemos apollo.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// apollo.js

import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'

const httpLink = new HttpLink({
    uri: process.env.VUE_APP_GRAPHQL_ENDPOINT
})

// Create the apollo client
export const apolloClient = new ApolloClient({
    link: httpLink,
    cache: new InMemoryCache(),
    connectToDevTools: true
})

// Install the Vue plugin

Vue.use(VueApollo)

export const apolloProvider = new VueApollo({
    defaultClient: apolloClient
})

HttpLink es un objeto que requiere una propiedad uri, que hace referencia al punto final GraphQL de la API que se está utilizando. Ej: localhost:8081/graphql

Luego, se debe crear una nueva instancia de ApolloClient, donde se pueden configurar el enlace, la instancia de caché y otras opciones.

Finalmente, envolvemos nuestro ApolloClient dentro de una instancia de VueApollo para que podamos usar sus ganchos dentro de nuestros componentes de Vue.

Gestión global de errores

Hay una forma de manejar errores globalmente dentro del archivo de configuración. Para eso necesitamos instalar un paquete npm llamado apollo-link-error, que inspecciona y gestiona los errores de la red:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// apollo.js

import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { onError } from "apollo-link-error"
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'

const httpLink = new HttpLink({
    uri: process.env.VUE_APP_GRAPHQL_ENDPOINT
})

// Error Handling
const errorLink = onError(({ graphQLErrors, networkError }) => {
    if (graphQLErrors)
        graphQLErrors.map(({ message, locations, path }) =>
            console.log(
                `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
            )
        )
    if (networkError) console.log(`[Network error]: ${networkError}`)
})

// Create the apollo client
export const apolloClient = new ApolloClient({
    link: errorLink.concat(httpLink),
    cache: new InMemoryCache(),
    connectToDevTools: true
})

// Install the Vue plugin
Vue.use(VueApollo)

export const apolloProvider = new VueApollo({
    defaultClient: apolloClient
})

Después de importar la función onError del paquete, podemos implementarla como una especie de middleware de Apollo Client. Detectará cualquier error de red o de GraphQL, dándonos la oportunidad de administrarlos globalmente.

La devolución de llamada se llama con un objeto con algunas propiedades cada vez que ocurre un error:

  • operación: la operación que activó la devolución de llamada porque se encontró un error.
  • respuesta: El resultado de la operación.
  • graphQLErrors: una matriz de errores del punto final de GraphQL
  • networkError: Cualquier error durante la ejecución de la operación o error del servidor.
  • adelante: El siguiente eslabón referenciado en la cadena.

Administración del estado con Apollo Client

Una alternativa diferente al uso de Vuex con proyectos Vue, y cuando se usa Apollo Client, es usar un paquete llamado apollo-link-state.

Funciona como una herramienta de gestión de datos local que funciona como si estuvieras consultando un servidor, pero lo hace localmente.

Además, es una excelente manera de administrar el caché para nuestra aplicación, lo que convierte a Apollo Client en un cliente HTTP y una herramienta de administración de estado/caché.

Para más información, puedes consultar la documentación oficial del Estado de enlace de Apolo.

Creación de consultas

Para crear consultas, debemos configurar una etiqueta de tipo cadena con el paquete graphql-tag. Para mantener un proyecto ordenado y estructurado, crearemos una carpeta llamada consultas dentro de la carpeta graphql.

Suponiendo que el servidor que recibe la consulta está configurado correctamente para interpretar esta consulta, por ejemplo, podemos activar una resolución llamada getAllUsers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import gql from 'graphql-tag'

export const GET_ALL_USERS_QUERY = gql`
  query getAllUsers {
    getAllUsers {
      // Fields to retrieve
      name
      age
    }
  }
`

La operación predeterminada en GraphQL es query, por lo que la palabra clave query es opcional.

Si un campo recuperado tiene subcampos, se debe recuperar al menos uno de ellos para que la consulta tenga éxito.

Usar mutaciones

Al igual que las consultas, también podemos usar mutaciones creando una gql-string.

1
2
3
4
5
6
7
import gql from 'graphql-tag'

export const CREATE_USER_MUTATION = gql`
  mutation createUser($user: UserInput!) {
    createUser(user: $user)
  }
`

Nuestra mutación createUser espera una entrada UserInput y poder usar los parámetros pasados ​​por Apollo. Primero definiremos una variable con el $ llamada usuario. Luego, el envoltorio externo pasará la variable a la mutación createUser, como lo espera el servidor.

Fragmentos

Para mantener nuestras cadenas gql-type ordenadas y legibles, podemos usar fragmentos para reutilizar la lógica de consulta.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fragment UserFragment on User {
  name: String!
  age: Int!
}

query getAllUsers {
  getAllUsers {
    ...UserFragment
  }
}

Usando GraphQL en componentes Vue

Dentro del archivo main.js, para configurar el cliente Apollo, necesitamos importar y adjuntar el cliente a nuestra instancia.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// main.js
import Vue from 'vue'
import { apolloProvider } from './graphql/apollo'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
    el: '#app',
    apolloProvider,
    render: h => h(App)
})

Como hemos agregado nuestro ApolloProvider a la instancia de Vue, podemos acceder al cliente a través de la palabra clave $apollo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// GraphQLTest.vue
<template>
    <div class="graphql-test">
        <h3 v-if="loading">Loading...</h3>
        <h4 v-if="!loading">{{ getAllUsers }}</h4>
    </div>
</template>

<script>
import { GET_ALL_USERS_QUERY } from '../graphl/queries/userQueries'
export default {
    name: 'GraphQLTest',
    data () {
        return {
            users: []
        }
    },
    async mounted () {
        this.loading = true
        this.users = await this.$apollo.query({ query: GET_ALL_USERS_QUERY })
        this.loading = false
    }
}
</script>

Si queremos crear un usuario, podemos usar una mutación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// GraphQLTest.vue
<template>
    <div class="graphql-test">
        <input v-model="user.name" type="text" placeholder="Name" />
        <input v-model="user.age" placeholder="Age" />
        <button @click="createUser">Create User</button>
    </div>
</template>

<script>
import { CREATE_USER_MUTATION } from '../graphl/queries/userQueries'
export default {
    name: 'GraphQLTest',
    data() {
        return {
            user: {
                name: null,
                age: null
            }
        }
    },
    methods: {
        async createUser () {
            const userCreated = await this.$apollo.mutate({
                mutation: CREATE_USER_MUTATION,
                variables: {
                    user: this.user // this should be the same name as the one the server is expecting
                }
            })
            // We log the created user ID
            console.log(userCreated.data.createUser)
        }
    }
}
</script>

El uso de este enfoque nos permite microgestionar cuándo y dónde se ejecutarán nuestras mutaciones y consultas. Ahora veremos algunas otras formas de manejar estos métodos que nos brinda Vue Apollo.

El Objeto Apolo

Dentro de nuestros componentes de Vue, tenemos acceso al objeto Apollo, que se puede usar para administrar fácilmente nuestras consultas y suscripciones:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<template>
    <div class="graphql-test">
        {{ getAllUsers }}
    </div>
</template>

<script>
import { GET_ALL_USERS_QUERY } from '../graphl/queries/userQueries'
export default {
    name: 'GraphQL-Test',
    apollo: {
        getAllUsers: {
            query: GET_ALL_USERS_QUERY
        }
    }
}
</script>
Recuperación de consultas

Al definir una consulta dentro del objeto Apollo, es posible recuperar esta consulta al llamar a una mutación u otra consulta con el método refetch o la propiedad refetchQueries:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
    <div class="graphql-test">
        {{ getAllUsers }}
    </div>
</template>

<script>
import { GET_ALL_USERS_QUERY, CREATE_USER_MUTATION } from '../graphl/queries/userQueries'
export default {
    name: 'GraphQL-Test',
    apollo: {
        getAllUsers: {
            query: GET_ALL_USERS_QUERY
        }
    },
    methods: {
        refetch () {
            this.$apollo.queries.getAllUsers.refetch()
        },
        queryUsers () {
            const user = { name: Lucas, age: 26 }
            this.$apollo.mutate({
                mutation: CREATE_USER_MUTATION,
                variables: {
                    user
                }
                refetchQueries: [
                    { query: GET_ALL_USERS_QUERY }
                ]
            })
        }
    }
}
</script>

Al usar el objeto Apollo, que nos proporciona Vue-Apollo, ya no necesitamos usar activamente la forma del cliente Apollo de activar consultas/suscripciones y algunas propiedades y opciones útiles están disponibles para nosotros.

Propiedades del objeto Apollo
  • consulta: Esta es la cadena de tipo gql que se refiere a la consulta que desea activar.
  • variables: un objeto que acepta los parámetros que se pasan a una consulta determinada.
  • fetchPolicy: una propiedad que establece la forma en que la consulta interactuará con el caché. Las opciones son cache-and-network, network-only, cache-only, no-cache, standby y el valor predeterminado es cache-first.
  • pollInterval: tiempo en milisegundos que determina la frecuencia con la que se activará automáticamente una consulta.
Opciones especiales
  • $error para detectar errores en un controlador de conjunto.
  • $deep observa atentamente los cambios en una consulta.
  • $skip: deshabilita todas las consultas y suscripciones en un componente determinado.
  • $skipAllQueries: deshabilita todas las consultas de un componente.
  • $skipAllSubscriptions: para deshabilitar todas las suscripciones en un componente.

Componentes de Apolo

Inspirado en la forma en que se implementa el Cliente Apollo para Reaccionar (Reaccionar-Apolo), Vue-Apollo proporciona nosotros con algunos componentes que podemos usar de inmediato para administrar la interfaz de usuario y el estado de nuestras consultas y mutaciones con un componente Vue dentro de la plantilla.

Consulta de Apolo

Manera más sencilla de gestionar nuestras consultas de forma más intuitiva:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<ApolloQuery
  :query="GET_ALL_USERS_QUERY"
>
    <template slot-scope="{ result: { loading, error, data } }">
        <!-- Loading -->
        <div v-if="loading">Query is loading.</div>

        <!-- Error -->
        <div v-else-if="error">We got an error!</div>

        <!-- Result -->
        <div v-else-if="data">{{ data.getAllUsers }}</div>

        <!-- No result (if the query succeed but there's no data) -->
        <div v-else>No result from the server</div>
    </template>
</ApolloQuery>
Mutación Apolo

Muy similar al ejemplo anterior, pero debemos desencadenar la mutación con la llamada a la función mutate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<ApolloMutation
  :mutation="CREATE_USER_MUTATION"
  :variables="{
    name,
    age
  }"
  @done="mutationFinished"
>
    <template slot-scope="{ mutate, loading, error }">
        <!-- Loading -->
        <h4 v-if="loading">The mutation is loading!</h4>

        <!-- Mutation Trigger -->
        <button @click="mutate()">Create User</button>

        <!-- Error -->
        <p v-if="error">An error has occurred!</p>
    </template>
</ApolloMutation>

Conclusión

GraphQL brinda mucha flexibilidad al desarrollo de API, desde el rendimiento, la facilidad de uso y una perspectiva general diferente de cómo debería verse y comportarse una API. Además, ApolloClient y Vue Apollo ofrecen un conjunto de herramientas para una mejor gestión de nuestra interfaz de usuario, estado y operaciones, ¡incluso manejo de errores y caché!

Para obtener más información sobre GraphQL y Apollo Client, puede visitar lo siguiente: