GitHub – jaydenseric/graphql-multipart-request-spec: A spec for GraphQL multipart form requests (file uploads).

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

--------------------------cec8e8123c05ba25Content-Disposition: form-data; name="operations"{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }--------------------------cec8e8123c05ba25Content-Disposition: form-data; name="map"{ "0": ["variables.file"] }--------------------------cec8e8123c05ba25Content-Disposition: form-data; name="0"; filename="a.txt"Content-Type: text/plainAlpha 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

--------------------------ec62457de6331cadContent-Disposition: form-data; name="operations"{ "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }--------------------------ec62457de6331cadContent-Disposition: form-data; name="map"{ "0": ["variables.files.0"], "1": ["variables.files.1"] }--------------------------ec62457de6331cadContent-Disposition: form-data; name="0"; filename="b.txt"Content-Type: text/plainBravo file content.--------------------------ec62457de6331cadContent-Disposition: form-data; name="1"; filename="c.txt"Content-Type: text/plainCharlie 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

--------------------------627436eaefdbc285Content-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] } }]--------------------------627436eaefdbc285Content-Disposition: form-data; name="map"{ "0": ["0.variables.file"], "1": ["1.variables.files.0"], "2": ["1.variables.files.1"] }--------------------------627436eaefdbc285Content-Disposition: form-data; name="0"; filename="a.txt"Content-Type: text/plainAlpha file content.--------------------------627436eaefdbc285Content-Disposition: form-data; name="1"; filename="b.txt"Content-Type: text/plainBravo file content.--------------------------627436eaefdbc285Content-Disposition: form-data; name="2"; filename="c.txt"Content-Type: text/plainCharlie file content.--------------------------627436eaefdbc285--

Implementations

Pull requests adding either experimental or mature implementations to these lists are welcome! Strikethrough means the project was renamed, deprecated, or no longer supports this spec out of the box (but might via an optional integration).

Client

Server

Xổ số miền Bắc