Eine Frage, die mir im Zusammenhang mit HTMX häufig gestellt wird, insbesondere bei Entwicklern, die die Bibliothek gerade erst ausprobiert haben, lautet: „Aber was kann man damit wirklich bauen?“
Gute Frage, und in diesem Artikel beginnen wir in kleinen Schritten mit dem Erstellen einer datenbankgestützten CRUD-Anwendung mit HTMX und Go als unserer Backend-Sprache.
Übrigens, wenn Sie wirklich eine praktische projektbasierte Anleitung zum Erstellen von Fullstack-Apps mit HTMX wünschen, schauen Sie sich meinen Kurs **HTMX + Go: Fullstack-Anwendungen mit Golang und HTMX erstellen [Rabatt inbegriffen] an.**
Lasst uns beginnen.
Ich würde es gerne eine Task-Management-Anwendung nennen, aber ich weiß, dass Sie bereits gedacht haben, dass das nur ein schicker Name für eine andere Todo-Anwendung ist. Keine Sorge, Todo-Apps eignen sich hervorragend zum Erlernen grundlegender Vorgänge mit Sprachen, Bibliotheken und Frameworks, daher werden wir dieselbe bewährte und bewährte Strategie verwenden.
Unsere Anwendung kann Folgendes tun:
Also zuerst brauchen wir eine Datenbank und für dieses Demoprojekt werde ich MySQL verwenden. Fühlen Sie sich frei, eine beliebige Datenbank Ihrer Wahl zu verwenden und nehmen Sie die erforderlichen Codeänderungen vor, um auf Ihre Datenbank zu verweisen, während Sie diesem Artikel folgen.
Wir werden die Dinge einfach halten, kein kompliziertes Schemadesign. Zuerst erstellen wir eine Datenbank mit dem Namen testdb und innerhalb dieser Datenbank erstellen wir eine Todos-Tabelle (Sie können Ihrer Datenbank und Tabelle gerne einen beliebigen Namen geben, aber stellen Sie sicher, dass Sie in Ihren SQL-Anweisungen dieselben Namen verwenden)
Implementieren Sie in der Todos-Tabelle das folgende Schema:
Sie können die Datenbanktabelle mit einigen Aufgaben versehen, damit wir einige Aufgaben sehen können, wenn wir die Anwendung zum ersten Mal laden.
Um mit der Einrichtung unserer kleinen Anwendung zu beginnen, erstellen Sie an einem beliebigen geeigneten Ort auf Ihrem Entwicklungscomputer einen Ordner für das Projekt.
mkdir task-management
Führen Sie den folgenden Befehl im Stammverzeichnis des Projektordners aus, um ihn als Golang-Projekt zu initialisieren:
go mod init task-management
Als nächstes müssen wir einige Abhängigkeiten installieren. Wir wissen bereits, dass wir MySQL als Datenbank verwenden, daher müssen wir den MySQL-Treiber für Golang installieren.
Wir müssen außerdem den Gorilla Mux Router installieren, der als Routing-Bibliothek für unser Projekt dient. Führen Sie die beiden folgenden Befehle im Stammverzeichnis Ihres Projekts aus, um diese Bibliotheken in Ihrem Projekt zu installieren
MySQL:
go get -u github.com/go-sql-driver/mysql
Gorilla Mux:
go get -u github.com/gorilla/mux
Wenn diese Bibliotheken vorhanden sind, erstellen Sie Ihre main.go-Datei im Stammverzeichnis des Projekts und fügen Sie den folgenden Code hinzu:
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 }
Ja, das war eine Menge Code. Keine Sorge, wir nehmen es von ganz oben und gehen nach unten
Also importieren wir zunächst alle notwendigen Pakete. Den von uns installierten MySQL-Treiber und den Gorilla-Mux-Router sowie eine Reihe von Paketen aus der Go-Standardbibliothek, die bei unseren Codeoperationen nützlich sein werden.
import ( "database/sql" "fmt" "html/template" "log" "net/http" "strconv" "strings" _ "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" )
Als nächstes erstellen wir eine tmpl-Variable, die zum Speichern unserer geladenen Vorlagen verwendet wird, und eine db-Variable, die als Zeiger auf unsere Datenbankverbindung zum Ausführen von Datenbankaufgaben dient. Anschließend erstellen wir eine benutzerdefinierte Aufgabenstruktur, die einen Aufgabentyp definiert.
Innerhalb der Funktion init() laden wir alle unsere Vorlagen aus einem Vorlagenordner. Von allen unseren Vorlagen wird erwartet, dass sie die Erweiterung .html haben, da HTMX von uns erwartet, dass wir HTML zurückgeben, was sehr sinnvoll ist.
Erstellen Sie den Vorlagenordner im Stammverzeichnis des Projekts, damit wir von dort aus mit dem Laden aller unserer Vorlagen beginnen können.
Wir haben auch eine initDB()-Funktion, die sich um den Aufbau unserer Verbindung zur Datenbank kümmert und eine Zeigerreferenz auf unsere Datenbank zurückgibt. Stellen Sie sicher, dass Sie die Verbindungszeichenfolge so ändern, dass sie mit der Ihrer Datenbank übereinstimmt (Anmeldeinformationen, Host, Port, Datenbankname usw.)
Innerhalb der Hauptfunktion initialisieren wir unseren Router und rufen unsere Datenbankfunktion initDB() auf, um unsere Datenbank zu initialisieren. Darauf folgen alle unsere Routen und Routenhandler und schließlich lauschen wir auf Port 4000, wo wir die Anwendung bereitstellen werden.
Jetzt beginnen wir mit der Aufschlüsselung unserer Routen und ihrer jeweiligen Handler.
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.
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>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>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.
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
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 :)
Das obige ist der detaillierte Inhalt vonHTMX + Go: Erstellen Sie eine CRUD-App mit Golang und HTMX. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!