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
.
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 menggunakanStatic()
agar kita dapat sekaligus load semua file yang ada pada./static/css
dan di serve ke{hostname}/css
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 port8080
.
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.
<!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">— {{ .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.
% {{ .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.
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.
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.
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.
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 declareberikan 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.
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.
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 katacurl/Wget/Go-http-client
danfalse
jika mengandung salah satu kata tersebut
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")
.
@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!