序章

このチュートリアルでは、フルスタックアプリを構築して、GraphQLでファイルのアップロードを処理する方法について説明します。 このチュートリアルは、GraphQL APIの構築と、フロントエンドアプリの作成という2つの主要なセクションに分かれています。 GraphQL APIはApolloサーバーを使用して構築され、フロントエンドアプリはVue.jsとVueApolloを使用して構築されます。

前提条件

このチュートリアルは、GraphQLとVueに慣れていることを前提としています。 詳細なガイダンスについては、この GraphQL with Nodeチュートリアル、およびVue、GraphQL、およびApolloClientを使用してブログを構築するこのチュートリアルから学ぶことができます。

構築するもの

このチュートリアルでは、ユーザーが写真をアップロードしたり表示したりできるフォトアルバムアプリを作成します。 すべての写真はCloudinaryに直接アップロードされます。 以下は、最終的なアプリの簡単なデモです。

ステップ1—Cloudinaryキーを取得する

コードに飛び込む前に、Cloudinaryキーが設定されていることを確認しましょう。 まだアカウントをお持ちでない場合は、無料でサインアップできます。 それ以外の場合は、ダッシュボードにログインすると、アカウントの詳細とキーが表示されます。

ステップ2—GraphQLサーバーを構築する

それでは、GraphQLサーバーの構築を始めましょう。 まず、プロジェクトディレクトリを作成しましょう。

$ mkdir graphql-vue-photo-upload && cd graphql-vue-photo-upload
$ mkdir server && cd server
$ npm init -y

すべてのGraphQLAPI関連のコードは内部にあります server ディレクトリ。 次に、GraphQLサーバーに必要な依存関係をインストールしましょう。

$ npm install graphql apollo-server cloudinary dotenv

Apollo Serverとその依存関係のインストールに加えて、CloudinaryNode.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サーバーが参照するスキーマとリゾルバーを作成する必要があります。 スキーマの作成から始めます。 作成する schema.js 中身 src ディレクトリに次のコードを貼り付けます。

// 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 写真のファイル名と実際の写真へのパスの2つのフィールドで構成されるタイプ。 次に、単一のクエリを定義します allPhotos アップロードされたすべての写真を取得します。 最後に、写真をアップロードするためのミューテーションがあります。 The uploadPhoto ミューテーションは、アップロードされる写真である単一の引数を受け入れます。 引数はスカラー型です Upload、ファイルアップロードのサポートが組み込まれているため、Apolloサーバーで利用できるようになります。 ミューテーションはアップロードされた写真を返します。

次に行うことは、リゾルバーを作成することです。 まだ中に 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 ミューテーションの場合、ApolloServerは選択したファイルを次のように返します。 Promise、次のようなファイルに関する一連の詳細が含まれています。 createReadStream, filename, mimetypeencoding. このチュートリアルでは、最初の2つのみを使用するため、オブジェクトからそれらを抽出します。 使用する createReadStream、ファイルをCloudinaryに直接ストリーミングします。 これは非同期操作であるため、 Promiseawait座る。 の場合 Promise が解決されました。つまり、ファイルがCloudinaryに正常にアップロードされました。アップロードされたファイル名と、ファイルへのCloudinaryパスを含む新しいオブジェクトを作成します。 次に、新しいオブジェクトをphotos配列にプッシュし、最後に新しいオブジェクトを返します。

最後に、Cloudinaryへのファイルのアップロードでエラーが発生した場合は、エラーをコンソールログに記録できます。

GraphQL APIをまとめる前に、環境変数を簡単に追加しましょう。 作成する .env 直接ファイル server ディレクトリを作成し、その中に以下のコードを追加します。

// server/.env

CLOUD_NAME=YOUR_CLOUD_NAME
API_KEY=YOUR_API_KEY
API_SECRET=YOUR_API_SECRET

プレースホルダーを実際のアカウントの詳細に置き換えることを忘れないでください。

最後に、サーバーを起動しましょう。

$ node src/index.js

サーバーはで実行されている必要があります [http://localhost:4000](http://localhost:4000)

ステップ3—フロントエンドアプリを構築する

すでに述べたように、フロントエンドアプリはVue.jsで構築されるので、VueCLIを使用して新しい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). これは、GraphQLマルチパートリクエストを送信できるようにするApolloクライアント用のパッケージです。 代わりに使用されます apollo-link.

次に、VueApolloとこれらの依存関係を構成しましょう。 アップデート 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')

ここで私たちが使用していることに気付くでしょう createUploadLink から apollo-upload-client を作成するには ApolloClient リンク、GraphQLAPIエンドポイントを渡します。

アプリに少しスタイルを与えるために、UIKitを使用します。 以下の行をに追加します head のセクション index.html:

<!-- client/public/index.html -->

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.5/css/uikit.min.css" />

ステップ4—写真を取得する

すべての写真をフェッチするためのGraphQLクエリから始めます。 作成する graphql 内部のディレクトリ client/src ディレクトリとで、を作成します 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
    }
  }`

次に、以下のスニペットをに追加します template のセクション App.vueフォトアルバムの見出しのすぐ下:

// 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 突然変異と必要な引数( variables 物体)。 選択したファイルを filestarget 物体。 ミューテーションが実行されると、新しくアップロードされた写真をに追加してキャッシュを更新します allPhotos 配列。

結論

したがって、このチュートリアルでは、サーバー側でApollo Serverを使用し、クライアント側でVueとVue Apolloを使用して、GraphQLでファイルのアップロードを処理する方法を見てきました。 写真の保存にはCloudinaryを使用しましたが、他のクラウドストレージサービス用にラップしたり、独自のローカルファイルシステムに直接保存したりすることもできます。