Image upload using Golang and React

Golang is a blockbuster server side language in the field of efficiency and concurrency. If you are a Nodejs developer definitely you will come across express js for building your web api services. Gofiber is exactly like the express framework for golang and no doubt it booms with the efficiency of Fasthttp and golang.
In this blog post we will create a simple image upload server using gofiber and we will use reactjs for frontend to select image from file and upload to server.
we will use axios for http request to server and it is really awesome when we deal with implementing authentication and handling lots of api requests. It has lots of features which make life easy when dealing with api in react.
we will use chakra ui for designing material like button , images and layout it shins in Accessibility that directly effect better SEO.
library and tools we will use
  • golang
  • gofiber
  • reactjs
  • axios
  • chakra ui
  • Setup backend
    create new directory and enter into it
    mkdir go-react-image-upload
    
    cd go-react-image-upload
    create a new directory server inside go-react-image-upload and enter into it
    mkdir server 
    
    cd server
    Setup go environment
    go mod init github.com/harshmangalam
    install packages required for backend
    go get  github.com/gofiber/fiber/v2
    
    go get github.com/google/uuid
    uuid will help to generate unique id so that we can name our image easily and no two image will have same name.
    create new go file main.go inside server and start writting code
    package main
    
    import (
        "fmt"
        "log"
        "os"
        "strings"
    
        "github.com/gofiber/fiber/v2"
        "github.com/gofiber/fiber/v2/middleware/cors"
        "github.com/google/uuid"
    )
    
    func main() {
        // create new fiber instance  and use across whole app
        app := fiber.New()
    
        // middleware to allow all clients to communicate using http and allow cors
        app.Use(cors.New())
    
        // serve  images from images directory prefixed with /images
        // i.e http://localhost:4000/images/someimage.webp
    
        app.Static("/images", "./images")
    
        // handle image uploading using post request
    
        app.Post("/", handleFileupload)
    
        // delete uploaded image by providing unique image name
    
        app.Delete("/:imageName", handleDeleteImage)
    
        // start dev server on port 4000
    
        log.Fatal(app.Listen(":4000"))
    }
    
    
    
    func handleFileupload(c *fiber.Ctx) error {
    
        // parse incomming image file
    
        file, err := c.FormFile("image")
    
        if err != nil {
            log.Println("image upload error --> ", err)
            return c.JSON(fiber.Map{"status": 500, "message": "Server error", "data": nil})
    
        }
    
        // generate new uuid for image name 
        uniqueId := uuid.New()
    
        // remove "- from imageName"
    
        filename := strings.Replace(uniqueId.String(), "-", "", -1)
    
        // extract image extension from original file filename
    
        fileExt := strings.Split(file.Filename, ".")[1]
    
        // generate image from filename and extension
        image := fmt.Sprintf("%s.%s", filename, fileExt)
    
        // save image to ./images dir 
        err = c.SaveFile(file, fmt.Sprintf("./images/%s", image))
    
        if err != nil {
            log.Println("image save error --> ", err)
            return c.JSON(fiber.Map{"status": 500, "message": "Server error", "data": nil})
        }
    
        // generate image url to serve to client using CDN
    
        imageUrl := fmt.Sprintf("http://localhost:4000/images/%s", image)
    
        // create meta data and send to client
    
        data := map[string]interface{}{
    
            "imageName": image,
            "imageUrl":  imageUrl,
            "header":    file.Header,
            "size":      file.Size,
        }
    
        return c.JSON(fiber.Map{"status": 201, "message": "Image uploaded successfully", "data": data})
    }
    
    
    func handleDeleteImage(c *fiber.Ctx) error {
        // extract image name from params
        imageName := c.Params("imageName")
    
        // delete image from ./images
        err := os.Remove(fmt.Sprintf("./images/%s", imageName))
        if err != nil {
            log.Println(err)
            return c.JSON(fiber.Map{"status": 500, "message": "Server Error", "data": nil})
        }
    
        return c.JSON(fiber.Map{"status": 201, "message": "Image deleted successfully", "data": nil})
    }
    run main.go from server
    go run main.go
    Now our server is up and running we can test it using Postman
    setup frontend
    come outside from server directory and generate reactjs project using create-react-app
    npx create-react-app reactjs
    
    cd reactjs
    install dependencies
    npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4 axios
    index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    
    
    ReactDOM.render(
    
      <App />
      ,
      document.getElementById('root')
    );
    setup App.js
    import { Box, ChakraProvider, Container } from "@chakra-ui/react";
    import Axios from "axios";
    import Upload from "./components/Upload";
    
    Axios.defaults.baseURL = "http://localhost:4000";
    
    function App() {
      return (
        <ChakraProvider>
          <Box
            minH="100vh"
            w="100%"
            bg="gray.200"
            display="flex"
            alignItems="center"
            justifyContent="center"
          >
            <Container maxWidth="container.xl">
              <Upload />
            </Container>
          </Box>
        </ChakraProvider>
      );
    }
    
    export default App;
    create new hook useUpload hook in hooks folder
    hooks/useUpload.js
    import { useState } from "react";
    import axios from "axios";
    import { useToast } from "@chakra-ui/react";
    
    const useUpload = () => {
      const [image, setImage] = useState(null);
      const [loading, setLoading] = useState(false);
    
      const [uploadedImage, setUploadedImage] = useState(null);
    
      const toast = useToast();
    
      const handleChangeImage = (e) => {
        setImage(e.target.files[0]);
      };
    
      const handleUploadImage = async () => {
        try {
          setLoading(true);
          const formData = new FormData();
          formData.append("image", image);
          const res = await axios.post("/", formData);
          if (res.data.data) {
            console.log(res.data);
            setUploadedImage(res.data.data);
            toast({
              title: "Image Uploaded",
              description: res.data.message,
              status: "success",
              duration: 4000,
              isClosable: true,
            });
          }
        } catch (error) {
          console.log(error);
        } finally {
          setImage(null);
          setLoading(false);
        }
      };
    
      const handleRemoveImage = async () => {
        try {
          setLoading(true);
    
          const res = await axios.delete(`/${uploadedImage.imageName}`);
          if (res.data) {
            console.log(res.data);
            setUploadedImage(null);
            toast({
              title: "Image Deleted",
              description: res.data.message,
              status: "success",
              duration: 4000,
              isClosable: true,
            });
          }
        } catch (error) {
          console.log(error);
        } finally {
          setLoading(false);
        }
      };
      return {
        image,
        uploadedImage,
        loading,
        handleChangeImage,
        handleUploadImage,
        handleRemoveImage,
      };
    };
    
    export default useUpload;
    create Upload.js inside components folder
    components/Upload.js
    import { Button, Heading, VStack, Image, HStack, Tag } from "@chakra-ui/react";
    import React from "react";
    import { useRef } from "react";
    import useUpload from "../hooks/useUpload";
    
    function Upload() {
      const imageRef = useRef(null);
      const {
        loading,
        image,
    
        handleRemoveImage,
        handleChangeImage,
        handleUploadImage,
        uploadedImage,
      } = useUpload();
      return (
        <>
          <input
            style={{ display: "none" }}
            type="file"
            accept="image/*"
            ref={imageRef}
            onChange={handleChangeImage}
          />
          <VStack>
            <Heading>Image uploading using Golang and Reactjs</Heading>
            <Button
              onClick={() => imageRef.current.click()}
              colorScheme="blue"
              size="lg"
            >
              Select Image
            </Button>
          </VStack>
    
          {image && (
            <VStack my="4">
              <Image
                src={URL.createObjectURL(image)}
                width="300px"
                height="300px"
                alt="selected image..."
              />
              <Button
                onClick={handleUploadImage}
                variant="outline"
                colorScheme="green"
                isLoading={loading}
              >
                Upload
              </Button>
            </VStack>
          )}
    
          {uploadedImage && (
            <VStack my="4">
              <Image
                src={uploadedImage.imageUrl}
                width="300px"
                height="300px"
                alt={uploadedImage.imageName}
              />
    
              <HStack>
                <Tag variant="outline" colorScheme="blackAlpha">
                  ~ {Math.floor(uploadedImage.size / 1024)} Kb
                </Tag>
                <Button
                  variant="solid"
                  colorScheme="red"
                  onClick={handleRemoveImage}
                  isLoading={loading}
                >
                  Delete
                </Button>
              </HStack>
            </VStack>
          )}
        </>
      );
    }
    
    export default Upload;
    Github Repo
    Backend
    Frontend

    Getting Started with Create React App

    This project was bootstrapped with Create React App.

    Available Scripts

    In the project directory, you can run:

    npm start

    Runs the app in the development mode.
    Open http://localhost:3000 to view it in the browser.

    The page will reload if you make edits.
    You will also see any lint errors in the console.

    npm test

    Launches the test runner in the interactive watch mode.
    See the section about running tests for more information.

    npm run build

    Builds the app for production to the build folder.
    It correctly bundles React in production mode and optimizes the build for the best performance.

    The build is minified and the filenames include the hashes.
    Your app is ready to be deployed!

    See the section about deployment for more information.

    npm run eject

    Note: this is a one-way operation. Once you eject, you can’t go back!

    If you…

    43

    This website collects cookies to deliver better user experience

    Image upload using Golang and React