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-yt2cd react-node-uploader-yt34git init # Optional56npx create-react-app client7cd 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'45import App from './App'67const root = ReactDOM.createRoot(document.getElementById('root'))89root.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'23import Uploader from './components/Uploader' // Does not exist for now45const App = () => {6 return (7 <div>8 <Heading fontSize='5xl' align='center' py={20}>9 Cules Uploader10 </Heading>1112 <Uploader />13 </div>14 )15}1617export 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'56const Uploader = () => {7 const [file, setFile] = useState({})89 const setFileState = data => setFile(p => ({ ...p, ...data }))1011 const handleSubmit = async () => {12 try {13 const { base64, height, width } = file1415 const url = 'http://localhost:8000/upload'1617 const { data } = await axios.post(url, {18 src: base64,19 height,20 width,21 })2223 console.log(data)24 } catch (error) {25 console.log(error.response.data.message)26 }27 }2829 const onDrop = acceptedFiles => {30 const fileObject = acceptedFiles[0]3132 const preview = URL.createObjectURL(fileObject)33 setFileState({ fileObject, preview })34 // Do something with the files3536 const image = new Image()3738 image.src = preview3940 image.onload = () =>41 setFileState({42 width: image.width,43 height: image.height,44 })4546 const reader = new FileReader()4748 reader.onabort = () => console.log('file reading was aborted')4950 reader.onerror = () => console.log('file reading has failed')5152 reader.readAsDataURL(fileObject)5354 reader.onload = () => setFileState({ base64: reader.result })55 }5657 const { getRootProps, getInputProps, isDragActive } = useDropzone({58 onDrop,59 maxFiles: 1,60 accept: {61 'image/*': ['.png', '.gif', '.jpeg', '.jpg'],62 },63 })6465 useEffect(() => () => URL.revokeObjectURL(file.preview), [file.preview])6667 return (68 <Box m='0 auto' maxW='50rem' w='80%'>69 {file.preview ? (70 <ChakraImage src={file.preview} alt='' w='100%' />71 ) : (72 <Box73 display='grid'74 placeItems='center'75 minH='15rem'76 border='1px dashed black'77 {...getRootProps()}78 >79 <input {...getInputProps()} />80 <Text>81 {isDragActive82 ? 'Release to drop the files here'83 : "Drag 'n' drop some files here, or click to select files"}84 </Text>85 </Box>86 )}8788 <Box89 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}101102export 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 withURL.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.
- First, we are getting the actual image file from the
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 thebase64
image to our backend server.
Build backend server
Let's create a new directory at the root and initialize a package.json file.
1mkdir server2cd server3npm 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'67config()89const app = fastify({10 logger: true,11 bodyLimit: 1024 * 1024 * 10,12})1314app.register(fastifyCors)15app.register(fastifyFormBody)1617cloudinary.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})2223const PORT = process.env.PORT || 80002425app.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.body45 const folder = '/cules-uploader/'67 const imageConfig = {8 width,9 height,10 folder,11 crop: 'fit',12 quality: 80,13 }1415 const uploadRes = await cloudinary.uploader.upload(src, imageConfig)1617 return { success: true, data: uploadRes }18 } catch (error) {19 const message = error.message2021 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
- Email: thatanjan@gmail.com
- LinkedIn: @thatanjan
- Portfolio: anjan
- Github: @thatanjan
- Instagram : @thatanjan
- Twitter: @thatanjan
Blogs you might want to read:
- Eslint, prettier setup with TypeScript and react
- What is Client-Side Rendering?
- What is Server Side Rendering?
- Everything you need to know about tree data structure
- 13 reasons why you should use Nextjs
- Beginners guide to quantum computers
Videos might you might want to watch: