Quite often you'll want to store user submitted content with Hygraph, and we've made this very easy for anything that's related to content, but what about file uploads? Every Hygraph project has an API endpoint for uploading files directly, or by remote URL that will be stored as Assets within your Hygraph project.
Simply append /upload
to your project API endpoint, and upload by file, or remote URL:
https://[region].hygraph.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 Hygraph.
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.HYGRAPH_URL}/upload`, {method: 'POST',headers: {Authorization: `Bearer ${process.env.HYGRAPH_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.HYGRAPH_URL}/upload`, {method: 'POST',headers: {Authorization: `Bearer ${process.env.HYGRAPH_ASSET_TOKEN}`,'Content-Type': 'application/x-www-form-urlencoded',},body: `url=${encodeURIComponent('https://eu-central-1-shared-euc1-02.graphassets.com/AvHQ3RDvFSousA8iwElOKz/P3TkBzxyQLupgDWNFydB')}`,});
#Client side file uploads
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 Hygraph 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}><imgsrc={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.HYGRAPH_ENDPOINT}/upload`, {method: 'POST',headers: {Authorization: `Bearer ${process.env.HYGRAPH_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 HYGRAPH_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.
Blog Author
Jamie Barton
Jamie is a software engineer turned developer advocate. Born and bred in North East England, he loves learning and teaching others through video and written tutorials. Jamie currently publishes Weekly GraphQL Screencasts.