How to Upload a file to GraphQL with VanillaJS
Tags: graphql, javascript, html
Published: 2022-08-01 17:54:00

File upload is a feature that is very easy to implement in rest API but when it comes to sending a file to a GraphQL server, there is no standard way to go about it.

In this article you will learn the widely accepted method of implementing file upload which is becoming increasingly popular in new apps. The technique follows the specification by [@jaydenseric](https://github.com/jaydenseric/graphql-multipart-request-spec). 

It works similar to REST and is intuitive to use. Most examples that you will find online will use react, making use of the [graphql-upload-client](https://www.npmjs.com/apollo-upload-client) to implement file upload to a GraphQL server. In this post, I will not be using any dependency client. I will upload a file to a GraphQL server by making use of Vanilla javascript. Hopefully this article helps you to determine what the graphql-upload-client library is doing under the hood.

I will describe how to upload a file to a GraphQL server by making use of the following server dependencies
- express
- apollo-server-express
- graphql-upload

These are the only dependencies that we will be using. We will run our application in node js with the dependencies above.

- **express** is a server that we use to serve our application to the web.
- **apollo-server-express** will add the graphql capability to our application
- **graphql-upload** is a schema type for the uploaded file in graphql

## Let's Get Started
We will begin by setting up our server to receive graphql upload requests.

Firstly, make sure you have node installed on your system, then create a new empty directory and initialize the nodejs application by typing the following into the command prompt/terminal

```
npm init -y
```
This will create a *package.json* file which serves as your nodejs application config file.

Next, install the following server dependencies

```
npm install express, apollo-server-express, graphql-upload
```

This will install express,apollo-server-express and graphql-upload dependencies into a new folder called node_modules, the package.json file will also be updated with a new entry for the installed dependencies.

Next, create a new file called *app.js*. In this file, you will enter the following to import the dependencies that you installed above

```javascript
require("dotenv").config();
const { ApolloServer } = require("apollo-server-express");
const { graphqlUploadExpress } = require('graphql-upload');
const express = require('express');
const schema = require('./schema');

async function startServer() {
    const server = new ApolloServer(schema);
    await server.start();
    const app = express();
    // This middleware should be added before calling `applyMiddleware`.
    app.use(graphqlUploadExpress());
    server.applyMiddleware({ app });
    app.use(express.static('public'));

    app.listen({ port: process.env.PORT }, () => {
        console.log("SERVER LISTENING ON PORT " + process.env.PORT);
    });
}
startServer();
```

The *require("dotenv").config()* is used to load the environment variables from the .env file.

We import the *apollo-server-express* and the *graphql-upload* into app.js. We also require a file called */schema*. This will load the schema/index.js, which will be used to define the schema for our graphQL application.

The folder 📂 structure will look like this
![Image](https://sharetxt.live/uploads/images/graphql-file-upload-folder-structure)



In the schema/index.js file, you fetch the data from schema.graphql using the built in node js *fs* dependency as shown below 
```javascript
const { typeDefs } = require('./typeDefs');
const resolvers = require("./resolvers");

module.exports = {
    typeDefs,
    resolvers
};
```
Here we load the type definitions and resolvers from their respective folders.

Here the schema for our graphQL application defined in schema/typeDefs/schema.graphql
```javascript
scalar Upload

type File {
  url: String!
}

type Query {
  hello: String!
}

type Mutation {
  singleUpload(file: Upload!): File!
}
```
The query is not really important for file upload, but is required by graphql so we leave it there.

The upload scalar is the type of our uploaded file.

From the above, you can see that we have one query called hello and a single mutation called singleUpload which we will use to upload our file to the graphql server. The response from this mutation is a URL path to the uploaded image.

Here is *schema/typeDefs/index.js*
```javascript
const { gql } = require("apollo-server-express");
const { join } = require('path');
const fs = require('fs');

const file = fs.readFileSync(join(__dirname, './schema.graphql'), 'utf8');

const typeDefs = gql `${file}`;

module.exports = { typeDefs };
`; 

module.exports = typeDefs;
```
Here, we simply load the schema and return it.

In resolvers/index.js, we have the following
```javascript
const path = require('path');
const fs = require('fs');
const { GraphQLUpload } = require('graphql-upload');

module.exports = {
    Upload: GraphQLUpload,
    Query: {
        hello: () => "Hello World!"
    },
    Mutation: {
        singleUpload: async(parent, { file }) => {
            const { createReadStream, filename, mimetype, encoding } = await file;
            const stream = createReadStream();
            const pathname = path.join(__dirname, `../../public/images/${filename}`);
            let out = fs.createWriteStream(pathname);
            await stream.pipe(out);

            return {
                url: `http://localhost:4000/images/${filename}`
            };
        },
    }
};
```javascript
Here, is where we handle the file upload. We restructure the file and store the image in our local machine in the public directory of our application.

That's basically it for our backend.

## The front end
The front end will be a very simple HTML and javascript app with a file upload functionality. We have a simple upload input which will enable you to select an image file from your local file system and upload it to the graphql server that we have just created.

### Let's get started.

Here is the HTML and Javascript for the front end. We have placed them together in the same file
*index.html*
```javascript
< html >

< body >
    < form method="post" >
        < input id="file" type="file" name="upload" / >

        < input type="submit" / >
    < / form >
< / body >

< / html >

< script >
    window.onload = () => {
        let file = document.getElementById("file");
        file.onchange = async(event) => {
            let file = event.target.files[0];
            const formData = new FormData();
            formData.append("operations", `{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { url } }", "variables": { "file": null } }`);
            formData.append("map", `{ "0": ["variables.file"] }`);
            formData.append("0", file);
            for (var value of formData.values()) {
                console.log(value);
            }

            try {
                let res = await fetch("http://localhost:4000/graphql", {
                    method: 'POST',
                    body: formData
                });

                let json = res.json();

                console.log(json);
            } catch (err) {
                console.error(err);
            }
        }
    };
< / script >
```
This is a very simple front end.

The mutation that sends the uploaded file is as shown below
```javascript
mutation ($file: Upload!) { 
	singleUpload(file: $file) { 
		URL 
	} 
}
```

With the following variables
```javascript
{ 
	"0": ["variables.file"] 
}
```

We submit the data to the graphql server with a formData which we create by appending the mutation in the *operations* and the variables in the *map*. You can upload multiple images in the same way, by incrementing the index of the variables from 0,1,2 and so on.

We first listen for an unchanged event on the file input which is triggered after selecting the file to be uploaded. We get the binary file and append it to the form that we created dynamically, we then send the form to your graphql server just like we would with a normal rest upload. 

Our server will process the request and determine what mutation should handle the request based on the value that we passed to it.

## Wrapping up
File upload to a graphql server can be very easily implemented with the **apollo-server-express** dependency provided by the apollo. You can also implement it with the apollo server.

In this tutorial, we have used the apollo-server-express, because it enables us to access the uploaded image from the public directory.