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.
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 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
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
In the schema/index.js file, you fetch the data from schema.graphql using the built in node js fs dependency as shown below
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
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
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
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
< 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
mutation ($file: Upload!) {
singleUpload(file: $file) {
URL
}
}
With the following variables
{
"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.
Top comments (0)