Skip to main content

Relay Uploadables

Sending normal GraphQL mutation is trivial:

commitMutation(graphql`
mutation TestMutation {
test(input: "test")
}
`);

It sends POST request with these headers:

Accept: application/json
Content-type: application/json
User-Agent: Mozilla/5.0

And with this request payload:

{
"query": "mutation TestMutation {\n test(input: \"test\")\n}\n",
"variables": {}
}

We can use the same POST request to send our files as well. To do so, you have to use uploadables from Relay and multipart/form-data content type. Mutation is similar:

commitMutation(
graphql`
mutation TestMutation {
test(input: "test")
}
`,
{
uploadables: {
file1: new File(['foo'], 'foo.txt', {
type: 'text/plain',
}),
file2: new File(['bar'], 'bar.txt', {
type: 'text/plain',
}),
},
},
);

With these headers:

Accept: */*
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryshXxygBlT4ATOyhW
User-Agent: Mozilla/5.0

And with these form data:

------WebKitFormBoundaryshXxygBlT4ATOyhW
Content-Disposition: form-data; name="query"

mutation TestMutation {
test(input: "test")
}

------WebKitFormBoundaryshXxygBlT4ATOyhW
Content-Disposition: form-data; name="variables"

{}
------WebKitFormBoundaryshXxygBlT4ATOyhW
Content-Disposition: form-data; name="file1"; filename="foo.txt"
Content-Type: text/plain

foo
------WebKitFormBoundaryshXxygBlT4ATOyhW
Content-Disposition: form-data; name="file2"; filename="bar.txt"
Content-Type: text/plain

bar
------WebKitFormBoundaryshXxygBlT4ATOyhW--

Please note - it's a good idea to create GraphQL type representing the file and send it in the query as well. Look at how Absinthe is doing it:

$ curl -X POST \\
-F query="{mutation uploadFile(users: \"users_csv\", metadata: \"metadata_json\")" \\
-F [email protected] \\
-F [email protected] \\
localhost:4000/graphql

They send the actual files and query as multipart/form-data as well but they require the file name (with underscore) in the query and it will fail if these two things do not match. Simple and elegant.

By treating uploads as regular arguments we get all the usual GraphQL argument benefits (such as validation and documentation)---which we wouldn't get if we were merely putting them in the context as in other implementations.