เว็บแอปพลิเคชันพื้นฐาน

Web Application (เว็บ แอพพลิเคชั่น) คือ Application ที่ถูกเขียนขึ้นมาเพื่อเป็น Browser (บราวเซอร์) สำหรับการใช้งานเว็บเพจต่างๆ ซึ่งถูกปรับแต่งให้แสดงผลแต่ส่วนที่จำเป็น เพื่อเป็นการลดทรัพยากรในการประมวลผล ของตัวเครื่องสมาร์ทโฟน หรือ แท็บเล็ต ทำให้โหลดหน้าเว็บไซต์ได้เร็วขึ้น อีกทั้งผู้ใช้งานยังสามารถใช้งานผ่าน อินเตอร์เน็ตและอินทราเน็ตได้ โดยการสร้าง Web Application สิ่งที่ต้องเรียนรู้ คือ ภาษา HTML, CSS, Go และภาษา SQL เป็นต้น

ภาษา HTML นั้นเป็นภาษาที่ใช้ในการออกแบบและตกแต่งหน้าตาของ From ในเว็บไซต์ หรือ Web Application เช่น การแสดงรูปแบบตัวอักษรที่เป็นแบบ ตัวหนา เอียง ขีดเส้นใต้ หรือจะเป็นการแสดงแบบรูปภาพ หรือตาราง เป็นต้น

ภาษา Go เป็นภาษาที่คอยประมวลผล โดยทำหน้าที่รับหรืออ่านคำสั่งที่คุณเขียนโค้ดไว้ด้วยภาษา Go แล้วทำงานตามคำสั่งเหล่านั้น เช่น การรับค่าแล้วเก็บค่าลงในตัวแปร การตัดสินใจ(การทำซ้ำ, การวนลูป) หรืออาจจะทำงานที่ซับซ้อนขึ้น เช่น การอัพไฟล์ข้อมูล การติดต่อกับฐานข้อมูล เป็นต้น

ภาษา SQL เป็นภาษามาตรฐานสำหรับการทำงานร่วมกับฐานข้อมูลเชิงสัมพันธ์ ซึ่งใช้ในการจัดการฐานข้อมูลของ Web Application ที่เราสร้างขึ้นมานั่นเอง


ข้อกำหนดเบื้องต้น


คุณต้องติดตั้ง Go บนคอมพิวเตอร์ของคุณตามบทความ ติดตั้ง Go และ ทดสอบ Hello World และ คุณคุ้นเคยกับรูปแบบและโครงสร้างของมัน ด้วยการผ่านการเรียนรู้บทความ Workshop-2 New Deck & Print มาก่อน


สร้างโปรเจคใหม่


สร้างโปรเจคใหม่ โดยไปที่ File -> Open Folder -> New Folder ตั้งชื่อเป็น modern-web

สร้าง Go Modules คือสิ่งที่สร้างขึ้นมาเพื่อจัดการ packages ต่างๆ สามารถกำหนดเวอร์ชั่นได้และมีการจัดการที่มีประสิทธิภาพมากขึ้น

สร้างไฟล์ Go Modules ด้วยคำสั่ง

go mod init modern-web


สร้างแพคเกจ main


สร้างโฟลเดอร์ cmd แล้วสร้างโฟลเดอร์ web ภายในโฟลเดอร์ cmd และสร้างไฟล์ main.go เขียนโค้ดสร้างแพคเกจ main และ ฟังก์ชัน main

package main

func main() {
	
}


สร้างเว็บเซิร์ฟเวอร์


สร้างเว็บเซิร์ฟเวอร์ ด้วยการนำเข้าแพคเกจ “net/http”

import (
	"net/http"
)


const คือการประกาศค่าคงที่ ชื่อ portNumber มีค่าเป็น :8080 

const portNumber = ":8080"


ListenAndServe รับ Parameter 2 ตัว คือ

Port ที่เราต้องการให้ Request จาก Client ส่งเข้ามา ณ ที่นี้คือ Port 8080 ตามค่าของ portNumber

Option สำหรับเลือกใช้ตัว Handle Request ถ้าใส่เป็น nil คือ “ว่างเปล่า” คือจะใช้ Default ในการ Handle Request

_ = http.ListenAndServe(portNumber, nil)


สร้าง Template


ในการพัฒนาระบบ Web Aplication ด้วย Go สามารถพัฒนาร่วมกับ html template ได้ มีลักษณะเช่นเดียวกับการใช้ Template engine ต่างๆ แต่ Go นั้นมี package html/template มาให้เราใช้งานอยู่แล้ว ดังนั้นมาดูกันว่าเราจะพัฒนากันอย่างไร

สร้างโฟลเดอร์ templates แล้ว สร้างไฟล์ base.layout.tmpl มีโค้ดดังนี้

{{define "base"}}
    <!doctype html>
    <html lang="en">
    <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">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
              integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
              crossorigin="anonymous">

        {{block "css" .}}

        {{end}}
    </head>
    <body>

    {{block "content" .}}

    {{end}}


    {{block "js" .}}

    {{end}}

    </body>
    </html>
{{end}}


สร้างไฟล์ home.page.tmpl มีโค้ดดังนี้

{{template "base" .}}

{{define "content"}}
    <div class="container">
        <div class="row">
            <div class="col">
                <h1>This is the home page</h1>
                <p>This is some text</p>
            </div>
        </div>
    </div>
{{end}}


สร้างไฟล์ about.page.tmpl มีโค้ดดังนี้

{{template "base" .}}

{{define "content"}}
    <div class="container">
        <div class="row">
            <div class="col">
                <h1>This is the about page</h1>
            </div>
        </div>
    </div>
{{end}}


สร้างแพคเกจ handlers


สร้างโฟลเดอร์ pkg แล้วสร้างโฟลเดอร์ handlers ภายในโฟลเดอร์ pkg และสร้างไฟล์ handlers.go เขียนโค้ดดังนี้

package handlers

import (
	"modern-web/pkg/render"
	"net/http"
)

// Home is the handler for the home page
func Home(w http.ResponseWriter, r *http.Request) {
	render.RenderTemplate(w, "home.page.tmpl")
}

// About is the handler for the about page
func About(w http.ResponseWriter, r *http.Request) {
	render.RenderTemplate(w, "about.page.tmpl")
}


สร้างแพคเกจ render


สร้างโฟลเดอร์ render ภายในโฟลเดอร์ pkg และสร้างไฟล์ render.go เขียนโค้ดดังนี้

package render

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"
)

var functions = template.FuncMap{}

// RenderTemplate renders a template
func RenderTemplate(w http.ResponseWriter, tmpl string) {
	// get the template cache from the app config

	tc, err := CreateTemplateCache()
	if err != nil {
		log.Fatal(err)
	}

	t, ok := tc[tmpl]
	if !ok {
		log.Fatal(err)
	}

	buf := new(bytes.Buffer)

	_ = t.Execute(buf, nil)

	_, err = buf.WriteTo(w)
	if err != nil {
		fmt.Println("error writing template to browser", err)
	}

}

// CreateTemplateCache creates a template cache as a map
func CreateTemplateCache() (map[string]*template.Template, error) {

	myCache := map[string]*template.Template{}

	pages, err := filepath.Glob("./templates/*.page.tmpl")
	if err != nil {
		return myCache, err
	}

	for _, page := range pages {
		name := filepath.Base(page)
		ts, err := template.New(name).Funcs(functions).ParseFiles(page)
		if err != nil {
			return myCache, err
		}

		matches, err := filepath.Glob("./templates/*.layout.tmpl")
		if err != nil {
			return myCache, err
		}

		if len(matches) > 0 {
			ts, err = ts.ParseGlob("./templates/*.layout.tmpl")
			if err != nil {
				return myCache, err
			}
		}

		myCache[name] = ts
	}

	return myCache, nil
}


นำเข้าแพคเกจ handlers


แก้ไขไฟล์ main.go นำเข้าแพคเกจ handlers เพื่อจัดการเส้นทาง ด้วยคำสั่ง

"modern-web/pkg/handlers"


จัดการเส้นทาง 2 เส้นทาง ไปยัง หน้า Home และ หน้า About ด้วยคำสั่ง

http.HandleFunc("/", handlers.Home)
http.HandleFunc("/about", handlers.About)


นำเข้าแพคเกจ “fmt”


นำเข้าแพคเกจ “fmt” ซึ่งภายใต้ fmt มีฟังก์ชันชื่อ Println ให้เราสามารถพิมพ์ข้อความได้ ด้วยคำสั่ง

fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))


ทดสอบการทำงาน


ทดสอบการทำงาน ด้วยคำสั่ง

go run ./cmd/web/ .


ทดสอบการทำงานโดยไปที่ หน้า Home http://localhost:8080/


และเมื่อเปลี่ยนไปที่ หน้า About http://localhost:8080/about


การแชร์ข้อมูลกับเทมเพลต


สร้างโฟลเดอร์ config ภายในโฟลเดอร์ pkg และสร้างไฟล์ config.go เขียนโค้ดดังนี้

package config

import (
	"html/template"
	"log"
)

// AppConfig holds the application config
type AppConfig struct {
	UseCache      bool
	TemplateCache map[string]*template.Template
	InfoLog       *log.Logger
}




สร้างโฟลเดอร์ models ภายในโฟลเดอร์ pkg และสร้างไฟล์ templatedata.go เขียนโค้ดดังนี้

package models

// TemplateData holds data sent from handlers to templates
type TemplateData struct {
	StringMap map[string]string
	IntMap    map[string]int
	FloatMap  map[string]float32
	Data      map[string]interface{}
	CSRFToken string
	Flash     string
	Warning   string
	Error     string
}

ไฟล์ render.go แก้ไขดังนี้

package render

import (
	"modern-web/pkg/config"
	"modern-web/pkg/models"
	"bytes"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"
)

var functions = template.FuncMap{}

var app *config.AppConfig

// NewTemplates sets the config for the template package
func NewTemplates(a *config.AppConfig) {
	app = a
}

func AddDefaultData(td *models.TemplateData) *models.TemplateData {

	return td
}

// RenderTemplate renders a template
func RenderTemplate(w http.ResponseWriter, tmpl string, td *models.TemplateData) {
	var tc map[string]*template.Template

	if app.UseCache {
		// get the template cache from the app config
		tc = app.TemplateCache
	} else {
		tc, _ = CreateTemplateCache()
	}

	t, ok := tc[tmpl]
	if !ok {
		log.Fatal("Could not get template from template cache")
	}

	buf := new(bytes.Buffer)

	td = AddDefaultData(td)

	_ = t.Execute(buf, td)

	_, err := buf.WriteTo(w)
	if err != nil {
		fmt.Println("error writing template to browser", err)
	}

}

// CreateTemplateCache creates a template cache as a map
func CreateTemplateCache() (map[string]*template.Template, error) {

	myCache := map[string]*template.Template{}

	pages, err := filepath.Glob("./templates/*.page.tmpl")
	if err != nil {
		return myCache, err
	}

	for _, page := range pages {
		name := filepath.Base(page)
		ts, err := template.New(name).Funcs(functions).ParseFiles(page)
		if err != nil {
			return myCache, err
		}

		matches, err := filepath.Glob("./templates/*.layout.tmpl")
		if err != nil {
			return myCache, err
		}

		if len(matches) > 0 {
			ts, err = ts.ParseGlob("./templates/*.layout.tmpl")
			if err != nil {
				return myCache, err
			}
		}

		myCache[name] = ts
	}

	return myCache, nil
}


ไฟล์ handlers.go แก้ไขดังนี้

package handlers

import (
	"modern-web/pkg/config"
	"modern-web/pkg/models"
	"modern-web/pkg/render"
	"net/http"
)

// Repo the repository used by the handlers
var Repo *Repository

// Repository is the repository type
type Repository struct {
	App *config.AppConfig
}

// NewRepo creates a new repository
func NewRepo(a *config.AppConfig) *Repository {
	return &Repository{
		App: a,
	}
}

// NewHandlers sets the repository for the handlers
func NewHandlers(r *Repository) {
	Repo = r
}

// Home is the handler for the home page
func (m *Repository) Home(w http.ResponseWriter, r *http.Request) {
	render.RenderTemplate(w, "home.page.tmpl", &models.TemplateData{})
}

// About is the handler for the about page
func (m *Repository) About(w http.ResponseWriter, r *http.Request) {
	// perform some logic
	stringMap := make(map[string]string)
	stringMap["test"] = "Hello, again"

	// send data to the template
	render.RenderTemplate(w, "about.page.tmpl", &models.TemplateData{
		StringMap: stringMap,
	})
}

ไฟล์ about.page.tmpl แก้ไขดังนี้

{{template "base" .}}

{{define "content"}}
    <div class="container">
        <div class="row">
            <div class="col">
                <h1>This is the about page</h1>
                <p>This is a paragraph of text</p>
                <p>This is a paragraph of text</p>

                <p>This came from the template: {{index .StringMap "test"}}</p>
            </div>
        </div>
    </div>
{{end}}


ไฟล์ main.go แก้ไขดังนี้

package main

import (
	"modern-web/pkg/config"
	"modern-web/pkg/handlers"
	"modern-web/pkg/render"
	"fmt"
	"log"
	"net/http"
)

const portNumber = ":8080"

// main is the main function
func main() {
	var app config.AppConfig

	tc, err := render.CreateTemplateCache()
	if err != nil {
		log.Fatal("cannot create template cache")
	}

	app.TemplateCache = tc
	app.UseCache = false

	repo := handlers.NewRepo(&app)
	handlers.NewHandlers(repo)

	render.NewTemplates(&app)

	http.HandleFunc("/", handlers.Repo.Home)
	http.HandleFunc("/about", handlers.Repo.About)

	fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
	_ = http.ListenAndServe(portNumber, nil)
}


ทดสอบการทำงาน ไปที่ หน้า About http://localhost:8080/about

credit : https://www.udemy.com/course/building-modern-web-applications-with-go/

Leave a Reply

Your email address will not be published. Required fields are marked *