Upload files using GraphQL
Did you know that you can upload files using GraphQL without any third-party libraries? Intrigued? Read on…
What is a file anyway?
The key to understanding how to upload files using GraphQL is to understand what a file is. A file is a collection of bits stored on a disk. When you upload a file to a server, you are essentially sending a collection of those bits to the server. The server then stores these bytes on its disk.
The contents of a file might look like this:
// hello-world.txt
01001000 01100101 01101100 01101100 01101111 00100000 01110111 01101111 01110010 01101100 01100100
What you see above is a series of bits that represent the word “Hello world”. When you upload a file, you are essentially sending a series of bits like the one above to the server. Normally, when you upload a file using a form, the browser sends the file to the server in a binary format. But we could just as well encode those bits in any format we like, such as base64 or even base2 and send them to the server as an ASCII string.
Uploading files using GraphQL
So let’s assume that you would like to send the following file to the server:
// hello-world.txt
Hello World
First, we will read this file byte by byte and convert each bit to its ASCII representation. We will convert this to ASCII using the toString(2) javascript function.
Example Breakdown:
Original number 65 → binary: 01000001. (ASCII for ‘A’)
After calling toString(2) → “1000001” (string).
In memory, this string looks like:
00110001 (for ‘1’)
00110000 (for ‘0’)
00110000 (for ‘0’)
00110000 (for ‘0’)
00110000 (for ‘0’)
00110000 (for ‘0’)
00110001 (for ‘1’)
Now, each character of the string “1000001” is taking up 1 byte because ASCII characters are always 1 byte each. This does mean that the file size will increase by 8 times when we convert it to ASCII. You could use base64 encoding to reduce the size of the file, but for the sake of simplicity, we will use ASCII encoding.
Example
Frontend
function binaryToBitsString(file: ArrayBuffer): string {
let bitsString = ''
const bytes = new Uint8Array(file)
// Convert each byte to its corresponding "1"s and "0"s string
for (const byte of bytes) {
bitsString += byte.toString(2).padStart(8, '0') // Pad to ensure 8 bits
}
return bitsString
}
async function uploadFile(file: File) {
const arrayBuffer = await file.arrayBuffer()
const bitsString = binaryToBitsString(arrayBuffer)
// You can now send `bitsString` via GraphQL mutation
const result = await sendToGraphQL(bitsString)
return result
}
async function sendToGraphQL(bitsString: string) {
// Your GraphQL mutation logic here
// Assuming you have an existing GraphQL setup
const mutation = `
mutation UploadBinary($binary: String!) {
uploadBinary(binary: $binary) {
success
}
}
`
return await fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: mutation,
variables: { binary: bitsString }
})
})
}
Backend
// Function to convert the ASCII string of "1"s and "0"s back into binary data
function bitsStringToBinary($bitsString) {
$binaryData = ''; // This will hold the binary data
// Step 1: Loop through the string in chunks of 8 bits (1 byte)
for ($i = 0; $i < strlen($bitsString); $i += 8) {
// Step 2: Take a group of 8 characters (1 byte)
$byteString = substr($bitsString, $i, 8);
// Step 3: Convert the binary string (e.g., "01000001") back to an integer (base 2)
$binaryData .= chr(bindec($byteString)); // chr() converts the number back to a character byte
}
return $binaryData;
}
// Example usage:
$asciiBitsString = "0100000101000010"; // This is an ASCII string of 1s and 0s
// Convert it back to binary data
$binaryFileData = bitsStringToBinary($asciiBitsString);
// Save the binary data to a file
file_put_contents('output_file.bin', $binaryFileData);