Notice that the above code contains an input
element that invokes a function called sendFile
when its value changes. That means when the user selects a file the sendFile
function will be invoked. The sendFile
function is defined in uploadFile.js
:
// A reference to the input element that will contain the file
const inputFile = document.getElementById('file-to-upload');
const results = document.getElementById('results');
// The function that is invoked when a user selects the file
async function sendFile() {
// Capture the first file from input element
const [theFile] = inputFile.files;
// The mutation that will be used to upload the file
const query = `
mutation upload($file: Upload!) {
submitAFile(file: $file) {
filename
mimetype
filesize
}
}
`;
// The operation contains the mutation itself as "query"
// and the variables that are associated with the arguments
// The file variable is null because we can only pass text
// in operation variables
const operation = {
query,
variables: {
file: null
}
};
// This map is used to associate the file saved in the body
// of the request under "0" with the operation variable "variables.file"
const map = {
'0': ['variables.file']
};
// This is the body of the request
// the FormData constructor builds a multipart/form-data request body
// Here we add the operation, map, and file to upload
const body = new FormData();
body.append('operations', JSON.stringify(operation));
body.append('map', JSON.stringify(map));
body.append(0, theFile);
// Create the options of our POST request
const opts = {
method: 'POST',
body
};
// Send the fetch request to the API
// Parse the response as json and obtain the resulting data
const { data } = await fetch('http://localhost:4000/graphql', opts).then(
res => res.json()
);
// Render the results of the submitAFIle mutation
// With plane old JavaScript
showStats(data.submitAFile);
}
// A function to reset the form so users can add another file
function reset() {
inputFile.style = ' display: block; ';
results.innerHTML = '';
}
// A function that displays the results on the page
function showStats({ filename, mimetype, filesize }) {
inputFile.style = ' display: none; ';
results.innerHTML = `
<p>
<b>name</b>: ${filename}
</p>
<p>
<b>type</b>: ${mimetype}
</p>
<p>
<b>size</b>: ${filesize} bytes
</p>
<button onclick="reset()">upload another file</button>
`;
}
The key to the above solution is the map
variable. The map variable is a JavaScript object that tells the server where to find any files associated with mutation variables. In this case, it is telling the server that there is a file saved under "0" in the request body that should map to the file
argument found in the variables. The body of the httpRequest contains the GraphQL operation, the map, and the file saved under "0".
You can see the full solution with both the Apollo Client and the Vanilla JavaScript client in this repository: How the Upload Scalar Works. No files are actually uploaded. We are using the upload stream to calculate the full file size only. However, this code could be easily modified to store the file in a location of our choosing.
We can't receive packages in our PO BOX. They are too large. A GraphQL server cannot receive files in variables
. The variables
that are associated with a query, mutation, or subscription, are parsed as JSON. Files are too large to parse and send this way. However, just like my post office has a workaround that allows me to still receive my package, the Apollo Server incorporates a workaround so that your resolvers can still receive files.
For more information on the Upload
type, check out Chapter 7 of our book, Learning GraphQL.