File Uploads, GraphQL and Apollo Federation

Gui Íscaro
ProFUSION Engineering
5 min readSep 9, 2020

With the rise of GraphQL, every day more and more APIs are written using this new technology in favor of REST APIs.

For those who have no idea what’s GraphQL, please stick with me. However if you are already familiar with it, please skip to this section.

What’s GraphQL?

GraphQL is a query language for an API and server-runtime for executing these queries. So when creating a new GraphQL service, one must explicitly define a schema that serves as a contract between the service itself and all its clients.

As an example, let’s imagine that we want to create an API, that one can use to obtain information about a user. The schema would look like this:

As we can see, it’s pretty much straight forward to create a schema that holds basic information about a user. You might be wondering what’s the Query and the Mutation type that we introduced.

The Mutation type is used to perform side-effects operation on the server-side, in our case, change the user’s name. For those coming from REST API, any operation that is made using PUT, POST or DELETE should be in a mutation.

The Query type is used strictly for fetching data, you can compare this type to any GET request to a REST API. This type should not cause side-effects on the server-side.

PRO-TIP: The “!” after the type name, means that null is not allowed. If, for some reason, the user query returns a null the server will yell at you.

With the schema defined a client can request the user’s information as well as changing their name. GraphQL queries resembles the schema itself and are very descriptive about what they are doing. For example, if one wants to obtain the information about the user, a query operation can be request to the server by sending the following structure:

The response would be the following:

The same idea can be applied when doing a mutation:

The response would be the following:

GraphQL is a complex topic and I’m not going to cover everything about it in this post, if you wish to learn more, please visit the official documentation. Also sandbox can be found in here, where you can play with the schema and do your own queries and mutations.

File Uploads

Now that we know a little bit about GraphQL, we can jump to the important question: How can I uploads files using my GraphQL API?

If you remember the old days when basically all APIs were based on REST we could simply use a multipart/form-data upload and move on. Something like this when using cURL:

$ curl -F ‘img=@/my/path/img.png’ http://my-api.com/photo

However this is not the case in GraphQL. If you remember the spec, GraphQL does not define the transport method — which means that my GraphQL API can be powered by smoke signals if needed 🌚.

Not having a standard way about how the data is transferred between a server and a client makes a little difficult to add to the spec how binary files should be transported, because it’s beyond its scope.

Fortunately the de facto standard for GraphQL is our good old friend HTTP and some folks even created a spec for it, which can be found in here.

The idea resembles what it was done with a REST API, the binary data will be sent using multipart/form-data along with the query/mutation request.

Going back to our user API, if we want to add mutation to add the user’s photo, it could be changed to look like this:

And to send a request one could do:

If you pay attention to the cURL request, you’ll see that we are doing a multipart/form-data request and instructing cURL that file is located at key 0 and it belongs to variables.photo. If more files were needed, this map value would grow to accommodate another file the request would look like:

Fortunately our friends at Apollo already supports basic file uploads by default on ApolloServer. On this link you can find the implementation and also send requests to it, directly on your computer. But remember to keep the sandbox open!

You can use the following test collection to play with:

Apollo Federation & File upload

Great, now that we know how file uploads works on GraphQL we can talk a little bit about Apollo Federation and its relation with uploads. In case you have no idea what Apollo federation is, please check this link.

Given the Apollo Federation architecture, all requests you make first hit the Apollo Gateway and then the gateway itself will decide to which micro-service the GraphQL request will be delivered to. I made some changes to our sandbox.

Now we have the following architecture.

Using this new architecture let’s try to upload a photo and see what happens:

As we can see the createReadStream() function is invalid, which means that our file was NOT received by the user micro service. Having this in mind, let’s inspect the gateway itself and see if it’s receiving the file. In order to do this, I created a RemoteGraphQLDataSource just for inspection.

If a run the file upload again I can see the following output on the server’s console:

Since the file is being sent the gateway itself, maybe there’s a way to send it to the user’s micro-service, right?

Enter @profusion/apollo-federation-upload

Here at Profusion we developed a npm package which handles file uploads with federation by simple adding a RemoteGraphQLDataSource to Apollo’s Gateway.

Its usage is quite simple, when creating a gateway you must do the following:

And that’s it!

This package supports arrays, inputs and simple uploads. Going back to our sandbox, let’s try to upload a file using this new package.

As we can see, the mutation works as expected and the image was correctly uploaded to the user micro-service.

The implementation of this package is quite simple and can be expressed with the following diagram (or you can check the code):

It’s important to note that this approach is not recommended in services that have a high amount of file uploads, since all files are uploaded to the gateway and then to the micro-service itself. One possible solution would be by letting the API user upload the file directly to an upload service that is not behind the gateway by using some kind of signed URL. I’ll cover this topic in the future with another post. In the mean time you can use our package 😬.

--

--