Need help with graphql-multipart-request-spec?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

jaydenseric
574 Stars 36 Forks 73 Commits 1 Opened issues

Description

A spec for GraphQL multipart form requests (file uploads).

Services available

!
?

Need anything else?

Contributors list

No Data

GraphQL multipart request specification

GitHub release

An interoperable multipart form field structure for GraphQL requests, used by various file upload client/server implementations.

It’s possible to implement:

  • Nesting files anywhere within operations (usually in
    variables
    ).
  • Operation batching.
  • File deduplication.
  • File upload streams in resolvers.
  • Aborting file uploads in resolvers.

Sync vs async GraphQL multipart request middleware

Multipart form field structure

An “operations object” is an Apollo GraphQL POST request (or array of requests if batching). An “operations path” is an

object-path
string to locate a file within an operations object.

So operations can be resolved while the files are still uploading, the fields are ordered:

  1. operations
    : A JSON encoded operations object with files replaced with
    null
    .
  2. map
    : A JSON encoded map of where files occurred in the operations. For each file, the key is the file multipart form field name and the value is an array of operations paths.
  3. File fields: Each file extracted from the operations object with a unique, arbitrary field name.

Examples

Single file

Operations

{
  query: `
    mutation($file: Upload!) {
      singleUpload(file: $file) {
        id
      }
    }
  `,
  variables: {
    file: File // a.txt
  }
}

cURL request

curl localhost:3001/graphql \
  -F operations='{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }' \
  -F map='{ "0": ["variables.file"] }' \
  -F [email protected]

Request payload

--------------------------cec8e8123c05ba25
Content-Disposition: form-data; name="operations"

{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } } --------------------------cec8e8123c05ba25 Content-Disposition: form-data; name="map"

{ "0": ["variables.file"] } --------------------------cec8e8123c05ba25 Content-Disposition: form-data; name="0"; filename="a.txt" Content-Type: text/plain

Alpha file content.

--------------------------cec8e8123c05ba25--

File list

Operations

{
  query: `
    mutation($files: [Upload!]!) {
      multipleUpload(files: $files) {
        id
      }
    }
  `,
  variables: {
    files: [
      File, // b.txt
      File // c.txt
    ]
  }
}

cURL request

curl localhost:3001/graphql \
  -F operations='{ "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }' \
  -F map='{ "0": ["variables.files.0"], "1": ["variables.files.1"] }' \
  -F [email protected] \
  -F [email protected]

Request payload

--------------------------ec62457de6331cad
Content-Disposition: form-data; name="operations"

{ "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } } --------------------------ec62457de6331cad Content-Disposition: form-data; name="map"

{ "0": ["variables.files.0"], "1": ["variables.files.1"] } --------------------------ec62457de6331cad Content-Disposition: form-data; name="0"; filename="b.txt" Content-Type: text/plain

Bravo file content.

--------------------------ec62457de6331cad Content-Disposition: form-data; name="1"; filename="c.txt" Content-Type: text/plain

Charlie file content.

--------------------------ec62457de6331cad--

Batching

Operations

;[
  {
    query: `
      mutation($file: Upload!) {
        singleUpload(file: $file) {
          id
        }
      }
    `,
    variables: {
      file: File // a.txt
    }
  },
  {
    query: `
      mutation($files: [Upload!]!) {
        multipleUpload(files: $files) {
          id
        }
      }
    `,
    variables: {
      files: [
        File, // b.txt
        File // c.txt
      ]
    }
  }
]

cURL request

curl localhost:3001/graphql \
  -F operations='[{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }, { "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }]' \
  -F map='{ "0": ["0.variables.file"], "1": ["1.variables.files.0"], "2": ["1.variables.files.1"] }' \
  -F [email protected] \
  -F [email protected] \
  -F [email protected]

Request payload

--------------------------627436eaefdbc285
Content-Disposition: form-data; name="operations"

[{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }, { "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }] --------------------------627436eaefdbc285 Content-Disposition: form-data; name="map"

{ "0": ["0.variables.file"], "1": ["1.variables.files.0"], "2": ["1.variables.files.1"] } --------------------------627436eaefdbc285 Content-Disposition: form-data; name="0"; filename="a.txt" Content-Type: text/plain

Alpha file content.

--------------------------627436eaefdbc285 Content-Disposition: form-data; name="1"; filename="b.txt" Content-Type: text/plain

Bravo file content.

--------------------------627436eaefdbc285 Content-Disposition: form-data; name="2"; filename="c.txt" Content-Type: text/plain

Charlie file content.

--------------------------627436eaefdbc285--

Implementations

Pull requests adding either experimental or mature implementations to these lists are welcome!

Client

Server

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.