Upload images from React and Node app to Cloudinary by Cules Coding

Upload images from React and Node app to Cloudinary

In this tutorial, we will learn how to upload images from a React and Node app to Cloudinary.

Requirements

Before you follow this tutorial, you need to know a few things:

  • JavaScript
  • React
  • Node.js

You don't need to be advanced in them but basic knowledge is required.

It will be great if you know about the followings:

  • Fastify(Node framework)
  • Chakara UI(React UI framework)

But you can learn about them from my channel.

Setup

First, we are going to work on the react app.

1mkdir react-node-uploader-yt
2cd react-node-uploader-yt
3
4git init # Optional
5
6npx create-react-app client
7cd client

Install necessary packages:

1npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion react-dropzone axios
  • First packages are all related to Chakra UI.
  • react-dropzone: A react library for drag and drop file upload.
  • Axios: A library for making HTTP requests.

Setup Chakara UI

Open up the src/index.js file and surround the App component with the ChakraProvider component.

1import React from 'react'
2import ReactDOM from 'react-dom/client'
3import { ChakraProvider } from '@chakra-ui/react'
4
5import App from './App'
6
7const root = ReactDOM.createRoot(document.getElementById('root'))
8
9root.render(
10 <React.StrictMode>
11 <ChakraProvider>
12 <App />
13 </ChakraProvider>
14 </React.StrictMode>
15)

Add the following code to the src/App.js file:

1import { Heading } from '@chakra-ui/react'
2
3import Uploader from './components/Uploader' // Does not exist for now
4
5const App = () => {
6 return (
7 <div>
8 <Heading fontSize='5xl' align='center' py={20}>
9 Cules Uploader
10 </Heading>
11
12 <Uploader />
13 </div>
14 )
15}
16
17export default App

Create Uploader Component

Create a new component called Uploader.

1import React, { useState, useEffect } from 'react'
2import { Box, Text, Image as ChakraImage, Button } from '@chakra-ui/react'
3import { useDropzone } from 'react-dropzone'
4import axios from 'axios'
5
6const Uploader = () => {
7 const [file, setFile] = useState({})
8
9 const setFileState = data => setFile(p => ({ ...p, ...data }))
10
11 const handleSubmit = async () => {
12 try {
13 const { base64, height, width } = file
14
15 const url = 'http://localhost:8000/upload'
16
17 const { data } = await axios.post(url, {
18 src: base64,
19 height,
20 width,
21 })
22
23 console.log(data)
24 } catch (error) {
25 console.log(error.response.data.message)
26 }
27 }
28
29 const onDrop = acceptedFiles => {
30 const fileObject = acceptedFiles[0]
31
32 const preview = URL.createObjectURL(fileObject)
33 setFileState({ fileObject, preview })
34 // Do something with the files
35
36 const image = new Image()
37
38 image.src = preview
39
40 image.onload = () =>
41 setFileState({
42 width: image.width,
43 height: image.height,
44 })
45
46 const reader = new FileReader()
47
48 reader.onabort = () => console.log('file reading was aborted')
49
50 reader.onerror = () => console.log('file reading has failed')
51
52 reader.readAsDataURL(fileObject)
53
54 reader.onload = () => setFileState({ base64: reader.result })
55 }
56
57 const { getRootProps, getInputProps, isDragActive } = useDropzone({
58 onDrop,
59 maxFiles: 1,
60 accept: {
61 'image/*': ['.png', '.gif', '.jpeg', '.jpg'],
62 },
63 })
64
65 useEffect(() => () => URL.revokeObjectURL(file.preview), [file.preview])
66
67 return (
68 <Box m='0 auto' maxW='50rem' w='80%'>
69 {file.preview ? (
70 <ChakraImage src={file.preview} alt='' w='100%' />
71 ) : (
72 <Box
73 display='grid'
74 placeItems='center'
75 minH='15rem'
76 border='1px dashed black'
77 {...getRootProps()}
78 >
79 <input {...getInputProps()} />
80 <Text>
81 {isDragActive
82 ? 'Release to drop the files here'
83 : "Drag 'n' drop some files here, or click to select files"}
84 </Text>
85 </Box>
86 )}
87
88 <Box
89 sx={{
90 display: 'flex',
91 justifyContent: 'space-around',
92 mt: '1rem',
93 }}
94 >
95 <Button onClick={() => setFile({})}>Reset</Button>
96 <Button onClick={handleSubmit}>Submit</Button>
97 </Box>
98 </Box>
99 )
100}
101
102export default Uploader

Explanation:

  • The file state is used to store the file object.

  • Use the useDropzone hook to create a dropzone component. Spread the necessary props to the <input> and its parent element.

  • The onDrop function is used to handle the drop event.

    • First, we are getting the actual image file from the files array. Then we create a preview link with URL.createObjectURL and set the state with the file object and preview link.
    • We then create an image object and set the source to the preview link. We are doing this to get the width and height of the image.
    • Finally, we will convert our image to base64 string with FileReader. base64 string will be sent to the node server. It will be stored inside Cloudinary.
    • All image data will be stored inside the file state.
  • The JSX is pretty simple. We create a container as the root and an input element inside that. The dropzone props will be spread out inside the components.

  • Last two buttons are for resetting the state and Submitting the image to our backend server. The submit button is hooked up with the handleSubmit function.

  • handleSubmit function will send the base64 image to our backend server.

Build backend server

Let's create a new directory at the root and initialize a package.json file.

1mkdir server
2cd server
3npm init -y

Install packages

For dependencies

1npm i @fastify/cors @fastify/formbody cloudinary dotenv fastify

Explanation:

  • fastify: Fastify core package.
  • @fastify/cors: Handling cors.
  • @fastify/formbody: Handling Form URL encoded request body.

For devDependencies

1npm i concurrently nodemon --save-dev

Explanation:

  • concurrently: Handles multiple scripts with the same command.
  • nodemon: Restart the dev server when files are changed.

Add new scripts to package.json:

1{
2 "scripts": {
3 "dev": "nodemon ./src/index.js",
4 "client-dev": "npm --prefix ../client/ run start",
5 "both-dev": "concurrently \"npm run client-dev\" \"npm run dev\""
6 }
7}

Add environment variables

1CLOUDINARY_CLOUD_NAME=<your key>
2CLOUDINARY_API_KEY=<your key>
3CLOUDINARY_API_SECRET=<your key>

You can find your keys from your Cloudinary dashboard.

Setup basic node server

Create a new index.js file

1touch index.js

Add the following code. You can see it is pretty similar to Express.

1import fastify from 'fastify'
2import fastifyCors from '@fastify/cors'
3import fastifyFormBody from '@fastify/formbody'
4import { config } from 'dotenv'
5import { v2 as cloudinary } from 'cloudinary'
6
7config()
8
9const app = fastify({
10 logger: true,
11 bodyLimit: 1024 * 1024 * 10,
12})
13
14app.register(fastifyCors)
15app.register(fastifyFormBody)
16
17cloudinary.config({
18 cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
19 api_key: process.env.CLOUDINARY_API_KEY,
20 api_secret: process.env.CLOUDINARY_API_SECRET,
21})
22
23const PORT = process.env.PORT || 8000
24
25app.listen({ port: PORT }).catch(err => app.log.error(err))

Explanation:

  • First, we create a fastify app. We set a body limit of 10MB.
  • We also register the fastify plugins.
  • Then we configure the cloudinary with the api keys.
  • Finally, we start the server with app.listen.

Create upload route

1app.post('/upload', async (req, reply) => {
2 try {
3 const { src, height, width } = req.body
4
5 const folder = '/cules-uploader/'
6
7 const imageConfig = {
8 width,
9 height,
10 folder,
11 crop: 'fit',
12 quality: 80,
13 }
14
15 const uploadRes = await cloudinary.uploader.upload(src, imageConfig)
16
17 return { success: true, data: uploadRes }
18 } catch (error) {
19 const message = error.message
20
21 return reply.status(400).send({ success: false, message })
22 }
23})

Explanation:

  • First, extract the properties from the request body.
  • We need to specify a folder path where we want to store the image.
  • Create an image config object where we need to specify all the information about the image. You can find the available options here
  • Finally, we use the upload method to upload the image. And if everything goes well then we will send a successful response.

That's it our app is done. Now you will be able to upload images from your react app.

Shameless Plug

I have made an Xbox landing page clone with React and Styled components. I hope you will enjoy it. Please consider like this video and subscribe to my channel.

That's it for this blog. I have tried to explain things simply. If you get stuck, you can ask me questions.

Contacts

Blogs you might want to read:

Videos might you might want to watch:

Previous PostHow to setup and deploy a fullStack(mern) application on Vercel and Render
Next PostWhat is Static Site Generation?