POST Images to a Drupal 8 Server ft. JSON:API, axios, and arrayBuffer

Uploading files with JS to a drupal backend

JSON:API - Nodes, Files, and Users

This post will assume you have experience setting up JSON:API and the Web services Module for Drupal 8. Interacting with a headless Drupal API is easy and quite fun, with a little practice. I'm going to go through the flow, assuming the user has logged in, received a JSON web Token, and is ready to upload files to your drupal 8 backend.

Drupal groups it's content into several groups. Nodes, or simply basic content, and has type:node--{entity_type}. Think of Nodes as your Schema, the actual content your site will be built around, and main thing that is returned form database each time you make a request.

A user is simply any person that creates an account with drupal, and has type: user--user.

Files are any form of information (.png,.pdf,.jpg,etc.) uploaded and stored in the Backend. Files have type: file--file.

Uploading Something to Drupal Backend

The JSON:API has some presupposed routes, that can allow for easy manipulation of your content, as well as easy access from a decoupled frontend.

First lets understand the routes we will be accessing to talk to our Drupal Server.

There are two "Flows" we can choose from to upload an image with Drupal 8. Flow 1 is for when you have already created an entity and simply want to add an image

Flow 1 - Entity already exists

POST /jsonapi/node/article/{uuid}/field_image HTTP/1.1
Content-Type: application/octet-stream
Accept: application/vnd.api+json
Content-Disposition: file; filename="filename.jpg"

[… binary file data …]

Flow 2 - Entity does not yet exist

/** Step 1 Upload to field

POST /jsonapi/node/article/field_image HTTP/1.1
Content-Type: application/octet-stream
Accept: application/vnd.api+json
Content-Disposition: file; filename="filename.jpg"

[… binary file data …]

/** RES will contain picture {picture_uuid}
/** Step 2 - PATCH uuid to reference entity within your node or user resource

PATCH /jsonapi/node/article/{uuid}/relationships/field_image HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "file--file",
    "id": "{UUID of new file entity}",
    "meta": {
      "description": "The most fascinating image ever!"
    }
  }
}

Handling Binary File Data with React/Axios

The part that may confuse many is how to handle uploading [Binary File data]. Binary File data is an array, that chops up your image and sends it in slices to your server. The way we will send our slices of data, is through a JS method called an ArrayBuffer. Using the FileReader Module, we can slice our image into a stream and send it on its way in an Array to our backend.

Below I will execute some boilerplate that will help us safely send our files from the web browser to the server.

First we will need some markup and state to grab our user's file

import React from 'react'

export const FileUploader = () =>{

const [fileUpload, setFileUpload] = useState(null);
const [name, setName] = useState('');

const handleChange = (e) => {
  const acceptedTypes = ['image/png','image/jpg']
  let file = e.target.files[0]
  if (acceptedTypes.indexOf(file.type) == -1){
    alert("improper file type: must be .png or .jpg")
  } else {
   // keep track of file in state
     setFileUpload(file)
     setName(file.name)
  }

}
const handleSubmit = (e) => {
  e.preventDefault()

}
return(

<form onSubmit={handleSubmit}>
<input 
 type='file' 
 onChange={handleChange}
 id='myFile' 
 name='myFile'/>
<input type="submit"/>
</form>

);
}

Great! With a little bit of React State management, we are able to grab our file from the user. Next we need to place some kind of flow inside the handleSubmit() function to send our file properly. Since our header Content-Type will be set to application/octet-stream, we cannot use form-data to send our file. Instead we will send it as an Array Buffer.

//inside of the handleSubmit function

let myFile = fileUpload // from react state
let myFileName = name // file name from react state

//example of uri for axios request

// example header object
const headers = {
 "Content-Type": "application/octet-stream",
 "Accept": "application/vnd.api+json",
 "Content-Disposition": `file; filename="${myFileName}"`,
 "X-Crsf-Token" : `${session_token}`,
 "Authorization":`Bearer ${token}`
}

// File Reader Class
let reader = new FileReader();

//Convert our file into Array Buffer
reader.readAsArrayBuffer(myFile);

reader.onabort = () =>{console.log("file reader aborted")}
reader.onerror = () =>{console.log('error with file reader')}

//Call axios within the onload event 
reader.onload = () =>{
   const arrayBuffer = reader.result; //our array buffer

   axios.post(uri, arrayBuffer, {headers})
     .then(result=>{
        // Do as you please with the result {uuid} of image
        console.log(result)})
     .catch(error=>{console.log(error)})
}

Now we have a seamless way to access our newly uploaded photo via it's {uuid}. In the case of Flow 1, we are finished and do not have to request any more data. In Flow 2, simply take the uuid of your new file entity, and perform a patch request to the desired node or user resource of your choosing.

Citations

Allow creation of file entities from binary data. Drupal.org. (2019, January 18). https://www.drupal.org/node/3024331.

FileReader - Web APIs: MDN. Web APIs | MDN. (n.d.). https://developer.mozilla.org/en-US/docs/Web/API/FileReader.

SukottoSukotto 14144 bronze badges, taggartJtaggartJ 1, & Patrick KennyPatrick Kenny 22.6k1717 gold badges114114 silver badges264264 bronze badges. (1966, December 1). How do you upload an image via jsonapi? Drupal Answers. https://drupal.stackexchange.com/questions/262374/how-do-you-upload-an-image-via-jsonapi/262400#262400.

76