How to upload files with Golang and Ajax?
Gustavo Castillo
Posted on August 16, 2017
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
- I'm not handling validation (you should do it), but it should work like a charm.
- I'm using my
GOPATH
root, please change according to yours. - Keep learning and happy code :).
Posted on August 16, 2017
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.