avatar

File uploads with Graphql and Cloudinary

Arthur Yeti

Arthur Yeti / April 11, 2020

3 min read––– vues

How many times you needed a way to upload files in your sideprojects? Me, every single time. But, I always found it difficult to implement it with graphQL.

BTW, you don't want to upload the files on your own server, when there are great services like Cloudinary. They take care of everything bro.

The client-side is the simplest part

I assume you already have a React + Apollo app going on. We're going to create a simple React component to pick a file and trigger a graphQL mutation with @apollo/react-hooks.

import React, { useState } from 'react';
import { useMutation } from '@apollo/react-hooks';
import gql from 'graphql-tag';

const UPLOAD_AVATAR = gql`
  mutation uploadAvatar($avatar: Upload) {
    uploadAvatar(avatar: $avatar) {
      id
    }
  }
`;

const UploadAvatar = ({ url }) => {
  const [uploadAvatarMutation] = useMutation(UPLOAD_AVATAR);
  const [avatar, setAvatar] = useState(null);

  // Store in the state the file
  const handleChange = (e) => {
    setAvatar(e.target.files[0]);
  };

  // Trigger the mutation when we click the submit button
  const handleClick = () => {
    uploadAvatarMutation({
      variables: {
        avatar
      }
    });
  };

  return (
    <div>
      <input id="logo" type="file" onChange={handleChange} />
      <button type="button" onClick={handleClick}>
        Submit
      </button>
    </div>
  );
};

export default UploadAvatar;

There is no fancy concept to grasp from this component. It's a graphql mutation with a simple file input.

Server is the serious stuff

I suggest you're using apollo-express-server, but you can also plug the graphql-upload scalar in any graphql server.

Here is my tiny Apollo server.

import { ApolloServer, gql } from 'apollo-server-express';

const typeDefs = gql`
  type Avatar {
    id: ID!
    url: String
  }

  type Mutation {
    uploadAvatar(avatar: Upload): Avatar!
  }
`;

const resolvers = {
  Mutation: {
    uploadAvatar: (parent, args, context, info) => {
      const file = await uploadFile(args.avatar);
      return saveToDatabase(file.secure_url);
    },
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen()

And this is the function that does the magic:

import cloudinary from 'cloudinary';

// A simple function to upload to Cloudinary
const uploadFile = async (file) => {
  // The Upload scalar return a a promise
  const { createReadStream } = await file;
  const fileStream = createReadStream();

  // Initiate Cloudinary with your credentials
  cloudinary.v2.config({
    cloud_name: 'CLOUDINARY_CLOUD_NAME',
    api_key: 'CLOUDINARY_API_KEY',
    api_secret: 'CLOUDINARY_API_SECRET'
  });

  // Return the Cloudinary object when it's all good
  return new Promise((resolve, reject) => {
    const cloudStream = cloudinary.v2.uploader.upload_stream(function (
      err,
      fileUploaded
    ) {
      // In case something hit the fan
      if (err) {
        rejet(err);
      }

      // All good :smile:
      resolve(fileUploaded);
    });

    fileStream.pipe(cloudStream);
  });
};

If you have any questions, I'm on twitter :smile:

Inscrit toi à la newsletter !

Reçois des emails à propos de Next.js/Hasura, ma vie de maker, et mon lifestyle à Bali.