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 serverdirectorio. 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 srcdirectorio y crea un nuevo index.jsarchivo 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.jsinterno 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 Phototipo que consta de dos campos: el nombre de archivo de la foto y la ruta a la foto real. Luego, definimos una única consulta allPhotospara obtener todas las fotos cargadas. Por último, tenemos una mutación para cargar una foto. La uploadPhotomutació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 srcdel directorio, crea un resolvers.jsy 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 allPhotosconsulta, que devuelve la matriz de fotos.
Para la uploadPhotomutació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 filenameCloudinary . 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.mimetypeencodingcreateReadStreamPromiseawaitPromise
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 .envarchivo directamente en el serverdirectorio 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.jscomo 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 createUploadLinkfrom apollo-upload-clientpara crear el ApolloClientenlace 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 headla 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 graphqldirectorio dentro del client/srcdirectorio y, dentro de él, crea un AllPhotos.jsarchivo 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 apolloobjeto, agregamos la ALL_PHOTOSconsulta para obtener todas las fotos y guardarlas en un archivo allPhotos. Una vez que allPhotosse 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 graphqldirectorio, crea un archivo UploadPhoto.jsy 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 templatesecció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, uploadPhotose activa un método.
En la scriptsecció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 targetdel evento de entrada, luego llamamos al mutatemétodo y le pasamos la UPLOAD_PHOTOmutación y el argumento requerido (a través del variablesobjeto). Obtenemos el archivo seleccionado del filesobjeto target. Una vez que se ejecuta la mutación, actualizamos la caché agregando la foto recién cargada a la allPhotosmatriz.
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