Cómo crear una aplicación de gestión de archivos en GraphQL y Vue

Introducción
En este tutorial, repasaremos cómo gestionar las cargas de archivos en GraphQL mediante la creación de una aplicación full-stack. Este tutorial se dividirá en dos secciones principales: creación de la API de GraphQL y creación de la aplicación frontend. La API de GraphQL se creará con Apollo Server y la aplicación frontend se creará con Vue.js y Vue Apollo.
Prerrequisitos
En este tutorial se presupone que estás familiarizado con GraphQL y Vue. Para obtener más orientación, puedes consultar este tutorial de GraphQL con Node y este tutorial que crea un blog con Vue, GraphQL y Apollo Client.
Lo que vamos a construir
Para este tutorial, crearemos una aplicación de álbum de fotos en la que los usuarios podrán cargar y ver sus fotos. Todas las fotos se cargarán directamente en Cloudinary. A continuación, se muestra una demostración rápida de la aplicación final:
Paso 1: obtención de claves de Cloudinary
Antes de comenzar con el código, asegurémonos de que tenemos nuestra clave de Cloudinary. Si aún no tienes una cuenta con ellos, puedes registrarte de forma gratuita. De lo contrario, inicia sesión en tu panel de control, donde verás los detalles de tu cuenta junto con tus claves.
Paso 2: creación del servidor GraphQL
Ahora, comencemos a crear el servidor GraphQL. Primero, creemos nuestro directorio de proyecto:
$ mkdir graphql-vue-photo-upload cd graphql-vue-photo-upload$ mkdir server cd server$ npm init -y
Todo el código relacionado con la API de GraphQL estará dentro del server
directorio. A continuación, instalemos las dependencias necesarias para nuestro servidor GraphQL:
$ npm install graphql apollo-server cloudinary dotenv
Además de instalar Apollo Server y sus dependencias, también instalamos la librería Node.js de Cloudinary y un paquete para leer variables de entorno.
Una vez que se haya realizado la instalación, podemos comenzar a crear el servidor GraphQL. Crea un nuevo src
directorio y crea un nuevo index.js
archivo dentro de él. Luego, agrega el siguiente código:
// server/src/index.jsconst { ApolloServer } = require('apollo-server')require('dotenv').config()const typeDefs = require('./schema')const resolvers = require('./resolvers')const server = new ApolloServer({ typeDefs, resolvers})server.listen().then(({ url }) = console.log(`Server ready at ${url}`))
A continuación, debemos crear el esquema y los solucionadores a los que hace referencia nuestro servidor GraphQL. Comenzaremos creando el esquema. Cree un directorio schema.js
interno y pegue el siguiente código en él:src
// server/src/schema.jsconst { gql } = require('apollo-server')const typeDefs = gql`type Photo { filename: String! path: String! } type Query { allPhotos: [Photo] } type Mutation { uploadPhoto(photo: Upload!): Photo! }`module.exports = typeDefs
Aquí, definimos un Photo
tipo que consta de dos campos: el nombre de archivo de la foto y la ruta a la foto real. Luego, definimos una única consulta allPhotos
para obtener todas las fotos cargadas. Por último, tenemos una mutación para cargar una foto. La uploadPhoto
mutación acepta un único argumento, que es la foto que se va a cargar. El argumento es del tipo escalar Upload
, que está disponible para nosotros en mi servidor Apollo, ya que tiene soporte integrado para la carga de archivos. La mutación devolverá la foto cargada.
El siguiente paso es crear los solucionadores. Dentro src
del directorio, crea un resolvers.js
y agrega el código que se muestra a continuación:
// server/src/resolvers.jsconst cloudinary = require('cloudinary').v2cloudinary.config({ cloud_name: process.env.CLOUD_NAME, api_key: process.env.API_KEY, api_secret: process.env.API_SECRET})const photos = []const resolvers = { Query: { allPhotos () { return photos } }, Mutation: { async uploadPhoto (parent, { photo }) { const { filename, createReadStream } = await photo try { const result = await new Promise((resolve, reject) = { createReadStream().pipe( cloudinary.uploader.upload_stream((error, result) = { if (error) { reject(error) } resolve(result) }) ) }) const newPhoto = { filename, path: result.secure_url } photos.push(newPhoto) return newPhoto } catch (err) { console.log(err) } } }}module.exports = resolvers
Primero, incorporamos la biblioteca Cloudinary y la configuramos con nuestras credenciales, que se obtienen de las variables de entorno. Luego, creamos una matriz vacía, que contendrá nuestras fotos. A continuación, definimos el solucionador para la allPhotos
consulta, que devuelve la matriz de fotos.
Para la uploadPhoto
mutación, Apollo Server devolvería el archivo seleccionado como Promise
, que contiene un montón de detalles sobre el archivo, como: createReadStream
, y . En este tutorial, solo haremos uso de los dos primeros, por lo que los extraemos del objeto. Usando , transmitimos el archivo directamente a filename
Cloudinary . Dado que es una operación asincrónica, lo envolvemos en un y lo s. Si se resolvió, es decir, el archivo se cargó correctamente a Cloudinary, creamos un nuevo objeto que contiene el nombre del archivo cargado y la ruta de Cloudinary al archivo. Luego, enviamos el nuevo objeto a la matriz de fotos y finalmente devolvemos el nuevo objeto.mimetype
encoding
createReadStream
Promise
await
Promise
Por último, si hubo un error al cargar el archivo a Cloudinary, podemos registrar el error en la consola.
Antes de finalizar la API de GraphQL, agreguemos rápidamente nuestras variables de entorno. Cree un .env
archivo directamente en el server
directorio y agregue el código que se muestra a continuación:
// server/.envCLOUD_NAME=YOUR_CLOUD_NAMEAPI_KEY=YOUR_API_KEYAPI_SECRET=YOUR_API_SECRET
Recuerde reemplazar los marcadores de posición con los detalles reales de su cuenta.
Por último, iniciemos el servidor:
$ node src/index.js
El servidor debería estar ejecutándose en[http://localhost:4000](http://localhost:4000)
Paso 3: creación de la aplicación front-end
Como ya se mencionó, la aplicación frontend se creará con Vue.js, así que creemos una nueva aplicación Vue.js usando Vue CLI:
$ vue create client
Cuando se le solicite, presione Enter para seleccionar el valor predeterminado. Inicie la aplicación y déjela ejecutándose mientras la desarrollamos:
$ cd client$ yarn serve
La aplicación debe ejecutarse en http://localhost:8080
Una vez creada la aplicación Vue, instalemos las dependencias necesarias:
$ npm install vue-apollo graphql-tag graphql apollo-cache-inmemory apollo-client apollo-upload-client
De estas dependencias, la que es nueva y que me gustaría destacar es [apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client)
. Es un paquete para Apollo Client que nos permite enviar solicitudes multiparte GraphQL. Se utilizará en lugar de apollo-link
.
A continuación, configuremos Vue Apollo y estas dependencias. Actualice main.js
como se indica a continuación:
// client/src/main.jsimport { InMemoryCache } from 'apollo-cache-inmemory'import { ApolloClient } from 'apollo-client'import { createUploadLink } from 'apollo-upload-client'import Vue from 'vue'import VueApollo from 'vue-apollo'import App from './App.vue'Vue.config.productionTip = falseVue.use(VueApollo)const apolloClient = new ApolloClient({ link: createUploadLink({ uri: 'http://localhost:4000' }), cache: new InMemoryCache()})const apolloProvider = new VueApollo({ defaultClient: apolloClient})new Vue({ apolloProvider, render: h = h(App)}).$mount('#app')
Notarás que aquí usamos createUploadLink
from apollo-upload-client
para crear el ApolloClient
enlace y le pasamos nuestro punto final de API GraphQL.
Para darle un poco de estilo a nuestra aplicación, utilizaremos UIKit. Agregue la siguiente línea a head
la sección de index.html
:
!-- client/public/index.html --link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.5/css/uikit.min.css" /
Paso 4: Obtener fotos
Comenzaremos con la consulta GraphQL para obtener todas nuestras fotos. Crea un graphql
directorio dentro del client/src
directorio y, dentro de él, crea un AllPhotos.js
archivo y pega el código que aparece a continuación:
// client/src/graphql/AllPhotos.jsimport gql from 'graphql-tag'export default gql`query allPhotos { allPhotos { filename path } }`
Para los fines de aprendizaje de este tutorial, utilizaremos únicamente el App.vue
componente. Por lo tanto, actualicémoslo de la siguiente manera:
// client/src/App.vuetemplate section div h2Photo Album/h2 div div v-for="(photo, index) in allPhotos" :key="index" div div img :src="photo.path" /div div{{ photo.filename }}/div /div /div /div /div /section/templatescriptimport ALL_PHOTOS from "./graphql/AllPhotos";export default { name: "app", apollo: { allPhotos: ALL_PHOTOS }};/script
Dentro del apollo
objeto, agregamos la ALL_PHOTOS
consulta para obtener todas las fotos y guardarlas en un archivo allPhotos
. Una vez que allPhotos
se completa con datos de nuestra API GraphQL, mostramos las fotos recorriéndolas en bucle.
Si visualizamos nuestra aplicación, deberíamos obtener algo similar a lo siguiente:
Paso 5: Carga de fotografías
Por supuesto, necesitamos haber subido algunas fotos antes de poder verlas. Vamos a implementar eso ahora. Dentro del graphql
directorio, crea un archivo UploadPhoto.js
y pega el código que aparece a continuación:
// client/src/graphql/UploadPhoto.jsimport gql from 'graphql-tag'export default gql`mutation uploadPhoto($photo: Upload!) { uploadPhoto(photo: $photo) { filename path } }`
A continuación, agregue el fragmento a continuación a la template
sección de App.vue
, justo debajo del encabezado Álbum de fotos :
// client/src/App.vuediv input type="file" accept="image/*" @change="uploadPhoto"/div
Aquí tenemos un campo de entrada de archivo que solo acepta imágenes. Al cambiar el campo de entrada, uploadPhoto
se activa un método.
En la script
sección, agregue:
// client/src/App.vueimport UPLOAD_PHOTO from "./graphql/UploadPhoto";methods: { async uploadPhoto({ target }) { await this.$apollo.mutate({ mutation: UPLOAD_PHOTO, variables: { photo: target.files[0] }, update: (store, { data: { uploadPhoto } }) = { const data = store.readQuery({ query: ALL_PHOTOS }); data.allPhotos.push(uploadPhoto); store.writeQuery({ query: ALL_PHOTOS, data }); } }); }}
Extraemos el archivo target
del evento de entrada, luego llamamos al mutate
método y le pasamos la UPLOAD_PHOTO
mutación y el argumento requerido (a través del variables
objeto). Obtenemos el archivo seleccionado del files
objeto target
. Una vez que se ejecuta la mutación, actualizamos la caché agregando la foto recién cargada a la allPhotos
matriz.
Conclusión
En este tutorial, hemos visto cómo gestionar las cargas de archivos en GraphQL utilizando Apollo Server en el lado del servidor y Vue y Vue Apollo en el lado del cliente. Aunque utilizamos Cloudinary para almacenar nuestras fotos, también puedes empaquetarlas para cualquier otro servicio de almacenamiento en la nube o incluso puedes guardarlas directamente en tu propio sistema de archivos local.
Deja una respuesta