HTMX + Go : Bina Apl CRUD dengan Golang dan HTMX
Satu soalan yang saya lihat sering berlaku tentang HTMX, terutamanya dalam kalangan pembangun yang baru mencuba perpustakaan ialah "Tetapi apakah yang anda boleh bina dengannya?"
Soalan yang bagus, dan dalam artikel ini, kami akan bermula dengan langkah kecil dengan membina aplikasi CRUD yang disokong pangkalan data dengan HTMX dan Go sebagai bahasa hujung belakang kami.
Sebenarnya, jika anda benar-benar mahukan panduan praktikal berasaskan projek untuk membina aplikasi fullstack dengan HTMX, lihat kursus **HTMX + Go: Bina Aplikasi Fullstack dengan Golang dan HTMX [Diskaun disertakan].**
Mari mulakan.
Apa sebenarnya yang kita bina?
Saya ingin memanggilnya Aplikasi Pengurusan Tugasan tetapi saya tahu anda sudah menganggap bahawa itu hanyalah nama yang menarik untuk aplikasi Todo yang lain. Jangan risau, apl Todo bagus untuk mempelajari operasi asas dengan bahasa, perpustakaan dan rangka kerja jadi kami akan menggunakan strategi yang diuji dan dipercayai yang sama.
Permohonan kami akan dapat melakukan perkara berikut:
- Tugas paparan
- Tambah tugasan baharu
- Kemas kini tugasan sedia ada dan …
- Padamkan tugas
Persediaan Pangkalan Data
Jadi pertama, kita memerlukan pangkalan data, dan untuk projek demo ini, saya akan menggunakan MySQL. Jangan ragu untuk menggunakan mana-mana pangkalan data pilihan anda dan buat perubahan kod yang diperlukan untuk merujuk pangkalan data anda semasa anda mengikuti artikel ini.
Kami akan memastikan perkara mudah, tiada reka bentuk skema yang rumit. Mula-mula kami mencipta pangkalan data dengan nama testdb dan di dalam pangkalan data ini, kami mencipta jadual todos (jangan ragu untuk memberikan pangkalan data dan jadual anda sebarang nama yang anda suka tetapi pastikan anda menggunakan nama yang sama dalam pernyataan SQL anda)
Di dalam jadual todos, laksanakan skema di bawah:
- id: PK, Penambahan automatik
- task : VARCHAR(200) - Mengandungi item tugas
- selesai: INT(1), lalai = 0 (medan Boolean)
Anda boleh memilih untuk menyemai jadual pangkalan data dengan beberapa tugasan supaya kami boleh mula melihat beberapa tugasan pada kali pertama kami memuatkan aplikasi.
Mencipta API Hipermedia
Untuk mula menyediakan aplikasi kecil kami, cipta folder untuk projek di mana-mana lokasi yang sesuai dalam komputer pembangunan anda.
mkdir task-management
Jalankan arahan berikut pada akar folder projek untuk memulakannya sebagai projek Golang:
go mod init task-management
Seterusnya, kita perlu memasang beberapa kebergantungan. Kami sudah tahu kami menggunakan MySQL sebagai pangkalan data kami, oleh itu, kami perlu memasang pemacu MySQL untuk Golang.
Kami juga perlu memasang Penghala Gorilla Mux yang akan menjadi perpustakaan penghalaan untuk projek kami. Jalankan dua arahan di bawah pada akar projek anda untuk mendapatkan perpustakaan ini dipasang ke dalam projek anda
MySQL:
go get -u github.com/go-sql-driver/mysql
Gorilla Mux:
go get -u github.com/gorilla/mux
Dengan adanya perpustakaan ini, buat fail main.go anda pada akar projek dan tambah kod di bawah:
package main import ( "database/sql" "fmt" "html/template" "log" "net/http" "strconv" "strings" _ "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" ) var tmpl *template.Template var db *sql.DB type Task struct { Id int Task string Done bool } func init() { tmpl, _ = template.ParseGlob("templates/*.html") } func initDB() { var err error // Initialize the db variable db, err = sql.Open("mysql", "root:root@(127.0.0.1:3333)/testdb?parseTime=true") if err != nil { log.Fatal(err) } // Check the database connection if err = db.Ping(); err != nil { log.Fatal(err) } } func main() { gRouter := mux.NewRouter() //Setup MySQL initDB() defer db.Close() gRouter.HandleFunc("/", Homepage) //Get Tasks gRouter.HandleFunc("/tasks", fetchTasks).Methods("GET") //Fetch Add Task Form gRouter.HandleFunc("/newtaskform", getTaskForm) //Add Task gRouter.HandleFunc("/tasks", addTask).Methods("POST") //Fetch Update Form gRouter.HandleFunc("/gettaskupdateform/{id}", getTaskUpdateForm).Methods("GET") //Update Task gRouter.HandleFunc("/tasks/{id}", updateTask).Methods("PUT", "POST") //Delete Task gRouter.HandleFunc("/tasks/{id}", deleteTask).Methods("DELETE") http.ListenAndServe(":4000", gRouter) } func Homepage(w http.ResponseWriter, r *http.Request) { tmpl.ExecuteTemplate(w, "home.html", nil) } func fetchTasks(w http.ResponseWriter, r *http.Request) { todos, _ := getTasks(db) //fmt.Println(todos) //If you used "define" to define the template, use the name you gave it here, not the filename tmpl.ExecuteTemplate(w, "todoList", todos) } func getTaskForm(w http.ResponseWriter, r *http.Request) { tmpl.ExecuteTemplate(w, "addTaskForm", nil) } func addTask(w http.ResponseWriter, r *http.Request) { task := r.FormValue("task") fmt.Println(task) query := "INSERT INTO tasks (task, done) VALUES (?, ?)" stmt, err := db.Prepare(query) if err != nil { log.Fatal(err) } defer stmt.Close() _, executeErr := stmt.Exec(task, 0) if executeErr != nil { log.Fatal(executeErr) } // Return a new list of Todos todos, _ := getTasks(db) //You can also just send back the single task and append it //I like returning the whole list just to get everything fresh, but this might not be the best strategy tmpl.ExecuteTemplate(w, "todoList", todos) } func getTaskUpdateForm(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) //Convert string id from URL to integer taskId, _ := strconv.Atoi(vars["id"]) task, err := getTaskByID(db, taskId) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } tmpl.ExecuteTemplate(w, "updateTaskForm", task) } func updateTask(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) taskItem := r.FormValue("task") //taskStatus, _ := strconv.ParseBool(r.FormValue("done")) var taskStatus bool fmt.Println(r.FormValue("done")) //Check the string value of the checkbox switch strings.ToLower(r.FormValue("done")) { case "yes", "on": taskStatus = true case "no", "off": taskStatus = false default: taskStatus = false } taskId, _ := strconv.Atoi(vars["id"]) task := Task{ taskId, taskItem, taskStatus, } updateErr := updateTaskById(db, task) if updateErr != nil { log.Fatal(updateErr) } //Refresh all Tasks todos, _ := getTasks(db) tmpl.ExecuteTemplate(w, "todoList", todos) } func deleteTask(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) taskId, _ := strconv.Atoi(vars["id"]) err := deleTaskWithID(db, taskId) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } //Return list todos, _ := getTasks(db) tmpl.ExecuteTemplate(w, "todoList", todos) } func getTasks(dbPointer *sql.DB) ([]Task, error) { query := "SELECT id, task, done FROM tasks" rows, err := dbPointer.Query(query) if err != nil { return nil, err } defer rows.Close() var tasks []Task for rows.Next() { var todo Task rowErr := rows.Scan(&todo.Id, &todo.Task, &todo.Done) if rowErr != nil { return nil, err } tasks = append(tasks, todo) } if err = rows.Err(); err != nil { return nil, err } return tasks, nil } func getTaskByID(dbPointer *sql.DB, id int) (*Task, error) { query := "SELECT id, task, done FROM tasks WHERE id = ?" var task Task row := dbPointer.QueryRow(query, id) err := row.Scan(&task.Id, &task.Task, &task.Done) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("No task was found with task %d", id) } return nil, err } return &task, nil } func updateTaskById(dbPointer *sql.DB, task Task) error { query := "UPDATE tasks SET task = ?, done = ? WHERE id = ?" result, err := dbPointer.Exec(query, task.Task, task.Done, task.Id) if err != nil { return err } rowsAffected, err := result.RowsAffected() if err != nil { return err } if rowsAffected == 0 { fmt.Println("No rows updated") } else { fmt.Printf("%d row(s) updated\n", rowsAffected) } return nil } func deleTaskWithID(dbPointer *sql.DB, id int) error { query := "DELETE FROM tasks WHERE id = ?" stmt, err := dbPointer.Prepare(query) if err != nil { return err } defer stmt.Close() result, err := stmt.Exec(id) if err != nil { return err } rowsAffected, err := result.RowsAffected() if err != nil { return err } if rowsAffected == 0 { return fmt.Errorf("no task found with id %d", id) } fmt.Printf("Deleted %d task(s)\n", rowsAffected) return nil }
Ya, itu adalah banyak kod. Jangan risau, kami akan mengambilnya dari bahagian paling atas dan berjalan ke bawah
Maka mula-mula kami mengimport semua pakej yang diperlukan. Pemacu MySQL dan penghala Gorilla Mux yang kami pasang, dan sekumpulan pakej daripada pustaka standard Go yang akan berguna dalam operasi kod kami.
import ( "database/sql" "fmt" "html/template" "log" "net/http" "strconv" "strings" _ "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" )
Seterusnya, kami mencipta pembolehubah tmpl yang akan digunakan untuk menyimpan templat kami yang dimuatkan dan pembolehubah db yang akan menjadi penunjuk kepada sambungan pangkalan data kami untuk menjalankan tugas pangkalan data. Kami kemudian mencipta struct Tugas tersuai yang mentakrifkan jenis tugas.
Di dalam fungsi init(), kami memuatkan semua templat kami daripada folder templat. Semua templat kami dijangka mempunyai sambungan .html kerana HTMX mengharapkan kami mengembalikan HTML, ini sangat masuk akal.
Teruskan dan buat folder templat di akar projek supaya kami boleh mula memuatkan semua templat kami dari sana.
Kami juga mempunyai fungsi initDB() yang menjaga penyediaan sambungan kami ke pangkalan data dan mengembalikan rujukan penuding ke pangkalan data kami. Pastikan untuk menukar rentetan sambungan agar sepadan dengan pangkalan data anda (kelayakan, hos, port, nama pangkalan data dll)
Di dalam fungsi utama, kami memulakan penghala kami dan memanggil fungsi pangkalan data initDB() kami untuk memulakan pangkalan data kami. Ini kemudian diikuti oleh semua laluan dan pengendali laluan kami dan akhirnya, kami mendengar pada port 4000 yang merupakan tempat kami akan menyampaikan aplikasi.
Laluan dan Pengendali
Sekarang mari kita mula memecahkan laluan kita dan pengendali masing-masing.
- The GET / Base Route: This is our base route and loads the home page of the application. The handler, Hompage returns the home.html file to the client.
- The GET /tasks Route: This route uses the fetchTasks handler to get all our tasks from our database and return them in an HTML list to the client using a todoList template.
- The GET /newtaskform Route: This route will load a new task form from the server each time a user wants to create a new task or clicks a Add New Task button. It uses a addTaskForm template to display a new HTML form for adding a new task
- The POST /tasks Route: This route calls the addTask handler to add a new task to the database and return an updated list of all tasks.
- The GET /gettaskupdateform/{id} Route: Uses the Id of a task to load the task into an update form with the updateTaskForm template and returns this form to the client when the user clicks the Edit button.
- The PUT/POST /tasks/{id} Route: Takes the Id of a task to be updated and updates it using the updateTask handler. After the update operation, the most recent version of the list is returned as HTML.
- The DELETE /tasks/{id} Route: Uses the deleteTask handler and a task Id to delete a specific task. Once the task is deleted, an updated list of tasks is returned back to the client.
And that’s all the routes and handlers used in this application.
You may have noticed some other functions asides the route handlers also defined in our main.go file. These are functions for performing database operations for fetching tasks (getTasks), getting a single task using its Id (getTaskByID), updating a task using its Id (updateTaskById), and deleting a task using the tasks’ Id (deleTaskWithID).
These helper functions are used within our route handlers to facilitate database operations and keep the handlers lean.
Creating Our Templates
Now that we are familiar with our Hypermedia API, let’s begin creating the HTML templates that will be retuned in the response to our API calls.
First, we create home.html file in the templates folder. This will load the home page of our task management application. Add the following code to the file after creating it.
<meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <script src="https://unpkg.com/htmx.org@1.9.12"></script> <title>To Do App</title> <div class="row"> <div class="col"> <h2 id="Tasks">Tasks</h2> <div> <a href="#" hx-get="/newtaskform" hx-target="#addTaskForm">Add New Item</a> </div> <div id="taskList" hx-get="/tasks" hx-trigger="load" hx-swap="innerHTML"> </div> </div> <!-- <div class="col"> </div> --> <div class="col"> <h2 id="Add-New-Task">Add New Task</h2> <div id="addTaskForm"> {{template "addTaskForm"}} </div> </div> </div>
This templates forms the shell and layout of the entire application. We have the boilerplate HTML structure and I have also added the Bootstrap CSS library for some basic styling. The HTMX library has also been included through a CDN link.
The application layout contains two sections. One section for displaying tasks and the other for showing the new task and task update forms.
The first section contains a button for requesting a new task form from the hypermedia API. Once the form is returned, we then use hx-target to load the form into the div with an id of addTaskForm in the forms section of the page.
<a href="#" hx-get="/newtaskform" hx-target="#addTaskForm">Add New Item</a>
The next component in the first section is the div where our tasks will be loaded into. This div uses hx-trigger to initiate a GET request to the /tasks route once the page loads, thus immediately loading the tasks into the page.
<div id="taskList" hx-get="/tasks" hx-trigger="load" hx-swap="innerHTML"> </div>
In the second section, as mentioned earlier, we have a div with an id of addTaskForm for loading both our new task and update forms. We have also preloaded the form for adding a new task into this div using Go template import syntax so as to have a default form in place.
Now let’s create the form for adding a new task next. Inside the templates folder, create the file addTaskForm.html and add the following code inside it:
{{define "addTaskForm"}}
This templates loads a fresh form in the UI for adding a new task. When the submit button is clicked, it uses HTMX to send a POST request to the /tasks route to add a new task. When the operation is done, it uses HTMX once again to load the response, an updated list of tasks, into the div with an id of taskList.
Next is our update form template. Inside the templates folder, create the file updateTaskForm.html and add the following code:
{{define "updateTaskForm"}}
This template takes in a task to be updated and uses it to pre-populate the update form so that the user can see the previous state of the task to be updated.
When the Update Task button is clicked, it will send the updated values to the hypermedia API for the task to be updated. Once updated, it loads the updated list into the page.
Finally, we create the template the returns our list of task items. Inside the templates folder, create the file todoList.html and add the following code:
{{define "todoList"}}
Yeah, a lot is going on in this template, so let’s break it down.
First, the template takes in a Go slice of Task types and loops over it using the range function to create an HTML list of unordered items.
The task it displayed in each list item and the Done property is used to check if the task is completed. If so, we use CSS to strike the task as being completed.
Just after the task text, we have an Edit button. This button calls the /gettaskupdateform endpoint to load an update form using the id of the specific task that was clicked. The user can then update the task and get an updated list of task items.
After the Edit button, we have a Delete button that uses hx-delete to call the DELETE /tasks/{id} endpoint so that we can delete the task. But before we can send the delete request, we use hx-confirm to display a confirmation dialog to the user so that they can confirm if they really want to delete this task item. Once deleted, a new updated list is returned and the task will be gone.
And with that we wrap up our application, so let’s move on to the fun part, checking it out.
Running the Application
With all the code in place, now let’s test our application.
Ensure that all files are saved and run the following command at the root of your project:
go run main.go
Now go to your browser and load the application page at http://localhost:4000. If you have used a different port, ensure that you’re using that port to load the app.
Now you should see your application as displayed below. See below as we add a new task, update an existing task and delete a task from our task list
Conclusion
If you have enjoyed this article, and will like to learn more about building projects with HTMX, I’ll like you to check out HTMX + Go: Build Fullstack Applications with Golang and HTMX, and The Complete HTMX Course: Zero to Pro with HTMX to further expand your knowledge on building hypermedia-driven applications with HTMX.
Happy Coding :)
Atas ialah kandungan terperinci HTMX + Go : Bina Apl CRUD dengan Golang dan HTMX. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas











Golang lebih baik daripada Python dari segi prestasi dan skalabiliti. 1) Ciri-ciri jenis kompilasi Golang dan model konkurensi yang cekap menjadikannya berfungsi dengan baik dalam senario konvensional yang tinggi. 2) Python, sebagai bahasa yang ditafsirkan, melaksanakan perlahan -lahan, tetapi dapat mengoptimumkan prestasi melalui alat seperti Cython.

Golang lebih baik daripada C dalam kesesuaian, manakala C lebih baik daripada Golang dalam kelajuan mentah. 1) Golang mencapai kesesuaian yang cekap melalui goroutine dan saluran, yang sesuai untuk mengendalikan sejumlah besar tugas serentak. 2) C Melalui pengoptimuman pengkompil dan perpustakaan standard, ia menyediakan prestasi tinggi yang dekat dengan perkakasan, sesuai untuk aplikasi yang memerlukan pengoptimuman yang melampau.

GoisidealforbeginnersandSuekableforcloudandnetworkservicesduetoitssimplicity, kecekapan, danconcurrencyfeatures.1) installgofromtheofficialwebsiteandverifywith'goversion'.2)

Golang sesuai untuk pembangunan pesat dan senario serentak, dan C sesuai untuk senario di mana prestasi ekstrem dan kawalan peringkat rendah diperlukan. 1) Golang meningkatkan prestasi melalui pengumpulan sampah dan mekanisme konvensional, dan sesuai untuk pembangunan perkhidmatan web yang tinggi. 2) C mencapai prestasi muktamad melalui pengurusan memori manual dan pengoptimuman pengkompil, dan sesuai untuk pembangunan sistem tertanam.

Goimpactsdevelopmentpositivielythroughspeed, efficiency, andsimplicity.1) Speed: goCompilesquicklyandrunsefficiently, idealforlargeproject.2) Kecekapan: ITSComprehensivestandardlibraryraryrarexternaldependencies, enhingdevelyficiency.

C lebih sesuai untuk senario di mana kawalan langsung sumber perkakasan dan pengoptimuman prestasi tinggi diperlukan, sementara Golang lebih sesuai untuk senario di mana pembangunan pesat dan pemprosesan konkurensi tinggi diperlukan. Kelebihan 1.C terletak pada ciri-ciri perkakasan dan keupayaan pengoptimuman yang tinggi, yang sesuai untuk keperluan berprestasi tinggi seperti pembangunan permainan. 2. Kelebihan Golang terletak pada sintaks ringkas dan sokongan konvensional semulajadi, yang sesuai untuk pembangunan perkhidmatan konvensional yang tinggi.

Golang dan Python masing -masing mempunyai kelebihan mereka sendiri: Golang sesuai untuk prestasi tinggi dan pengaturcaraan serentak, sementara Python sesuai untuk sains data dan pembangunan web. Golang terkenal dengan model keserasiannya dan prestasi yang cekap, sementara Python terkenal dengan sintaks ringkas dan ekosistem perpustakaan yang kaya.

Perbezaan prestasi antara Golang dan C terutamanya ditunjukkan dalam pengurusan ingatan, pengoptimuman kompilasi dan kecekapan runtime. 1) Mekanisme pengumpulan sampah Golang adalah mudah tetapi boleh menjejaskan prestasi, 2) Pengurusan memori manual C dan pengoptimuman pengkompil lebih cekap dalam pengkomputeran rekursif.
