How to upload files with Golang and Ajax?

gcdcoder

Gustavo Castillo

Posted on August 16, 2017

How to upload files with Golang and Ajax?

Uploading file(s) is a common functionality that we want to use in our websites, it could be for user's profile avatar, an image gallery, a cover post and so on, but What if you want to do it by using Ajax. Let's see how I solved this problem.

First of all let me give you the project structure:

public\
    api.js
    app.js
    index.html
    style.css
controllers\
    file.go
files\

Then let's create the index.html file inside public folder and write the following code:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, 
    user-scalable=no, initial-scale=1.0, maximum-scale=1.0,
minimum-scale=1.0">
    <link rel="stylesheet" 
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="styles.css">
    <title>File upload using Ajax</title>
</head>
<body>
    <form action="/upload" method="post" enctype="multipart/form-data" class="uploadForm">
        <input class="uploadForm__input" type="file" name="file" id="inputFile" accept="image/*">
        <label class="uploadForm__label" for="inputFile">
            <i class="fa fa-upload uploadForm__icon"></i> Select a file
        </label>
    </form>
    <div class="notification" id="alert"></div>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="api.js"></script>
    <script src="app.js"></script>
</body>
</html>

Let's add some basic CSS styles and create the styles.css file inside public folder:


body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100vh;    
    font-family: Verdana, Geneva, Tahoma, sans-serif;
}

body,
.uploadForm,
.uploadForm__label {
    display: flex;
    align-items: center;
    justify-content: center;
}

.uploadForm {
    width: 600px;
    max-width: 600px;
    height: 300px;
    flex-direction: column;    
    border: 2px dashed gray;
    font-family: inherit;
}

.uploadForm__input {
    display: none;
}

.uploadForm__label {
    border: 1px solid gray;
    padding: .5em 2em;
    color: deepskyblue;
    transition: transform .4s;
    flex-direction: column;
}

.uploadForm__label:hover {
    cursor: pointer;
    transform: scale(1.01);
    box-shadow: grey 2px 2px 10px;
}

.uploadForm__icon {
    font-size: 1.8em;
}

.notification {    
    display: none;    
}

.success,
.error {
    right: 30px;
    z-index: 10;
    width: 300px;
    bottom: 40px;
    padding: 1em;
    height: auto;
    text-align: center;
    display: block;
    position: fixed;
    font-family: inherit;
    animation: alert .8s forwards;
}

.notification.success {
    background: #D9EDF7;
    color: #31709C;
}

.notification.error {
    background: #F2DEDE;
    color: #B24842;
}

@keyframes alert {
    0% {
        opacity: 0;
        bottom: -40px;
    }

    100% {
        opacity: 1;
        bottom: 40px;
    }
}

Next let's create the app.js file inside public folder too and write the following code:

(function (d, axios) {
    "use strict";
    var inputFile = d.querySelector("#inputFile");
    var divNotification = d.querySelector("#alert");

    inputFile.addEventListener("change", addFile);

    function addFile(e) {
        var file = e.target.files[0]
        if(!file){
            return
        }
        upload(file);
    }

    function upload(file) {
        var formData = new FormData()
        formData.append("file", file)
        post("/upload", formData)
            .then(onResponse)
            .catch(onResponse);        
    }

    function onResponse(response) {
        var className = (response.status !== 400) ? "success" : "error";
        divNotification.innerHTML = response.data;
        divNotification.classList.add(className);
        setTimeout(function() {
            divNotification.classList.remove(className);
        }, 3000);
    }
})(document, axios)

Finally we need to create api.js file one more time inside public folder and write this code:

"use strict";

function post(url, data) {
    return axios.post(url, data)
        .then(function (response) {
            return response;
        }).catch(function (error) {
            return error.response;
        });
}

Well, after all this let's finally write some Go code. I'm going to start by creating the file.go file inside controllers folder and write the following:

package controllers

import (
    "fmt"
    "io/ioutil"
    "mime/multipart"
    "net/http"
)

// UploadFile uploads a file to the server
func UploadFile(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }

    file, handle, err := r.FormFile("file")
    if err != nil {
        fmt.Fprintf(w, "%v", err)
        return
    }
    defer file.Close()

    mimeType := handle.Header.Get("Content-Type")
    switch mimeType {
    case "image/jpeg":
        saveFile(w, file, handle)
    case "image/png":
        saveFile(w, file, handle)
    default:
        jsonResponse(w, http.StatusBadRequest, "The format file is not valid.")
    }
}

func saveFile(w http.ResponseWriter, file multipart.File, handle *multipart.FileHeader) {
    data, err := ioutil.ReadAll(file)
    if err != nil {
        fmt.Fprintf(w, "%v", err)
        return
    }

    err = ioutil.WriteFile("./files/"+handle.Filename, data, 0666)
    if err != nil {
        fmt.Fprintf(w, "%v", err)
        return
    }
    jsonResponse(w, http.StatusCreated, "File uploaded successfully!.")
}

func jsonResponse(w http.ResponseWriter, code int, message string) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    fmt.Fprint(w, message)
}

As our final step let's create the main.go file in your root directory and use the file package like so:

package main

import (
    "log"
    "net/http"

        // Note this is my path according to my GOPATH, chage it according to yours.
    "bitbucket.org/gustavocd/upload-img/controllers"
)

func main() {
    http.Handle("/", http.FileServer(http.Dir("./public")))
    http.HandleFunc("/upload", controllers.UploadFile)
    log.Println("Running")
    http.ListenAndServe(":8080", nil)
}

Notes

  1. I'm not handling validation (you should do it), but it should work like a charm.
  2. I'm using my GOPATH root, please change according to yours.
  3. Keep learning and happy code :).
💖 💪 🙅 🚩
gcdcoder
Gustavo Castillo

Posted on August 16, 2017

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related