Home > Blog > tech

HTMX + Go + Templ คืออะไร? Full-Stack Web Development แบบ Simple แต่ Powerful 2026

HTMX Go Templ Full-Stack Guide 2026
2026-04-11 | tech | 4200 words

ในยุคที่ทุก Web framework ต้องมี Node.js, npm, Webpack/Vite, TypeScript, State management library, SSR/SSG/ISR แต่ละโปรเจกต์ต้อง Setup build toolchain ครึ่งวัน ก่อน Hello World จะออกมา หลายคนเริ่มถามว่า "ต้องซับซ้อนขนาดนี้เลยหรือ?" คำตอบคือ ไม่ต้อง ถ้าคุณรู้จัก HTMX + Go + Templ — Stack ที่ถูกเรียกว่า "The Boring Full-Stack" เพราะมันน่าเบื่อ (ในทางที่ดี) Simple, Predictable และ Fast

HTMX ให้ Interactivity โดยไม่ต้องเขียน JavaScript, Go ให้ Performance ระดับ C/C++ พร้อม Concurrency ที่ยอดเยี่ยม, Templ ให้ Type-safe HTML templates สำหรับ Go ทั้งหมดนี้ Compile เป็น Single binary ไฟล์เดียว Copy ไปวางบน Server แล้ว Deploy ได้เลย ไม่ต้อง Node.js ไม่ต้อง npm ไม่ต้อง Docker (ก็ได้)

ทำไมต้อง Stack นี้? — The "Boring" Full-Stack

ปัจจัยReact/Next.js StackHTMX + Go + Templ Stack
Language (Frontend)JavaScript/TypeScriptHTML + HTMX attributes
Language (Backend)Node.js/TypeScriptGo
Build toolWebpack/Vite/Turbopackgo build (ไม่มี Frontend build)
Bundle size (JS)200-500+ KB~14 KB (HTMX CDN)
Node.js required?ใช่ (Frontend + Backend)ไม่
npm packages500-2,000+ packages0 npm packages
Deploy artifactNode.js app + static filesSingle binary file
Memory usage (server)100-500 MB10-30 MB
Startup time2-10 seconds< 100 ms
Type safety (templates)JSX/TSX (full)Templ (full, compile-time)
Learning curveสูง (React + hooks + state + SSR)ต่ำ (HTML + Go + HTMX attrs)

HTMX คืออะไร?

HTMX เป็น JavaScript library ขนาดเล็ก (~14 KB gzipped) ที่ให้คุณเพิ่ม Interactivity ให้กับ HTML ได้โดยใช้ HTML attributes แทนการเขียน JavaScript แนวคิดหลัก: แทนที่ Server จะส่ง JSON กลับมาแล้ว Frontend ต้อง Render HTML เอง (SPA model) ให้ Server ส่ง HTML fragment กลับมาแล้ว HTMX จะ "แทรก" HTML นั้นเข้าไปในหน้าเว็บ

<!-- แบบ Traditional: JavaScript fetch + DOM manipulation -->
<button onclick="fetchData()">Load</button>
<div id="result"></div>
<script>
async function fetchData() {
  const res = await fetch('/api/data');
  const json = await res.json();
  document.getElementById('result').innerHTML = `<p>${json.name}</p>`;
}
</script>

<!-- แบบ HTMX: ไม่ต้องเขียน JavaScript เลย -->
<button hx-get="/api/data" hx-target="#result">Load</button>
<div id="result"></div>
<!-- Server ส่ง HTML กลับมา: <p>John Doe</p> -->
<!-- HTMX เอาไปใส่ใน #result อัตโนมัติ -->

HTMX Attributes ที่ใช้บ่อย

Attributeหน้าที่ตัวอย่าง
hx-getGET request เมื่อ Triggerhx-get="/api/users"
hx-postPOST requesthx-post="/api/users"
hx-putPUT requesthx-put="/api/users/1"
hx-deleteDELETE requesthx-delete="/api/users/1"
hx-targetElement ที่จะแทรก Responsehx-target="#result"
hx-swapวิธีแทรก HTMLhx-swap="innerHTML" / "outerHTML" / "beforeend"
hx-triggerEvent ที่ Trigger Requesthx-trigger="click" / "keyup changed delay:500ms"
hx-indicatorLoading indicatorhx-indicator="#spinner"
hx-confirmConfirm dialog ก่อน Requesthx-confirm="Delete this item?"
hx-valsส่งค่าเพิ่มเติมhx-vals='{"page": 2}'

Go + Chi/Echo Router

Go เป็นภาษาที่เหมาะกับ Web server มาก: Performance สูง, Memory ต่ำ, Concurrency ยอดเยี่ยม (Goroutines), Compile เป็น Single binary, Standard library มี HTTP server ในตัว

// main.go — Go web server พื้นฐาน
package main

import (
    "fmt"
    "net/http"
    "log"
)

func main() {
    // Go standard library — ไม่ต้อง Framework ก็ได้!
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "<h1>Hello from Go!</h1>")
    })

    http.HandleFunc("/api/greeting", func(w http.ResponseWriter, r *http.Request) {
        name := r.URL.Query().Get("name")
        if name == "" {
            name = "World"
        }
        // ส่ง HTML fragment กลับ (ไม่ใช่ JSON!)
        fmt.Fprintf(w, "<p>Hello, %s! 🎉</p>", name)
    })

    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

สำหรับโปรเจกต์ที่ซับซ้อนขึ้น แนะนำใช้ Router เช่น Chi หรือ Echo:

// main.go — ใช้ Chi router
package main

import (
    "net/http"
    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)

    r.Get("/", handleIndex)
    r.Get("/api/todos", handleListTodos)
    r.Post("/api/todos", handleCreateTodo)
    r.Put("/api/todos/{id}", handleUpdateTodo)
    r.Delete("/api/todos/{id}", handleDeleteTodo)

    http.ListenAndServe(":8080", r)
}

Templ — Type-Safe HTML Templates สำหรับ Go

Templ เป็น Template engine สำหรับ Go ที่ Generate Go code จาก Template files ข้อดีเหนือ html/template ของ Go standard library:

// components/todo.templ
package components

type Todo struct {
    ID        int
    Title     string
    Completed bool
}

// TodoItem component — เหมือน React component
templ TodoItem(todo Todo) {
    <div class="todo-item" id={{ fmt.Sprintf("todo-%d", todo.ID) }}>
        <input type="checkbox"
            if todo.Completed {
                checked
            }
            hx-put={{ fmt.Sprintf("/api/todos/%d", todo.ID) }}
            hx-target={{ fmt.Sprintf("#todo-%d", todo.ID) }}
            hx-swap="outerHTML"
        />
        <span class={{ templ.KV("completed", todo.Completed) }}>
            {{ todo.Title }}
        </span>
        <button
            hx-delete={{ fmt.Sprintf("/api/todos/%d", todo.ID) }}
            hx-target={{ fmt.Sprintf("#todo-%d", todo.ID) }}
            hx-swap="outerHTML"
            hx-confirm="Delete this todo?"
        >X</button>
    </div>
}

// TodoList component
templ TodoList(todos []Todo) {
    <div id="todo-list">
        for _, todo := range todos {
            @TodoItem(todo)
        }
    </div>
}

// AddTodoForm component
templ AddTodoForm() {
    <form hx-post="/api/todos"
          hx-target="#todo-list"
          hx-swap="beforeend"
          hx-on::after-request="this.reset()">
        <input type="text" name="title" placeholder="New todo..." required />
        <button type="submit">Add</button>
    </form>
}

Building CRUD App — Todo List

มาสร้าง Todo app เต็มรูปแบบ:

// handlers.go
package main

import (
    "net/http"
    "strconv"
    "myapp/components"
)

var todos = []components.Todo{
    {ID: 1, Title: "Learn HTMX", Completed: false},
    {ID: 2, Title: "Learn Go", Completed: true},
    {ID: 3, Title: "Learn Templ", Completed: false},
}
var nextID = 4

func handleIndex(w http.ResponseWriter, r *http.Request) {
    components.Page(todos).Render(r.Context(), w)
}

func handleListTodos(w http.ResponseWriter, r *http.Request) {
    components.TodoList(todos).Render(r.Context(), w)
}

func handleCreateTodo(w http.ResponseWriter, r *http.Request) {
    title := r.FormValue("title")
    if title == "" {
        http.Error(w, "Title required", 400)
        return
    }
    todo := components.Todo{ID: nextID, Title: title, Completed: false}
    nextID++
    todos = append(todos, todo)
    // ส่ง HTML fragment ของ todo ใหม่กลับ
    components.TodoItem(todo).Render(r.Context(), w)
}

func handleDeleteTodo(w http.ResponseWriter, r *http.Request) {
    id, _ := strconv.Atoi(chi.URLParam(r, "id"))
    for i, t := range todos {
        if t.ID == id {
            todos = append(todos[:i], todos[i+1:]...)
            break
        }
    }
    // ส่ง Empty response — HTMX จะลบ Element ออก (outerHTML swap)
    w.WriteHeader(200)
}

Form Handling และ Dynamic UI Updates

HTMX ทำให้ Form handling ง่ายมาก ไม่ต้อง useState ไม่ต้อง onChange ไม่ต้อง onSubmit ไม่ต้อง preventDefault:

<!-- Search with Live Results (Debounced) -->
<input type="search"
    name="q"
    placeholder="Search todos..."
    hx-get="/api/todos/search"
    hx-trigger="keyup changed delay:300ms"
    hx-target="#search-results"
/>
<div id="search-results"></div>

<!-- Infinite Scroll -->
<div id="todo-list">
    <!-- existing todos -->
</div>
<div hx-get="/api/todos?page=2"
     hx-trigger="revealed"
     hx-swap="afterend">
    Loading more...
</div>

<!-- Inline Edit -->
<span hx-get="/api/todos/1/edit"
      hx-trigger="dblclick"
      hx-swap="outerHTML">
    Todo title (double-click to edit)
</span>

Authentication

Authentication ใน HTMX + Go stack ใช้ Session-based (cookie) เหมือน Web แบบดั้งเดิม ไม่ต้อง JWT ไม่ต้อง localStorage:

// middleware/auth.go
package middleware

import (
    "net/http"
    "context"
)

func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        session, err := store.Get(r, "session")
        if err != nil || session.Values["user_id"] == nil {
            // HTMX: ถ้า Request มาจาก HTMX ส่ง HX-Redirect header
            if r.Header.Get("HX-Request") == "true" {
                w.Header().Set("HX-Redirect", "/login")
                w.WriteHeader(200)
                return
            }
            http.Redirect(w, r, "/login", http.StatusFound)
            return
        }
        ctx := context.WithValue(r.Context(), "user_id", session.Values["user_id"])
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Database — SQLite/PostgreSQL

Go มี Database drivers ครบทุก DB ที่ต้องการ:

// db/db.go — SQLite (เหมาะกับ Single-server, ง่ายที่สุด)
package db

import (
    "database/sql"
    _ "github.com/mattn/go-sqlite3"
)

var DB *sql.DB

func Init() {
    var err error
    DB, err = sql.Open("sqlite3", "./app.db")
    if err != nil {
        panic(err)
    }

    // Create table
    DB.Exec(`CREATE TABLE IF NOT EXISTS todos (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        completed BOOLEAN DEFAULT FALSE,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )`)
}

func GetTodos() ([]Todo, error) {
    rows, err := DB.Query("SELECT id, title, completed FROM todos ORDER BY created_at DESC")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var todos []Todo
    for rows.Next() {
        var t Todo
        rows.Scan(&t.ID, &t.Title, &t.Completed)
        todos = append(todos, t)
    }
    return todos, nil
}
SQLite สำหรับ Production? ใช่! SQLite รองรับ Millions of reads/sec และ Thousands of writes/sec (WAL mode) เหมาะกับ App ที่มี Traffic ปานกลาง เช่น Blog, Internal tools, SaaS MVP ข้อดี: ไม่ต้องรัน Database server แยก Backup แค่ Copy file เดียว

Deployment — Single Binary + Docker

# Build single binary
# 1. Generate Templ code
templ generate

# 2. Build Go binary
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myapp .

# 3. Copy ไปวาง Server (ถ้าใช้ SQLite)
scp myapp user@server:/opt/myapp/
ssh user@server "/opt/myapp/myapp"

# Dockerfile (ถ้าต้องการ Docker)
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go install github.com/a-h/templ/cmd/templ@latest
RUN templ generate
RUN CGO_ENABLED=0 go build -o /myapp .

FROM alpine:latest
COPY --from=builder /myapp /myapp
EXPOSE 8080
CMD ["/myapp"]

# Build: docker build -t myapp .
# Run: docker run -p 8080:8080 myapp
# Image size: ~15 MB (Alpine + Go binary)

Performance Benchmarks

MetricHTMX + GoNext.js (React SSR)Laravel (PHP)
Requests/sec (simple page)50,000+2,000-5,000500-2,000
Memory usage10-30 MB100-300 MB50-150 MB
Startup time< 100 ms2-10 sec200-500 ms
JS sent to client14 KB (HTMX)200-500 KB0-50 KB
Time to Interactive< 100 ms1-3 sec (Hydration)< 500 ms
Build time1-3 sec (go build)10-60 sec0 (interpreted)
Docker image size15-20 MB200-500 MB100-300 MB

เมื่อไหร่ที่ Stack นี้ดีกว่า React/Next.js?

เหมาะมาก:

ไม่เหมาะ:

Limitations และ Trade-offs

1. Complex client-side state: HTMX ไม่เหมาะกับ UI ที่ต้อง Track state ซับซ้อนบน Client เช่น Drag-and-drop, Multi-step wizard ที่ต้องจำสถานะทุก Step, Real-time validation หลายฟิลด์ที่ขึ้นต่อกัน แก้ได้โดยเพิ่ม Alpine.js (lightweight JavaScript framework) ควบคู่กับ HTMX

2. ไม่มี ecosystem ใหญ่เท่า React: React มี Component library (Material UI, Ant Design, Chakra UI) ให้เลือกเป็นพัน HTMX + Templ ยังไม่มี ต้องเขียน UI เอง หรือใช้ CSS framework (Tailwind, Bootstrap)

3. SEO: HTMX ส่ง Full HTML จาก Server อยู่แล้ว ดังนั้น SEO ดีกว่า SPA by default ไม่ต้อง SSR/SSG เพิ่ม

4. Learning curve: ถ้าคุ้นเคย React อยู่แล้ว อาจรู้สึก "ถอยหลัง" แต่ถ้าเพิ่งเริ่ม Web dev หรือคุ้นเคย Server-side (PHP, Django, Rails) stack นี้จะเรียนรู้ง่ายกว่ามาก

สรุป

HTMX + Go + Templ เป็น Stack ที่เหมาะสำหรับ Developer ที่ต้องการ: Simplicity — ไม่ต้อง Build toolchain ซับซ้อน ไม่ต้อง npm, Performance — Go ให้ Performance สูงด้วย Memory ต่ำ, Type safety — Templ ให้ Compile-time type checking, Easy deployment — Single binary ไฟล์เดียว Copy ไปวาง, Productivity — เขียน Code น้อย ได้ผลลัพธ์เยอะ

ไม่ได้หมายความว่า React/Vue/Angular ไม่ดี แต่หลายโปรเจกต์ "ไม่ต้องการ" ความซับซ้อนระดับนั้น ถ้าคุณกำลังสร้าง Admin dashboard, Internal tool, Blog, Landing page, หรือ SaaS MVP ลอง HTMX + Go + Templ ก่อน คุณอาจแปลกใจว่ามันเพียงพอสำหรับ 80% ของ Web applications ที่มีอยู่


Back to Blog | iCafe Forex | SiamLanCard | Siam2R