如何在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 ]、mimetypeencoding。 在本教程中,我们将只使用前两个,因此我们从对象中提取它们。 使用 createReadStream,我们将文件直接流式传输到 Cloudinary。 由于它是异步操作,我们将其包装在 Promiseawait 中。 如果 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.htmlhead 部分:

<!-- 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.vuetemplate 部分,就在 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 来存储我们的照片,但您也可以将其包装为任何其他云存储服务,甚至可以直接保存到您自己的本地文件系统。