How to build Web App with Go + gin-gonic + Vue
Tags: go, webpack, vue
Published: 2020-01-13 00:39:47

---
title: How to build Web App with Go + gin-gonic + Vue
published: true
description: 
tags: go,webpack,vue
---

This article describes how to build and develop Web application with Go + [gin-gonic](https://github.com/gin-gonic/gin) (Web Application Framework in Go) + Vue.

*(Edited)* Sample code repository: https://github.com/m-mizutani/web-app-go

## Steps

### Configure webpack

At first, install yarn packages

```bash
$ yarn init
$ yarn add -D @babel/cli @babel/core @babel/preset-env babel-loader webpack webpack-cli webpack-dev-server html-webpack-plugin vue-loader vue-template-compiler css-loader vue-style-loader sass-loader
$ yarn add babel-polyfill vue  node-sass axios
```

After that, create `webpack.config.js` file.

```js
const path = require('path');
const VueLoaderPlugin = require("vue-loader/lib/plugin");

module.exports = {
  mode: "development",
  entry: ["babel-polyfill", path.resolve("src", "js", "index.js")],
  output: {
    filename: "bundle.js",
    path: path.join(__dirname, "static/js/"),
    publicPath: "/js"
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: "vue-loader"
      },
      {
        test: /\.js$/,
        loader: "babel-loader"
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          'vue-style-loader',
          // Translates CSS into CommonJS
          'css-loader',
          // Compiles Sass to CSS
          'sass-loader',
        ],
      }
    ]
  },
  resolve: {
    extensions: [".js", "json", "jsx", "vue"],
    alias: {
      vue$: "vue/dist/vue.esm.js"
    }
  },
  devServer: {
    contentBase: "static",
    proxy: {
      "/api": "http://localhost:9080"
    }
  },
  plugins: [new VueLoaderPlugin()]
};
```

Additoinaly, put following setting into `package.json`.

```json
  "scripts": {
    "start": "webpack-dev-server",
    "build": "webpack --optimize-minimize"
  },
```

### Add web assets

Required following files:

- `src/css/main.scss`
- `src/js/index.js`
- `src/js/app.vue`
- `static/index.html`

**main.scss**

```scss
body {
  font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
}
```

**index.js**

```js
import '../css/main.scss'

import _ from 'lodash';
import "babel-polyfill";
import Vue from "vue";

import App from "./app.vue";

new Vue({
    el: "#app",
    render: h => h(App)
});
```

**app.vue**

```vue
<template>
  <div>
    <div>MyApp</div>
    <button v-on:click="showMessage">This is test</button>
    <div>{{message}}</div>
  </div>
</template>
<script>
import axios from "axios";
const appData = {
  message: ""
};
export default {
  data() {
    return appData;
  },
  methods: {
    showMessage: showMessage
  }
};
function showMessage() {
  axios.get("/api/v1/hello").then(res => {
    console.log(res);
    appData.message = res.data.message;
  });
}
</script>
<style>
</style>
```

**index.html**

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>MyApp</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="/js/bundle.js"></script>
  </body>
</html>
```

### Add go module

Initialize go module.

```sh
$ go mod init github.com/m-mizutani/web-app-go
```

After that, add main.go.

```go
package main

import (
	"fmt"
	"os"

	"github.com/gin-contrib/static"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
)

var logger = logrus.New()

var logLevelMap = map[string]logrus.Level{
	"trace": logrus.TraceLevel,
	"debug": logrus.DebugLevel,
	"info":  logrus.InfoLevel,
	"warn":  logrus.WarnLevel,
	"error": logrus.ErrorLevel,
}

type arguments struct {
	LogLevel       string
	BindAddress    string
	BindPort       int
	StaticContents string
}

func runServer(args arguments) error {
	level, ok := logLevelMap[args.LogLevel]
	if !ok {
		return fmt.Errorf("Invalid log level: %s", args.LogLevel)
	}
	logger.SetLevel(level)
	logger.SetFormatter(&logrus.JSONFormatter{})

	logger.WithFields(logrus.Fields{
		"args": args,
	}).Info("Given options")

	r := gin.Default()

	r.Use(static.Serve("/", static.LocalFile(args.StaticContents, false)))
	r.GET("/api/v1/hello", func(c *gin.Context) {
		c.String(200, `{"message":"hello, hello, hello"}`)
	})

	if err := r.Run(fmt.Sprintf("%s:%d", args.BindAddress, args.BindPort)); err != nil {
		return err
	}

	return nil
}

func main() {
	args := arguments{
		LogLevel: "info",
		BindAddress: "0.0.0.0",
		BindPort: 9080,
		StaticContents: "./static",
        }

	if err := runServer(args); err != nil {
		logger.WithError(err).Fatal("Server exits with error")
	}
}
```

## Development

### Run go server

```
$ go run .
```

[air](https://github.com/cosmtrek/air) command with `.air.conf` like following is recommended for hot reloading.

```toml
[build]
include_ext = ["go", "tpl", "tmpl", "html"]
exclude_dir = ["src", "tmp", "node_modules"]
delay = 1000 # ms
stop_on_error = true
log = "air_errors.log"
```

And run `air`

```
$ air -c .air.conf
```

### Start webpack development server

```
$ npm run start
```

### Open browser

Open http://localhost:8080 to confirm Web UI.

## Deploy

```
$ npm run build
$ go build -o sercer
$ cp -r server static /path/to/server
```