Htmx allows us to handle async form submissions with ease by using hx-post
attribute. In background it will issue a post request to the url specified in hx-post
attribute and replace the element with the response.
Setup with Gofiber
Initialize go module and install dependencies
go mod init htmx-form
go get github.com/gofiber/fiber/v2
go get github.com/gofiber/template/html/v2
Setup Server
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/gofiber/template/html/v2"
)
func main() {
// Setup html template engine
engine := html.New("./views", ".html")
app := fiber.New(fiber.Config{
Views: engine,
})
// Get request to render index page
app.Get("/", func(c *fiber.Ctx) error {
values := fiber.Map{
"EmailErr": "",
"PasswordErr": "",
"Success": "",
"Error": "",
}
// Render index template
return c.Render("index", values)
})
// Post request to handle form submission
app.Post("/form", func(c *fiber.Ctx) error {
// Get form values
email := c.FormValue("email")
password := c.FormValue("password")
// Setup values to be passed to template
values := fiber.Map{
"EmailValue": email,
"EmailErr": "",
"PasswordErr": "",
"Success": "",
"Error": "",
}
// Validate form values
if email == "" {
values["EmailErr"] = "Email is required"
}
if password == "" {
values["PasswordErr"] = "Password is required"
}
if email != "" && password != "" {
values["EmailValue"] = ""
values["Success"] = "Validation success"
} else {
values["Error"] = "Validation error"
}
// Render index template
return c.Render("form", values)
})
log.Fatal(app.Listen(":3000"))
}
Setup Views
views/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bootstrap demo</title>
<!-- bootstrap -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
crossorigin="anonymous"
/>
</head>
<body>
<main class="container pt-5">{{ template "form" . }}</main>
<!-- bootstrap -->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"
></script>
<!-- htmx -->
<script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
</body>
</html>
views/form.html
form template with rendering logic
<form
id="user-form"
hx-post="/form"
autocomplete="off"
>
{{ if ne .Success "" }}
<div class="alert alert-success" role="alert">{{.Success}}</div>
{{ end }} {{ if ne .Error "" }}
<div class="alert alert-danger" role="alert">{{.Error}}</div>
{{ end }}
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input
type="email"
class="form-control{{ if ne .EmailErr "" }} is-invalid{{ end }}"
id="exampleInputEmail1"
aria-describedby="emailHelp"
name="email"
value="{{.EmailValue}}"
/>
<div id="emailHelp" class="form-text">
We'll never share your email with anyone else.
</div>
{{ if ne .EmailErr "" }}
<div class="invalid-feedback">{{.EmailErr}}</div>
{{ end }}
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Password</label>
<input
type="password"
name="password"
class="form-control{{ if ne .PasswordErr "" }} is-invalid{{end}}"
id="exampleInputPassword1"
/>
{{ if ne .PasswordErr "" }}
<div class="invalid-feedback">{{.PasswordErr}}</div>
{{ end }}
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
This setup will render a page with a form. On submitting the form, it will validate the form values and render the form with validation errors or success message.