Working with React Dropzone and GraphCMS Uploads

Upload files to GraphCMS, and store them as assets using React Dropzone.

Jamie Barton
Jamie Barton
React Dropzone + GraphCMS

Quite often you'll want to store user submitted content with GraphCMS, and we've made this very easy for anything that's related to content, but what about file uploads? Every GraphCMS project has an API endpoint for uploading files directly, or by remote URL that will be stored as Assets within your GraphCMS project.

Simply append /upload to your project API endpoint, and upload by file, or remote URL:

https://[region].graphcms.com/v2/[projectId]/[environment]/upload

It's also best to create a dedicated Permanent Auth Token for uploads.

⚠️ Using this endpoint with a Permanent Auth Token will expose that to anyone uploading files, so we’ll need to create a proxy to pass on the file to GraphCMS.

If you want to upload files from one service to another, in a safe environment (server context, e.g. server endpoint) then you can use form-data (FormData API) package to do this easily:

const form = new FormData();
form.append('fileUpload', fs.createReadStream('path/to/file.png'));
fetch(`${process.env.GRAPHCMS_URL}/upload`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GRAPHCMS_ASSET_TOKEN}`,
},
body: form,
});

Alternatively, you can use fetch to upload via a remote URL of an asset hosted elsewhere:

const fetch = require('node-fetch');
fetch(`${process.env.GRAPHCMS_URL}/upload`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GRAPHCMS_ASSET_TOKEN}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `url=${encodeURIComponent(
'https://media.graphcms.com/P3TkBzxyQLupgDWNFydB'
)}`,
});

Client side file uploadsAnchor

While these work for assets you control, or an asset already hosted in the previous example, what if you wanted to allow your user to upload a file by drag 'n' drop?

React Dropzone is a popular library that will help us with this.

I'll assume you have a React frontend setup, Create React App makes it really easy if you haven't already got something going.

Let's take a look at the "Quickstart" provided by the React Dropzone docs to get going:

import React, { useCallback } from 'react'
import { useDropzone } from 'react-dropzone'
function MyDropzone() {
const onDrop = useCallback(acceptedFiles => {
// Do something with the files
}, [])
const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop})
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
{
isDragActive ?
<p>Drop the files here ...</p> :
<p>Drag 'n' drop some files here, or click to select files</p>
}
</div>
)
}

We can take this code and adjust it to work with GraphCMS in a few lines.

Inside of the onDrop function, let's invoke FormData, and fetch to make this happen.

const onDrop = useCallback(
(acceptedFiles) => {
setFiles(
acceptedFiles.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file)
})
)
);
const form = new FormData();
form.append("fileUpload", acceptedFiles[0]);
fetch(
"/api/upload",
{
method: "POST",
body: form
}
);
},
[setFiles]
);

You can also restrict the type of accepted files by specifying accept to the useDropzone hook. For example, to accept images, use:

const { getRootProps, getInputProps, isDragActive } = useDropzone({
accept: "image/*",
onDrop
});

You'll notice above we're using setFiles, which is state we'll store with React.useState. Update your React imports to include useState, and useEffect:

import React, { useCallback, useState, useEffect } from "react";

Then inside of your MyDropzone component, add:

const [files, setFiles] = useState([]);

Next you'll want to invoke useEffect inside of your MyDropzone component:

useEffect(
() => () => {
files.forEach((file) => URL.revokeObjectURL(file.preview));
},
[files]
);

Then wherever you want to show a preview of your files, you can do so using the files from useState:

{files.map((file, index) => (
<div key={file.name}>
<img
src={file.preview}
style={{ width: "100px", height: "100px" }}
alt=""
/>
</div>
))}

We're sending the file to /api/upload which we'll need to create next.

Here we're using express, form-data, node-fetch, and multer to handle the uploads. You can use whatever framework/language you want to create this proxy, but here's a quickstart example:

const express = require('express');
const FormData = require('form-data');
const fetch = require('node-fetch');
const multer = require('multer')();
const app = express();
app.post('/upload', multer.single('fileUpload'), (req, res, next) => {
const fileUpload = req.file;
if (!fileUpload) {
const error = new Error('No file attached');
error.status = 400;
return next(error);
}
const form = new FormData();
form.append('fileUpload', fileUpload.buffer, fileUpload.originalname);
fetch(`${process.env.GRAPHCMS_ENDPOINT}/upload`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GRAPHCMS_TOKEN}`,
},
body: form,
})
.then((response) => response.json())
.then((data) => res.send(data))
.catch((err) => res.send({ message: 'Something went wrong' }));
});
app.listen(4000, () => {
console.log('Running on port 4000');
});

You'll notice here we're passing the fileUpload onto GRAPHCMS_ENDPOINT/upload. You'll want to set the values as an environment variable so the server can read these. If you're working in development, you can use dotenv to read from .env automatically.

Create React App makes proxying requests to a backend API easy with a simple update to package.json.

Inside of package.json add "proxy": "http://localhost:4000". You'll want to update the port 4000 if you made a change inside of the Express server above.

All that's left to do is run your frontend app, and backend server.

You can get the code on GitHub and work with this locally.

It's Easy To Get Started

GraphCMS plans are flexibly suited to accommodate your growth. Get started for free, or request a demo to discuss larger projects with more complex needs