Go Lang + Vue.js + Mysql CRUD example - Go Full stack development
Hello everyone, today we will learn how to build a full-stack application that is a basic User Management Application using Go Lang, Mysql, and Vue.js.GitHub repository link is provided at the end of this tutorial. You can download the source code.
After completing this tutorial what we will build?
We will build a full-stack web application that is a basic User Management Application with CRUD features:
• Create User
• List User
• Update User
• Delete User
Following is the screenshot of our application -
-Fetch all Users:
-Add a User:
-Update User:
We divided this tutorial into two parts
PART 1 - Rest APIs Development using Go and Mysql
PART 2 - UI development using Vue.js
PART 1 - Rest APIs Development using Go Language
These are APIs that the Go Language application will export:
- GET all User's : /users
- GET User by ID : /users/{_id}
- POST User : /users
- PUT User : /users/{_id}
- DELETE User : /users/{_id}
Backend Project Structure:
Create database 'userdb':
CREATE DATABASE userdb;
Create table 'users':
CREATE TABLE `users` (
`id` bigint(20) UNSIGNED NOT NULL,
`first_name` varchar(200) NOT NULL,
`last_name` varchar(200) NOT NULL,
`email` varchar(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `users`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `id` (`id`);
ALTER TABLE `users`
MODIFY `id` bigint(20) UNSIGNED NOT NULL
AUTO_INCREMENT, AUTO_INCREMENT=11;
Initialize the Go project:
Initialize the Go project using the following command
go mod init backend
Adding the modules required for the project:
go get github.com/gorilla/mux
go get github.com/go-sql-driver/mysql
main.go
package main
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
func main() {
Routers()
}
func Routers() {
InitDB()
defer db.Close()
router := mux.NewRouter()
router.HandleFunc("/users",
GetUsers).Methods("GET")
router.HandleFunc("/users",
CreateUser).Methods("POST")
router.HandleFunc("/users/{id}",
GetUser).Methods("GET")
router.HandleFunc("/users/{id}",
UpdateUser).Methods("PUT")
router.HandleFunc("/users/{id}",
DeleteUser).Methods("DELETE")
http.ListenAndServe(":9080",
&CORSRouterDecorator{router})
}
/***************************************************/
//Get all users
func GetUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var users []User
result, err := db.Query("SELECT id, first_name," +
"last_name,email from users")
if err != nil {
panic(err.Error())
}
defer result.Close()
for result.Next() {
var user User
err := result.Scan(&user.ID, &user.FirstName,
&user.LastName, &user.Email)
if err != nil {
panic(err.Error())
}
users = append(users, user)
}
json.NewEncoder(w).Encode(users)
}
//Create user
func CreateUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
stmt, err := db.Prepare("INSERT INTO users(first_name," +
"last_name,email) VALUES(?,?,?)")
if err != nil {
panic(err.Error())
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err.Error())
}
keyVal := make(map[string]string)
json.Unmarshal(body, &keyVal)
first_name := keyVal["firstName"]
last_name := keyVal["lastName"]
email := keyVal["email"]
_, err = stmt.Exec(first_name, last_name, email)
if err != nil {
panic(err.Error())
}
fmt.Fprintf(w, "New user was created")
}
//Get user by ID
func GetUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
result, err := db.Query("SELECT id, first_name,"+
"last_name,email from users WHERE id = ?", params["id"])
if err != nil {
panic(err.Error())
}
defer result.Close()
var user User
for result.Next() {
err := result.Scan(&user.ID, &user.FirstName,
&user.LastName, &user.Email)
if err != nil {
panic(err.Error())
}
}
json.NewEncoder(w).Encode(user)
}
//Update user
func UpdateUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
stmt, err := db.Prepare("UPDATE users SET first_name = ?," +
"last_name= ?, email=? WHERE id = ?")
if err != nil {
panic(err.Error())
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err.Error())
}
keyVal := make(map[string]string)
json.Unmarshal(body, &keyVal)
first_name := keyVal["firstName"]
last_name := keyVal["lastName"]
email := keyVal["email"]
_, err = stmt.Exec(first_name, last_name, email,
params["id"])
if err != nil {
panic(err.Error())
}
fmt.Fprintf(w, "User with ID = %s was updated",
params["id"])
}
//Delete User
func DeleteUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
stmt, err := db.Prepare("DELETE FROM users WHERE id = ?")
if err != nil {
panic(err.Error())
}
_, err = stmt.Exec(params["id"])
if err != nil {
panic(err.Error())
}
fmt.Fprintf(w, "User with ID = %s was deleted",
params["id"])
}
/***************************************************/
type User struct {
ID string `json:"id"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Email string `json:"email"`
}
//Db configuration
var db *sql.DB
var err error
func InitDB() {
db, err = sql.Open("mysql",
"root:@tcp(127.0.0.1:3306)/userdb")
if err != nil {
panic(err.Error())
}
}
/***************************************************/
// CORSRouterDecorator applies CORS headers to a mux.Router
type CORSRouterDecorator struct {
R *mux.Router
}
func (c *CORSRouterDecorator) ServeHTTP(rw http.ResponseWriter,
req *http.Request) {
if origin := req.Header.Get("Origin"); origin != "" {
rw.Header().Set("Access-Control-Allow-Origin", origin)
rw.Header().Set("Access-Control-Allow-Methods",
"POST, GET, OPTIONS, PUT, DELETE")
rw.Header().Set("Access-Control-Allow-Headers",
"Accept, Accept-Language,"+
" Content-Type, YourOwnHeader")
}
// Stop here if its Preflighted OPTIONS request
if req.Method == "OPTIONS" {
return
}
c.R.ServeHTTP(rw, req)
}
Run:
go run main.go
A package.json is a JSON file that subsists at the root of a Javascript/Node project. It holds metadata pertinent to the project and is utilized for managing the project's dependencies, scripts, version, and a whole lot more.
{
"name": "frontend-vuejs",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.18.0",
"vue": "^2.6.6",
"vue-router": "^3.0.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.5.0",
"@vue/cli-plugin-eslint": "^3.5.0",
"@vue/cli-service": "^3.5.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.5.21"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
Components
Vue Components are one of the important features of VueJS that creates custom elements, which can be reused in HTML
User.vue
<template>
<div>
<h3>User</h3>
<div class="container">
<form @submit="validateAndSubmit">
<div v-if="errors.length">
<div
class="alert alert-danger"
v-bind:key="index"
v-for="(error, index) in errors"
>
{{ error }}
</div>
</div>
<fieldset class="form-group">
<label>First Name</label>
<input type="text" class="form-control"
v-model="firstName" />
</fieldset>
<fieldset class="form-group">
<label>Last Name</label>
<input type="text" class="form-control"
v-model="lastName" />
</fieldset>
<fieldset class="form-group">
<label>Email</label>
<input type="text" class="form-control"
v-model="email" />
</fieldset>
<button class="btn btn-success"
type="submit">Save</button>
</form>
</div>
</div>
</template>
<script>
import UserDataService from "../service/UserDataService";
export default {
name: "User",
data() {
return {
firstName: "",
lastName: "",
email: "",
errors: [],
};
},
computed: {
id() {
return this.$route.params.id;
},
},
methods: {
refreshUserDetails() {
UserDataService.retrieveUser(this.id).then((res) => {
this.firstName = res.data.firstName;
this.lastName = res.data.lastName;
this.email = res.data.email;
});
},
validateAndSubmit(e) {
e.preventDefault();
this.errors = [];
if (!this.firstName) {
this.errors.push("Enter valid values");
} else if (this.firstName.length < 4) {
this.errors.push
("Enter atleast 4 characters in First Name");
}
if (!this.lastName) {
this.errors.push("Enter valid values");
} else if (this.lastName.length < 4) {
this.errors.push
("Enter atleast 4 characters in Last Name");
}
if (this.errors.length === 0) {
if (this.id == -1) {
UserDataService.createUser({
firstName: this.firstName,
lastName: this.lastName,
email: this.email,
}).then(() => {
this.$router.push("/users");
});
} else {
UserDataService.updateUser(this.id, {
id: this.id,
firstName: this.firstName,
lastName: this.lastName,
email: this.email,
}).then(() => {
this.$router.push("/users");
});
}
}
},
},
created() {
this.refreshUserDetails();
},
};
</script>
Users.vue
<template>
<div class="container">
<h3>All Users</h3>
<div v-if="message" class="alert alert-success">
{{ this.message }}</div>
<div class="container">
<table class="table">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" v-bind:key="user.id">
<td>{{ user.firstName }}</td>
<td>{{ user.lastName }}</td>
<td>{{ user.email }}</td>
<td>
<button class="btn btn-warning"
v-on:click="updateUser(user.id)">
Update
</button>
</td>
<td>
<button class="btn btn-danger"
v-on:click="deleteUser(user.id)">
Delete
</button>
</td>
</tr>
</tbody>
</table>
<div class="row">
<button class="btn btn-success"
v-on:click="addUser()">Add</button>
</div>
</div>
</div>
</template>
<script>
import UserDataService from "../service/UserDataService";
export default {
name: "Users",
data() {
return {
users: [],
message: "",
};
},
methods: {
refreshUsers() {
UserDataService.retrieveAllUsers().then((res) => {
this.users = res.data;
});
},
addUser() {
this.$router.push(`/user/-1`);
},
updateUser(id) {
this.$router.push(`/user/${id}`);
},
deleteUser(id) {
UserDataService.deleteUser(id).then(() => {
this.refreshUsers();
});
},
},
created() {
this.refreshUsers();
},
};
</script>
UserDataService.js
import axios from 'axios'
const USER_API_URL = 'http://localhost:9080'
class UserDataService {
retrieveAllUsers() {
return axios.get(`${USER_API_URL}/users`);
}
retrieveUser(id) {
return axios.get(`${USER_API_URL}/users/${id}`);
}
deleteUser(id) {
return axios.delete(`${USER_API_URL}/users/${id}`);
}
updateUser(id, user) {
return axios.put(`${USER_API_URL}/users/${id}`, user);
}
createUser(user) {
return axios.post(`${USER_API_URL}/users`, user);
}
}
export default new UserDataService()
App.vue
<template>
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">
Go Lang + Mysql + Vue.js CRUD Application
</a><br/><br/>
</div>
<router-view/>
</div>
</template>
<script>
export default {
name: "app"
};
</script>
<style>
@import
url(https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css);
</style>
routes.js
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
const router = new Router({
mode: 'history',
routes: [
{
path: "/",
name: "Users",
component: () => import("./components/Users"),
},
{
path: "/users",
name: "Users",
component: () => import("./components/Users"),
},
{
path: "/user/:id",
name: "User",
component: () => import("./components/User"),
},
]
});
export default router;
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './routes';
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>golang-vuejs-mysql</title>
</head>
<body>
<noscript>
<strong>We're sorry but golang-vuejs-mysql-
crud doesn't work properly without JavaScript
enabled. Please
enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
Frontend Local Setup
Step 1: The npm install installs all modules that are listed on package.json file and their
dependencies
npm install
Step 2: Run the Frontend application
npm run serve
App running at:
- Local:http://localhost:8080/