เราจะออกแบบโครงสร้างให้มี api สำหรับงาน todolist ประเภท CRUD และให้มี web ui ที่สวยงามโดย โครงสร้าง project จะเป็นแบบนี้ครับ
File : main.go
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"sync"
"github.com/gorilla/mux"
)
// Todo struct (Model)
// นี่คือโครงสร้างข้อมูลของแต่ละ task
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
// ตัวแปรสำหรับเก็บข้อมูลทั้งหมด (เปรียบเสมือน Database ชั่วคราว)
var (
todos []Todo
nextID = 1
lock = sync.Mutex{} // ใช้ lock เพื่อป้องกันการเขียนข้อมูลพร้อมกัน (thread-safe)
)
// GetTodos: Handler สำหรับดึงข้อมูลทั้งหมด (Read)
func GetTodos(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
lock.Lock()
defer lock.Unlock()
json.NewEncoder(w).Encode(todos)
}
// CreateTodo: Handler สำหรับสร้าง task ใหม่ (Create)
func CreateTodo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var todo Todo
// อ่านข้อมูล JSON จาก request body
if err := json.NewDecoder(r.Body).Decode(&todo); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
lock.Lock()
defer lock.Unlock()
todo.ID = nextID
nextID++
todos = append(todos, todo)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(todo)
}
// UpdateTodo: Handler สำหรับอัปเดตสถานะ task (Update)
func UpdateTodo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
id, err := strconv.Atoi(params["id"])
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
lock.Lock()
defer lock.Unlock()
for i, item := range todos {
if item.ID == id {
todos[i].Completed = !todos[i].Completed // สลับค่า Completed
json.NewEncoder(w).Encode(todos[i])
return
}
}
http.NotFound(w, r)
}
// DeleteTodo: Handler สำหรับลบ task (Delete)
func DeleteTodo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
id, err := strconv.Atoi(params["id"])
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
lock.Lock()
defer lock.Unlock()
for i, item := range todos {
if item.ID == id {
// ---- ↓ บรรทัดที่แก้ไข ↓ ----
todos = append(todos[:i], todos[i+1:]...)
// ---- ↑ บรรทัดที่แก้ไข ↑ ----
w.WriteHeader(http.StatusNoContent)
return
}
}
http.NotFound(w, r)
}
func main() {
// สร้างข้อมูลเริ่มต้น
todos = append(todos, Todo{ID: nextID, Title: "เรียนรู้ภาษา Go", Completed: false}); nextID++
todos = append(todos, Todo{ID: nextID, Title: "สร้าง Web API", Completed: false}); nextID++
// สร้าง Router
r := mux.NewRouter()
// API Routes
api := r.PathPrefix("/api").Subrouter()
api.HandleFunc("/todos", GetTodos).Methods("GET")
api.HandleFunc("/todos", CreateTodo).Methods("POST")
api.HandleFunc("/todos/{id}", UpdateTodo).Methods("PUT")
api.HandleFunc("/todos/{id}", DeleteTodo).Methods("DELETE")
// Static File Server สำหรับหน้าเว็บ UI
// ให้ Go server ของเราสามารถเสิร์ฟไฟล์ html, css, js ได้
r.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))
log.Println("Starting server on :8080")
// เริ่มการทำงานของ Server ที่ Port 8080
if err := http.ListenAndServe(":8080", r); err != nil {
log.Fatal(err)
}
}
File : index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go Todo List</title>
<link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<header>
<h1>📝 Go Todo List</h1>
<p>A simple CRUD application built with Golang & Vanilla JS</p>
</header>
<form id="todo-form">
<input type="text" id="todo-input" placeholder="What needs to be done?" autocomplete="off" required>
<button type="submit">Add Task</button>
</form>
<ul id="todo-list">
</ul>
</div>
<script src="script.js"></script>
</body>
</html>
File : style.css
:root {
--bg-color: #1a1a2e;
--primary-color: #16213e;
--secondary-color: #0f3460;
--accent-color: #e94560;
--text-color: #dcdcdc;
--completed-color: #555;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Poppins', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
padding-top: 50px;
}
.container {
width: 100%;
max-width: 600px;
background-color: var(--primary-color);
border-radius: 15px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
header {
text-align: center;
margin-bottom: 30px;
border-bottom: 1px solid var(--secondary-color);
padding-bottom: 20px;
}
header h1 {
color: var(--accent-color);
margin-bottom: 5px;
}
#todo-form {
display: flex;
margin-bottom: 30px;
}
#todo-input {
flex-grow: 1;
padding: 15px;
border: 2px solid var(--secondary-color);
border-radius: 8px 0 0 8px;
background-color: var(--bg-color);
color: var(--text-color);
font-size: 16px;
outline: none;
}
#todo-input:focus {
border-color: var(--accent-color);
}
#todo-form button {
padding: 15px 20px;
border: none;
background-color: var(--accent-color);
color: white;
font-size: 16px;
font-weight: 600;
border-radius: 0 8px 8px 0;
cursor: pointer;
transition: background-color 0.3s;
}
#todo-form button:hover {
background-color: #ff5a79;
}
#todo-list {
list-style: none;
}
#todo-list li {
display: flex;
align-items: center;
background-color: var(--secondary-color);
padding: 15px;
border-radius: 8px;
margin-bottom: 10px;
transition: all 0.3s;
}
#todo-list li.completed {
background-color: var(--bg-color);
opacity: 0.6;
}
#todo-list li.completed span {
text-decoration: line-through;
color: var(--completed-color);
}
#todo-list li span {
flex-grow: 1;
cursor: pointer;
}
#todo-list li button {
background: none;
border: none;
color: var(--text-color);
font-size: 20px;
cursor: pointer;
margin-left: 10px;
transition: color 0.3s;
}
#todo-list li button.delete:hover {
color: var(--accent-color);
}
File : script.js
// รอให้ HTML โหลดเสร็จก่อนเริ่มทำงาน
document.addEventListener('DOMContentLoaded', () => {
const todoForm = document.getElementById('todo-form');
const todoInput = document.getElementById('todo-input');
const todoList = document.getElementById('todo-list');
const apiBaseUrl = '/api/todos';
// ฟังก์ชันสำหรับดึงข้อมูล Todos ทั้งหมดจาก API (Read)
async function fetchTodos() {
try {
const response = await fetch(apiBaseUrl);
const todos = await response.json();
todoList.innerHTML = ''; // ล้างรายการเก่า
if (todos) {
todos.forEach(todo => renderTodo(todo));
}
} catch (error) {
console.error('Failed to fetch todos:', error);
}
}
// ฟังก์ชันสำหรับแสดงผล Todo 1 รายการบนหน้าเว็บ
function renderTodo(todo) {
const li = document.createElement('li');
li.dataset.id = todo.id;
if (todo.completed) {
li.classList.add('completed');
}
const span = document.createElement('span');
span.textContent = todo.title;
// Event Listener สำหรับการกดที่ข้อความเพื่อสลับสถานะ (Update)
span.addEventListener('click', () => toggleTodo(todo.id));
const deleteButton = document.createElement('button');
deleteButton.textContent = '🗑️';
deleteButton.classList.add('delete');
// Event Listener สำหรับปุ่มลบ (Delete)
deleteButton.addEventListener('click', () => deleteTodo(todo.id));
li.appendChild(span);
li.appendChild(deleteButton);
todoList.appendChild(li);
}
// Event Listener สำหรับการเพิ่ม Todo ใหม่ (Create)
todoForm.addEventListener('submit', async (e) => {
e.preventDefault();
const title = todoInput.value.trim();
if (!title) return;
try {
const response = await fetch(apiBaseUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: title, completed: false })
});
const newTodo = await response.json();
renderTodo(newTodo);
todoInput.value = ''; // เคลียร์ช่อง input
} catch (error) {
console.error('Failed to create todo:', error);
}
});
// ฟังก์ชันสำหรับสลับสถานะ completed (Update)
async function toggleTodo(id) {
try {
const response = await fetch(`${apiBaseUrl}/${id}`, {
method: 'PUT'
});
const updatedTodo = await response.json();
// อัปเดต UI โดยไม่ต้องโหลดใหม่ทั้งหมด
const li = document.querySelector(`li[data-id='${id}']`);
if (li) {
li.classList.toggle('completed', updatedTodo.completed);
li.querySelector('span').style.textDecoration = updatedTodo.completed ? 'line-through' : 'none';
}
} catch (error) {
console.error('Failed to update todo:', error);
}
}
// ฟังก์ชันสำหรับลบ Todo (Delete)
async function deleteTodo(id) {
try {
await fetch(`${apiBaseUrl}/${id}`, {
method: 'DELETE'
});
// ลบออกจาก UI
const li = document.querySelector(`li[data-id='${id}']`);
if (li) {
li.remove();
}
} catch (error) {
console.error('Failed to delete todo:', error);
}
}
// เริ่มต้นโดยการดึงข้อมูลทั้งหมดมาแสดง
fetchTodos();
});
วิธีการรันโปรเจกต์
ติดตั้ง Go: ตรวจสอบให้แน่ใจว่าคุณได้ติดตั้ง Go บนเครื่องของคุณแล้ว
ติดตั้ง gorilla/mux: เปิด Terminal หรือ Command Prompt ขึ้นมาในโฟลเดอร์โปรเจกต์ (go-todolist) แล้วรันคำสั่ง:
go mod init go-todolist
go get -u github.com/gorilla/mux
รันเซิร์ฟเวอร์: รันคำสั่งต่อไปนี้เพื่อเริ่มการทำงานของ API Server
go run main.go
คุณจะเห็นข้อความ: Starting server on :8080
เปิดใช้งาน: เปิดเว็บเบราว์เซอร์แล้วเข้าไปที่ URL:
http://localhost:8080
Top comments (0)