ホームページ  >  記事  >  バックエンド開発  >  HTMX + Go : Golang と HTMX を使用して CRUD アプリを構築する

HTMX + Go : Golang と HTMX を使用して CRUD アプリを構築する

PHPz
PHPzオリジナル
2024-07-17 07:24:39870ブラウズ

HTMX について、特にこのライブラリを試したばかりの開発者の間でよく聞かれる質問の 1 つは、「しかし、実際にそれを使って何を構築できるのですか?」

素晴らしい質問です。この記事では、バックエンド言語として HTMX と Go を使用して、データベースをサポートする CRUD アプリケーションを構築することから始めます。

ところで、HTMX を使用してフルスタック アプリを構築するための実践的なプロジェクトベースのガイドが本当に必要な場合は、私の **HTMX + Go: Build Fullstack Applications with Golang and HTMX [割引あり] コースをご覧ください。**

始めましょう。

私たちは正確に何を構築しているのでしょうか?

私はこれをタスク管理アプリケーションと呼びたいと思いますが、それが別の Todo アプリケーションの単なる派手な名前であることはすでに理解していると思います。心配しないでください。Todo アプリは、言語、ライブラリ、フレームワークを使用した基本的な操作を学習するのに最適であるため、テスト済みで信頼できる同じ戦略を使用します。

私たちのアプリケーションは次のことを行うことができます:

  • タスクを表示
  • 新しいタスクを追加
  • 既存のタスクを更新して…
  • タスクを削除する

データベースのセットアップ

まず、データベースが必要です。このデモ プロジェクトでは、MySQL を使用します。この記事を読み進めながら、任意のデータベースを自由に使用し、必要なコード変更を加えてデータベースを参照してください。

複雑なスキーマ設計を行わず、物事をシンプルにしていきます。まず、testdb という名前のデータベースを作成し、このデータベース内に todos テーブルを作成します (データベースとテーブルには任意の名前を付けて構いませんが、SQL ステートメントでは必ず同じ名前を使用してください)

todos テーブル内に、以下のスキーマを実装します。

  • id: PK、自動インクリメント
  • task : VARCHAR(200) - タスク項目
  • が含まれます
  • 完了: INT(1)、デフォルト = 0 (ブールフィールド)

アプリケーションを初めてロードするときにいくつかのタスクの表示を開始できるように、データベース テーブルにいくつかのタスクをシードすることを選択できます。

ハイパーメディア API の作成

小さなアプリケーションのセットアップを開始するには、開発用コンピューター内の任意の場所にプロジェクト用のフォルダーを作成します。

mkdir task-management

プロジェクト フォルダーのルートで次のコマンドを実行して、Golang プロジェクトとして初期化します。

go mod init task-management

次に、いくつかの依存関係をインストールする必要があります。データベースとして MySQL を使用していることはすでにわかっているため、Golang 用の MySQL ドライバーをインストールする必要があります。

プロジェクトのルーティング ライブラリとなる Gorilla Mux Router もインストールする必要があります。プロジェクトのルートで以下の 2 つのコマンドを実行して、これらのライブラリをプロジェクトにインストールします

MySQL:

go get -u github.com/go-sql-driver/mysql

ゴリラ・マックス:

go get -u github.com/gorilla/mux

これらのライブラリを配置したら、プロジェクトのルートに main.go ファイルを作成し、以下のコードを追加します。

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

}

ええ、それはたくさんのコードでした。心配しないでください、一番上から取って下ります

それでは、まず必要なパッケージをすべてインポートします。インストールした MySQL ドライバーと Gorilla Mux ルーター、およびコード操作に役立つ Go 標準ライブラリの多数のパッケージ。

import (
    "database/sql"
    "fmt"
    "html/template"
    "log"
    "net/http"
    "strconv"
    "strings"
    _ "github.com/go-sql-driver/mysql"
    "github.com/gorilla/mux"
) 

次に、ロードされたテンプレートを保持するために使用される tmpl 変数と、データベース タスクを実行するためのデータベース接続へのポインターとなる db 変数を作成します。次に、タスク タイプを定義するカスタム タスク構造体を作成します。

init() 関数内で、テンプレート フォルダーからすべてのテンプレートを読み込みます。 HTMX は HTML を返すことを期待しているため、すべてのテンプレートには .html 拡張子が付いていることが期待されており、これは非常に理にかなっています。

プロジェクトのルートにテンプレート フォルダーを作成して、そこからすべてのテンプレートの読み込みを開始できるようにします。

データベースへの接続を設定し、データベースへのポインタ参照を返す initDB() 関数もあります。データベースの接続文字列 (認証情報、ホスト、ポート、データベース名など) と一致するように接続文字列を変更してください

メイン関数内でルーターを初期化し、initDB() データベース関数を呼び出してデータベースを初期化します。次に、すべてのルートとルート ハンドラーが続き、最後に、アプリケーションを提供するポート 4000 でリッスンします。

ルートとハンドラー

次に、ルートとそれぞれのハンドラーを詳しく見てみましょう。

  • 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>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"}}
{{end}}

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"}}
{{end}}

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"}}
{{end}}

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

HTMX + Go : Golang と HTMX を使用して CRUD アプリを構築する

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 :)

以上がHTMX + Go : Golang と HTMX を使用して CRUD アプリを構築するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
前の記事:ジオ - 後ほど次の記事:ジオ - 後ほど