node.jsgraphqlapollo-clientapollo-serverkeystonejs

How can one upload an image to a KeystoneJS GraphQL endpoint?


I'm using TinyMCE in a custom field for the KeystoneJS AdminUI, which is a React app. I'd like to upload images from the React front to the KeystoneJS GraphQL back. I can upload the images using a REST endpoint I added to the Keystone server -- passing TinyMCE an images_upload_handler callback -- but I'd like to take advantage of Keystone's already-built GraphQL endpoint for an Image list/type I've created.

enter image description here

I first tried to use the approach detailed in this article, using axios to upload the image

const getGQL = (theFile) => {
    const query = gql`
  mutation upload($file: Upload!) {
    createImage(file: $file) {
      id
      file {
        path
        filename
      }
    }
  }
`;
// 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',
        url: 'http://localhost:4545/admin/api',
        body
    };
// @ts-ignore
    return axios(opts);

};

but I'm not sure what to pass as theFile -- TinyMCE's images_upload_handler, from which I need to call the image upload, accepts a blobInfo object which contains functions to give me

enter image description here

The file name doesn't work, neither does the blob -- both give me server errors 500 -- the error message isn't more specific.

I would prefer to use a GraphQL client to upload the image -- another SO article suggests using apollo-upload-client. However, I'm operating within the KeystoneJS environment, and Apollo-upload-client says

Apollo Client can only have 1 “terminating” Apollo Link that sends the GraphQL requests; if one such as apollo-link-http is already setup, remove it.

I believe Keystone has already set up Apollo-link-http (it comes up multiple times on search), so I don't think I can use Apollo-upload-client.


Solution

  • The UploadLink is just a drop-in replacement for HttpLink. There's no reason you shouldn't be able to use it. There's a demo KeystoneJS app here that shows the Apollo Client configuration, including using createUploadLink.

    Actual usage of the mutation with the Upload scalar is shown here.

    Looking at the source code, you should be able to use a custom image handler and call blob on the provided blobInfo object. Something like this:

    tinymce.init({
      images_upload_handler: async function (blobInfo, success, failure) {
        const image = blobInfo.blob()
        try {
          await apolloClient.mutate(
            gql` mutation($image: Upload!) { ... } `,
            {
              variables: { image }
            } 
          )
          success()
        } catch (e) {
          failure(e)
        }
      }
    })