Home > Enterprise >  How to upload files from a backend (Heroku) to frontend in (Netlify) hosted on github
How to upload files from a backend (Heroku) to frontend in (Netlify) hosted on github

Time:03-24

I've developed an app that's uploaded to Github and I'm using Heroku to host the (Backend folder) from Github using (automatic deployment) and also using Netlify to host the (Frontend folder) and it's working great in my local computer, but when I try to upload files from my form in frontend it sends a request to the backend and the backend it self saves the file to /uploads folder that's located in frontend directory.

My file structure is like this:

[Server]
- controllers
- - food.js
[Client]
- public
-- uploads

- src
-- pages
--- dashboard
---- food
----- AddFood.js

it's working great on localhost, and this is my code: (client) AddFood.js:

import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import Axios from 'axios'

import useDocumentTitle from '../../../hooks/useDocumentTitle'

import Modal from '../../../components/Modal/Modal'
import { Success, Error, Loading } from '../../../components/Icons/Status'

import { createSlug } from '../../../functions/slug'
import goTo from '../../../functions/goTo'

const AddFood = () => {
  useDocumentTitle('Add Food')

  //Form States
  const [foodName, setFoodName] = useState('')
  const [foodPrice, setFoodPrice] = useState('')
  const [foodDesc, setFoodDesc] = useState('')

  const [foodFile, setFoodFile] = useState('')
  const [preview, setPreview] = useState()

  const [addFoodStatus, setAddFoodStatus] = useState()
  const [addFoodMessage, setAddFoodMessage] = useState()

  //Form errors messages
  const ImgErr = document.querySelector('[data-form-img-msg]')
  const foodNameErr = document.querySelector('[data-form-name-msg]')
  const priceErr = document.querySelector('[data-form-price-msg]')
  const descErr = document.querySelector('[data-form-desc-msg]')
  const formMsg = document.querySelector('[data-form-msg]')

  const modalLoading = document.querySelector('#modal')
  const BASE_URL =
    process.env.NODE_ENV === 'development'
      ? process.env.REACT_APP_API_LOCAL_URL
      : process.env.REACT_APP_API_URL

  const updateFoodImg = e => {
    const file = e.target.files[0]

    if (file) {
      const fileType = file.type.split('/')[0]
      if (fileType === 'image') setFoodFile(file)

      const fileSizeToMB = file.size / 1000000
      const MAX_FILE_SIZE = 1 //mb

      if (fileSizeToMB > MAX_FILE_SIZE) {
        if (ImgErr)
          ImgErr.textContent = `file size can't be more than ${MAX_FILE_SIZE} MB`
      } else {
        ImgErr.textContent = ''
      }
    }
  }

  useEffect(() => {
    // if there's an image
    if (foodFile) {
      const reader = new FileReader()

      reader.onloadend = () => setPreview(reader.result)

      reader.readAsDataURL(foodFile)
    } else {
      setPreview(null)
    }
  }, [foodFile])

  const handleAddFood = async e => {
    e.preventDefault()

    //using FormData to send constructed data
    const formData = new FormData()
    formData.append('foodName', foodName)
    formData.append('foodPrice', foodPrice)
    formData.append('foodDesc', foodDesc)
    formData.append('foodImg', foodFile)

    if (
      ImgErr.textContent === '' &&
      foodNameErr.textContent === '' &&
      priceErr.textContent === '' &&
      descErr.textContent === ''
    ) {
      //show waiting modal
      modalLoading.classList.remove('hidden')

      try {
        const response = await Axios.post(`${BASE_URL}/foods`, formData)

        const { foodAdded, message } = response.data
        setAddFoodStatus(foodAdded)
        setAddFoodMessage(message)
        //Remove waiting modal
        setTimeout(() => {
          modalLoading.classList.add('hidden')
        }, 300)
      } catch (err) {
        formMsg.textContent = `Sorry something went wrong ${err}`
      }
    } else {
      formMsg.textContent = 'please add all details'
    }
  }

  return (
    <>
      {addFoodStatus === 1 ? (
        <Modal
          status={Success}
          msg='Added food'
          redirectLink='menu'
          redirectTime='3000'
        />
      ) : addFoodStatus === 0 ? (
        <Modal
          status={Error}
          msg={addFoodMessage}
          msg=''
        />
      ) : null}

      <section className='py-12 my-8 dashboard'>
        <div className='container mx-auto'>
          <h3 className='mx-0 mt-4 mb-12 text-2xl text-center'>Add food</h3>
          <div>
            <div className='food'>
              {/* Show Modal Loading when submitting form */}
              <Modal
                status={Loading}
                modalHidden='hidden'
                classes='text-blue-500 text-center'
                msg='Please wait'
              />

              <form
                method='POST'
                className='form'
                encType='multipart/form-data'
                onSubmit={handleAddFood}
              >
                <label className='flex flex-wrap items-center justify-center gap-4 mb-8 sm:justify-between'>
                  <img
                    src={
                      preview === null
                        ? 'https://source.unsplash.com/random?food'
                        : preview
                    }
                    alt='food' //change with food image name
                    className='object-cover p-1 border border-gray-400 w-28 h-28 dark:border-gray-300 rounded-xl'
                  />
                  <input
                    type='file'
                    name='foodImg'
                    id='foodImg'
                    accept='image/*'
                    onChange={updateFoodImg}
                    className='grow-[.7] cursor-pointer text-lg text-white p-3 rounded-xl bg-orange-800 hover:bg-orange-700 transition-colors'
                    required
                  />
                  <span
                    className='inline-block my-2 text-red-400 font-[600]'
                    data-form-img-msg
                  ></span>
                </label>

                <label htmlFor='foodName' className='form-group'>
                  <input
                    type='text'
                    id='foodName'
                    className='form-input'
                    autoFocus
                    required
                    onChange={e => setFoodName(createSlug(e.target.value.trim()))}
                  />
                  <span className='form-label'>Food Name</span>
                  <span
                    className='inline-block my-2 text-red-400 font-[600]'
                    data-form-name-msg
                  ></span>
                </label>

                <label htmlFor='foodPrice' className='form-group'>
                  <input
                    type='number'
                    id='foodPrice'
                    className='form-input'
                    min='5'
                    max='500'
                    required
                    onChange={e => setFoodPrice(e.target.value.trim())}
                  />
                  <span className='form-label'>Price</span>
                  <span
                    className='inline-block my-2 text-red-400 font-[600]'
                    data-form-price-msg
                  ></span>
                </label>

                <label htmlFor='foodDescription' className='form-group'>
                  <textarea
                    name='foodDescription'
                    id='foodDescription'
                    minLength='10'
                    maxLength='300'
                    className='form-input'
                    required
                    onChange={e => setFoodDesc(e.target.value.trim())}
                  ></textarea>
                  <span className='form-label'>Description</span>
                  <span
                    className='inline-block my-2 text-red-400 font-[600]'
                    data-form-desc-msg
                  ></span>
                </label>

                <div
                  className='my-14 text-red-400 font-[600] text-center text-xl'
                  data-form-msg
                ></div>

                <div className='flex items-center justify-evenly'>
                  <button
                    type='submit'
                    className='min-w-[7rem] bg-green-600 hover:bg-green-700 text-white py-1.5 px-6 rounded-md'
                  >
                    Add
                  </button>
                  <Link
                    to={goTo('menu')}
                    className='text-gray-800 underline-hover text-bold dark:text-white'
                  >
                    Food Menu
                  </Link>
                </div>
              </form>
            </div>
          </div>
        </div>
      </section>
    </>
  )
}

export default AddFood

(server) foods.js:

const FoodsModel = require(`${__dirname}/../models/food-model.js`)
const { v4: uuidv4 } = require('uuid')
const sharp = require('sharp')
const deleteFile = require('../functions/deleteFile')

const addFood = async (req, res) => {
  const { foodName, foodPrice, foodDesc } = req.body
  const { foodImg } = req.files
  const foodImgName = uuidv4()   foodImg.name
  const foodImgMovePath = `${__dirname}/../../client/public/uploads/${foodImgName}.webp`
  const foodImgDisplayPath = `/uploads/${foodImgName}`

  const foods = new FoodsModel({
    foodImgDisplayPath,
    foodName,
    foodPrice,
    foodDesc
  })

  sharp(foodImg.data)
    .rotate()
    .resize(200)
    .jpeg({ mozjpeg: true, quality: 50 })
    .toBuffer()
    .then(newBuffer => {
      //changing the old jpg image buffer to new webp buffer
      foodImg.data = newBuffer

      foodImg.mv(foodImgMovePath, err => {
        if (err) {
          res.json({
            message: `Sorry something wrong with server!            
  • Related