Skip to main content

Golang Gin Profile Page (Part 1)

· 9 min read
Adrianus Vian

screenshot screenshot

Di post ini saya akan memperlihatkan bagaimana saya membuat profile page milik saya menggunakan golang dan gin. Profile page ini akan dapat membaca konfigurasi dari sebuah file yaml dan menampilkan sebuah webpage dan sebuah markdown document berdasarkan key dan value yang di set di sebuah file yaml.

Yang dibutuhkan

  • Golang sudah terinstall
  • Code editor favorit kalian

Struktur projek

 my-project
├──  controller
│ ├──  common.go
│ ├──  config.go
│ ├──  index.go
│ └──  redirect.go
├──  models
│ ├──  config.go
│ └──  credly.go
├──  static
│ ├──  css
│ │ └──  index.css
│ └──  favicon.ico
├──  templates
│ ├──  index.html
│ └──  index.md
├──  cloudbuild.yaml
├──  config.yaml
├──  Dockerfile
├──  go.mod
├──  go.sum
├──  main.go
└──  README.md

Di akhir post ini kalian seharusnya mempunyai struktur projek yang sedikit mirip dengan ini.

Setup projek

Pertama kita buat dulu semua folder yang kita butuhkan untuk projek ini.

mkdir my-project; cd my-project
mkdir controller models static templates

Command ini akan membuat folder dengan nama my-project dan membuat semua folder yang kita butuhkan di dalam folder itu.

Routing applikasi

untuk melakukan routing yang dimana menjadi core dari aplikasi kita, kita deklarasikan routing aplikasi kita pada file main.go.

main.go
package main

import (
"github.com/cocatrip/cocatrip/controller"
"github.com/gin-gonic/gin"
)

func main() {
router := gin.Default()

router.Delims("{{", "}}")
router.LoadHTMLGlob("./templates/*.html")

router.StaticFile("/favicon.ico", "./static/favicon.ico")

router.Static("/css", "./static/css")

router.GET("/", controller.GetIndex)
router.GET("/config", controller.GetConfig)

router.Run(":8080")
}

Penjelasan:

  • router := gin.Default() kita declare router nya dengan default option yaitu dengan logger dan middleware dari gin lihat disini untuk lebih detail nya lagi.

  • router.Delims("{{", "}}") kita set delimiter dari template yang digunakan.

  • router.LoadHTMLGlob("./templates/*.html") lalu load semua file di dalam folder templates yang berakhiran dengan .html

  • router.StaticFile("/favicon.ico", "./static/favicon.ico") (optional) load favicon yang ada di ./static/favicon.ico dan di serve ke {hostname}/favicon.ico

  • router.Static("/css", "./static/css") perlu di note kali ini kita menggunakan Static() agar kita dapat sekaligus load semua file yang ada pada ./static/css dan di serve ke {hostname}/css

tip

kalian bisa pake StaticFile() and Static() untuk serve file lain!. Contohnya:

router.StaticFile("/logo.png", "./static/logo.png")
router.Static("/my-scripts", "./static/my-scripts")
  • router.GET() ini kita tulis untuk setiap route yang kita inginkan contohnya / untuk home /config untuk melihat konfigurasi dari website tersebut dll.

  • router.Run(":8080") ini akan menjalankan server nya pada port 8080.

Code utama sudah berhasil kita buat dan kita dapat lanjut ke tahap berikutnya yaitu membuat semua controller yang digunakan untuk handling semua route yang ada.

Membuat template

Setelah routing utama telah dibuat kita buat terlebih dahulu template yang ingin kita pakai dan di route nantinya. Kita buat terlebih dahulu file index.html kita.

templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>

<link rel="stylesheet" type="text/css" href="css/index.css" />

<title>{{ .Title }}</title>
</head>

<div class="container">
<img class="item logo" src="{{ .Logo }}" />

<div class="item name-container">
<a href="#">
<div class="name">{{ .Name }}</div>
</a>
<div class="alias">{{ .Alias }}</div>
</div>

<div class="item quote-container">
<div class="quote">
<q>{{ .Quote.Text }}</q>
</div>
<div class="author">&mdash; {{ .Quote.Author }}</div>
</div>

<div class="item desc">{{ .Desc }}</div>
</div>
</html>

Perhatikan bahwa di dalam file ini banyak terdapat syntax {{ }} sesuai yang sebelumnya kita declare pada file main.go, yang dimana nantinya value itu digantikan oleh value yang diberikan pada controller nya. Dikarenakan konsep websitenya yang simple saya juga ingin menambahkan fitur dengan memberi bentuk lain dari profile page ini selain html yaitu markdown document sehingga dapat di load dan dibaca menggunakan glow. Oleh karena itu kita tambahkan lagi markdown template pada folder templates.

templates/index.md
% {{ .Title }}

![Logo]({{ .Logo }})

# {{ .Name }} ({{ .Alias }})

> {{ .Quote.Text }} > <br>
> — {{ .Quote.Author }}

{{ .Desc }}

Setelah semua template berhasil dibuat maka kita sekarang dapat membuat config yang akan dibaca dan di masukkan value nya pada template yang kita buat.

Membuat config file

Kita buat file config kita pada root directory dan kita isi dengan key dan value yang kita inginkan.

config.yaml
title: Profile
quote:
text: I prefer dangerous freedom over peaceful slavery.
author: Thomas Jefferson
logo: https://ember.cocatrip.xyz/img/logo.png
name: Adrianus Vian Habirowo
alias: cocatrip
desc: |
I am a student at Bina Nusantara University, majoring in Cyber Security. Love
using linux and been using it since 2019. Altough i major in Cyber Security i
don’t like doing Cyber Security stuff, it’s just a way for me to learn linux
and other stuff related to Infrastructure and Deployment. Rather than doing
Cyber Security stuff i’d prefer doing what i like the most and that is playing
with kubernetes, working on my homelab, deploying an application, and other
kind of stuff similar to that.
tip

template dan key dari config yang kita buat bersifat dinamis. <title>{{ .Title }}</title> sesuai dengan title: Profile yang kita deklarasikan pada file config, yang artinya value pada template menyesuaikan apa ayang kita deklarasikan pada file config dan dapat dirubah menjadi variable apapun seperti <title>{{ .Judul }}</title> dan judul: Profile.

Membaca config file

Setelah config file berhasil dibuat maka kita harus membaca config file tersebut dengan cara melakukan parse file dari config file yang kita buat sehingga dapat terbaca oleh golang dan dimasukkan ke dalam template nantinya. Agar dapat terbaca maka kita harus membuat terlebih dahulu struktur dari config yang kita buat dalam bentuk golang struct untuk menampung konfigurasi kita.

models/config.go
package models

type Config struct {
Title string `yaml:"title"`
Quote struct {
Text string `yaml:"text"`
Author string `yaml:"author"`
} `yaml:"quote"`
Logo string `yaml:"logo"`
Name string `yaml:"name"`
Alias string `yaml:"alias"`
Desc string `yaml:"desc"`
}

Dapat kita lihat disini bahwa struct yang kita buat memiliki struktur yang persis dengan config file yang kita buat. Setelah itu, kita baca file config yang kita buat dan kita masukkan ke dalam struct yang baru saja kita buat. Kita taruh dalam sebuah function yang bersifat tidak global namun masih dalam satu package dengan controller function yang membutuhkan konfigurasi yang kita buat.

controller/common.go
package controller

import (
"fmt"
"io/ioutil"

"github.com/cocatrip/cocatrip/models"
"gopkg.in/yaml.v3"
)

func readConfig() (models.Config, error) {
var config models.Config

yamlFile, err := ioutil.ReadFile("config.yaml")
if err != nil {
return config, err
}

if err := yaml.Unmarshal(yamlFile, &config); err != nil {
return config, err
}

return config, nil
}

Penjelasan:

  • import terlebih dahulu semua package yang dibutuhkan
  • buat sebuah function yang akan memberi hasil Config beserta error jika ada.

  • deklarasikan sebuah variable yang memiliki tipe Config

  • baca file yaml yang kita buat dan parse file yaml yang bertipe byte tersebut ke dalam variabel config yang baru saja kita declare

  • berikan return config dan err untuk setiap validasi error dan pada akhir function

Membuat controller

Setelah berhasil maka kita buat controller yang akan dipanggil pada saat routing path / pada file main.go yang kita buat sebelumnya.

controller/index.go
package controller

import (
"bytes"
"html/template"
"log"
"net/http"

"github.com/gin-gonic/gin"
)

func GetIndex(c *gin.Context) {
config, err := readConfig()
if err != nil {
log.Fatal(err)
}

if isHtmlOutput(c.Request.Header["User-Agent"][0]) {
c.HTML(http.StatusOK, "index.html", gin.H{
"Title": config.Title,
"Quote": config.Quote,
"Logo": config.Logo,
"Name": config.Name,
"Alias": config.Alias,
"Desc": config.Desc,
})
} else {
t, err := template.ParseFiles("./templates/index.md")
if err != nil {
log.Fatal(err)
}

var buffer bytes.Buffer
err = t.Execute(&buffer, config)
output := buffer.String()

c.String(http.StatusOK, output)
}
}

Perhatikan pada code diatas bahwa sebelum kita memberi response html saya memberikan statement jika dan memberi user agent dari pengguna yang mengakses menjadi sebuah parameter function isHtmlOutput, function ini digunakan untuk fitur yang saya sebutkan sebelumnya yaitu untuk membaca apakah user harus disajikan dengan html atau markdown. Dengan demikian kita buat function tersebut dan kita taruh function tersebut di tempat yang sama dengan function untuk membaca config karena di file itu nantinya akan terdapat function-function yang sering digunakan oleh file pada package yang sama.

controller/common.go
import (
...
strings
...
)

func isHtmlOutput(ua string) bool {
if strings.Contains(ua, "curl") ||
strings.Contains(ua, "Wget") ||
strings.Contains(ua, "Go-http-client") {
return false
} else {
return true
}
}

kita tambahkan function tersebut ke file controller/common.go dan tambahkan package strings kedalam list package import.

Penjelasan:

  • berikan user agent sebagai parameter untuk function tersebut dan berikan boolean value sehingga statement if dapat memvalidasi apakah harus memberi output html atau markdown

  • memvalidasi apakah user agent mengandung kata curl/Wget/Go-http-client

  • memberikan return true jika tidak mengandung kata curl/Wget/Go-http-client dan false jika mengandung salah satu kata tersebut

info

saat kita menjalankan command curl atau wget atau glow user agent yang terkirim akan mengandung kata curl atau Wget atau Go-http-client untuk glow yang dimana glow menggunakan Go-http-client untuk melakukan request terhadap sebuah link.

curl http://localhost:8080 # curl/<ip>
wget http://localhost:8080 # Wget/<ip>
glow http://localhost:8080 # Go-http-client/<ip>

Menambahkan css

Untuk kita dapat menambahkan css kita tinggal taruh ke tempat dimana href diarahkan pada tag <link rel="stylesheet" type="text/css" href="css/index.css" /> maka kita masukkan file css sesuai dengan path yang sudah kita declare pada file main.go router.Static("/css", "./static/css").

static/css/index.css
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&family=Noto+Emoji&display=swap');

/* global */
* {
font-family: 'Fira Code', monospace;
background-color: #293329;
color: #c5cac5;
}

html {
scroll-behavior: smooth;
}

::-webkit-scrollbar {
display: none;
}

/* base layout */
.container {
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
padding: 2rem 4rem;
}

.item {
text-align: center;
}

/* logo image */
.logo {
border-radius: 5%;
width: 150px;
height: auto;
}

/* name and alias */
.name-container {
display: flex;
flex-direction: column;
}

.name {
font-size: 2em;
font-weight: bold;
}

.alias {
font-style: italic;
}

/* quote */
.quote-container {
display: flex;
flex-direction: column;
align-items: flex-end;
}

/* description */
.desc {
text-align: justify;
max-width: 50rem;
}

Done!

Setelah semua sudah berhasil terbuat kita dapat coba membuka localhost pada port 8080 untuk melihat hasil jadi dari website yang kita buat dan kita pun juga dapat melihat profile page kita dalam bentuk markdown jika direquest menggunakan glow jika sudah terinstall. Kalian juga dapat menambahkan section-section lain sesuka kalian sesuai yang kalian inginkan!.

Berikut cara saya membuat profile page milik saya dan jangan lupa untuk membaca juga part 2 untuk bagian deployment dari aplikasi ini.

Terima Kasih!