如何在GraphQL和Vue中构建文件处理应用程序
介绍
在本教程中,我们将通过构建一个全栈应用程序来了解如何在 GraphQL 中处理文件上传。 本教程将分为两个主要部分:构建 GraphQL API 和创建前端应用程序。 GraphQL API 将使用 Apollo Server 构建,前端应用程序将使用 Vue.js 和 Vue Apollo 构建。
先决条件
本教程假设您熟悉 GraphQL 和 Vue。 如需更多指导,您可以学习此 GraphQL with Node 教程,以及此 使用 Vue、GraphQL 和 Apollo Client 构建博客的教程。
我们将要建造的
出于本教程的目的,我们将构建一个相册应用程序,用户可以在其中上传和查看他们的照片。 所有照片将直接上传到Cloudinary。 下面是最终应用程序的快速演示:
第 1 步 — 获取 Cloudinary 密钥
在我们深入研究代码之前,让我们确保我们有我们的 Cloudinary 密钥。 如果您还没有他们的帐户,您可以免费注册。 否则,登录到您的仪表板,您将在其中看到您的帐户详细信息以及您的密钥。
第 2 步 — 构建 GraphQL 服务器
现在,让我们开始构建 GraphQL 服务器。 首先,让我们创建我们的项目目录:
$ mkdir graphql-vue-photo-upload && cd graphql-vue-photo-upload $ mkdir server && cd server $ npm init -y
所有 GraphQL API 相关代码都将在 server
目录中。 接下来,让我们为我们的 GraphQL 服务器安装必要的依赖项:
$ npm install graphql apollo-server cloudinary dotenv
除了安装 Apollo Server 及其依赖之外,我们还安装了 Cloudinary Node.js 库和一个用于读取环境变量的包。
安装完成后,我们就可以开始构建 GraphQL 服务器了。 新建一个src
目录,在里面新建一个index.js
文件,然后在里面添加如下代码:
// server/src/index.js const { 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}`))
接下来,我们需要创建 GraphQL 服务器引用的模式和解析器。 我们将从创建模式开始。 在 src
目录中创建一个 schema.js
并将以下代码粘贴到其中:
// server/src/schema.js const { 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
在这里,我们定义了一个 Photo
类型,它包含两个字段:照片的文件名和实际照片的路径。 然后我们定义一个查询 allPhotos
来获取所有上传的照片。 最后,我们有一个用于上传照片的突变。 uploadPhoto
突变接受一个参数,即要上传的照片。 参数是标量类型 Upload
,我们的 Apollo Server 可以使用它,因为它内置了对文件上传的支持。 突变将返回上传的照片。
接下来要做的是创建解析器。 仍然在 src
目录中,创建一个 resolvers.js
并在其中添加以下代码:
// server/src/resolvers.js const cloudinary = require('cloudinary').v2 cloudinary.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
首先,我们拉入 Cloudinary 库并将其配置为我们的凭据,这些凭据是从环境变量中获取的。 然后我们创建一个空数组来保存我们的照片。 接下来,我们为 allPhotos
查询定义解析器,它返回照片数组。
对于 uploadPhoto
突变,Apollo Server 会将所选文件返回为 Promise
,其中包含有关该文件的一堆详细信息,例如:createReadStream
、[X161X ]、mimetype
和 encoding
。 在本教程中,我们将只使用前两个,因此我们从对象中提取它们。 使用 createReadStream
,我们将文件直接流式传输到 Cloudinary。 由于它是异步操作,我们将其包装在 Promise
和 await
中。 如果 Promise
已解析,即文件已成功上传到 Cloudinary,我们创建一个新对象,其中包含上传的文件名和文件的 Cloudinary 路径。 然后我们将新对象推送到照片数组,最后返回新对象。
最后,如果将文件上传到 Cloudinary 时出错,我们可以通过控制台记录错误。
在我们结束 GraphQL API 之前,让我们快速添加我们的环境变量。 直接在server
目录下创建一个.env
文件,在里面添加如下代码:
// server/.env CLOUD_NAME=YOUR_CLOUD_NAME API_KEY=YOUR_API_KEY API_SECRET=YOUR_API_SECRET
请记住用您的实际帐户详细信息替换占位符。
最后,让我们启动服务器:
$ node src/index.js
服务器应该在 [1](http://localhost:4000)
上运行
第 3 步 - 构建前端应用程序
如前所述,前端应用程序将使用 Vue.js 构建,因此让我们使用 Vue CLI 创建一个新的 Vue.js 应用程序:
$ vue create client
出现提示时,按 Enter 键选择默认预设。 启动应用程序并在我们构建它时让它运行:
$ cd client $ yarn serve
该应用程序应该在 http://localhost:8080 上运行
创建 Vue 应用程序后,让我们安装必要的依赖项:
$ npm install vue-apollo graphql-tag graphql apollo-cache-inmemory apollo-client apollo-upload-client
在这些依赖项中,我想指出的新依赖项是 [apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client)
。 它是 Apollo Client 的一个包,允许我们发送 GraphQL 多部分请求。 它将代替 apollo-link
使用。
接下来,让我们配置 Vue Apollo 和这些依赖项。 更新 main.js
如下:
// client/src/main.js import { 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 = false Vue.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')
您会注意到这里我们使用 apollo-upload-client
中的 createUploadLink
来创建 ApolloClient
链接,并将我们的 GraphQL API 端点传递给它。
为了给我们的应用添加一些样式,我们将引入 UIKit。 将下面的行添加到 index.html
的 head
部分:
<!-- client/public/index.html --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.5/css/uikit.min.css" />
第 4 步 — 获取照片
我们将从用于获取所有照片的 GraphQL 查询开始。 在 client/src
目录中创建一个 graphql
目录,并在其中创建一个 AllPhotos.js
文件并将以下代码粘贴到其中:
// client/src/graphql/AllPhotos.js import gql from 'graphql-tag' export default gql`query allPhotos { allPhotos { filename path } }`
出于本教程的学习目的,我们将仅使用 App.vue
组件。 所以让我们更新如下:
// client/src/App.vue <template> <section class="uk-section"> <div class="uk-container uk-container-small"> <h2>Photo Album</h2> <div class="uk-grid uk-child-width-1-3@m"> <div class="uk-margin" v-for="(photo, index) in allPhotos" :key="index"> <div class="uk-card uk-card-default"> <div class="uk-card-media-top"> <img :src="photo.path"> </div> <div class="uk-card-body">{{ photo.filename }}</div> </div> </div> </div> </div> </section> </template> <script> import ALL_PHOTOS from "./graphql/AllPhotos"; export default { name: "app", apollo: { allPhotos: ALL_PHOTOS } }; </script>
在 apollo
对象中,我们添加 ALL_PHOTOS
查询以获取所有照片并将其保存在 allPhotos
中。 一旦 allPhotos
被来自我们的 GraphQL API 的数据填充,我们通过循环显示照片。
如果我们查看我们的应用程序,我们应该会得到类似下面的内容:
第 5 步 — 上传照片
当然,我们需要先上传一些照片才能看到它们。 现在让我们实现它。 仍然在 graphql
目录中,创建一个 UploadPhoto.js
并将以下代码粘贴到其中:
// client/src/graphql/UploadPhoto.js import gql from 'graphql-tag' export default gql`mutation uploadPhoto($photo: Upload!) { uploadPhoto(photo: $photo) { filename path } }`
接下来,将下面的代码片段添加到 App.vue
的 template
部分,就在 Photo Album 标题下方:
// client/src/App.vue <div class="uk-margin"> <input type="file" accept="image/*" @change="uploadPhoto"> </div>
在这里,我们有一个只接受图像的文件输入字段。 更改输入字段时会触发 uploadPhoto
方法。
在 script
部分中,添加:
// client/src/App.vue import 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 }); } }); } }
我们从输入事件中提取 target
,然后调用 mutate
方法,将 UPLOAD_PHOTO
突变以及所需的参数(通过 [ X170X] 对象)。 我们从 target
对象上的 files
获取选定的文件。 执行突变后,我们通过将新上传的照片添加到 allPhotos
数组来更新缓存。
结论
所以在本教程中,我们看到了如何在 GraphQL 中使用服务器端的 Apollo Server 和客户端的 Vue 和 Vue Apollo 来处理文件上传。 虽然我们使用 Cloudinary 来存储我们的照片,但您也可以将其包装为任何其他云存储服务,甚至可以直接保存到您自己的本地文件系统。