LoginSignup
0
0

More than 1 year has passed since last update.

多分絶対誰も必要ないけど、node環境からapollo clientを使ってfileをアップロードする方法

Last updated at Posted at 2021-10-20

注意

fetchのpolyfillとしてnode-fetchを使っていると(next.jsのapi routesは使ってる)

Note: when body is a Stream, Content-Length is not set automatically.

なので、fs.createReadStreamなファイルを渡すと、結果としてcontent-lengthがheaders情報としてサーバー側に渡されない。 サーバーによってはエラーの原因になると思う。

puma v4.3.7未満はcontent-lengthが無いとPOSTデータを上手く処理できない様子なので、うまくサーバーにデータが行かない場合はpumaのバージョンを確認したほうが良い。

Backport set CONTENT_LENGTH for chunked requests

問題

  • apollo clientを使っている
  • apollo-upload-clientを使っている
  • node環境から使っている(nextjsのapi routesとか)

この場合、clientと同じようなコードを書いてもアップロードが機能しない。

解決方法

node環境では

なので、apollo-upload-clientのcreateUploadLinkを使う際にこれらのnode版を指定してやる。

そしてFile、Blobがないのでfsでファイルをstreamとして読み込み、それをアップロード対象のオブジェクトとするが、デフォルトではFileかBlobしか対象になっていないので、ReadStreamが対象になるようにisExtractableFileで設定し直す。

import FormData from "form-data"  // yarn add form-data
import fetch from "isomorphic-unfetch" // yarn add isomorphic-unfetch
import { ReadStream } from "fs"
import { createUploadLink, isExtractableFile } from "apollo-upload-client"

createUploadLink({
  uri: "https://backend/graphql",
  fetch,
  FormData,  
  isExtractableFile: (value) =>
    isExtractableFile(value) ||
    (typeof ReadStream !== "undefined" && value instanceof ReadStream),
})
import { ApolloClient, InMemoryCache } from "@apollo/client"
import { createUploadLink, isExtractableFile } from "apollo-upload-client"
import FormData from "form-data"
import fs, { ReadStream } from "fs"
import type { NextApiRequest, NextApiResponse } from "next"
import * as Generated from "src/generated/graphql" // @graphql-codegenを使っているので...

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: createUploadLink({
    uri: "https://backend/graphql", // 他のgraphqlサーバー
    // fetch, nextjsを使っているので必要ない
    FormData,  
    isExtractableFile: (value) =>
      isExtractableFile(value) ||
      (typeof ReadStream !== "undefined" && value instanceof ReadStream),
  }),
})

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const file = fs.createReadStream(fs.realpathSync("./test.jpg"))

  await client.mutate<
    Generated.TestMutationMutation,
    Generated.TestMutationMutationVariables
  >({
    mutation: Generated.TestMutationDocument,
    variables: {
      input: {
        file,
      },
    },
    fetchPolicy: "no-cache",
  })

  res.status(200).end()
}
package.json
{
  "scripts": {
    "dev": "next",
    "generate": "graphql-codegen -w --config codegen.yml"
  },
  "dependencies": {
    "@apollo/client": "^3.4.16",
    "apollo-upload-client": "^16.0.0",
    "form-data": "^4.0.0",
    "graphql": "^15.6.1",
    "next": "^11.1.2",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@graphql-codegen/cli": "^2.2.1",
    "@graphql-codegen/typescript": "^2.2.4",
    "@graphql-codegen/typescript-operations": "^2.1.8",
    "@graphql-codegen/typescript-react-apollo": "^3.1.6",
    "@types/react": "^17.0.30",
    "typescript": "^4.4.4"
  }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0