Bookmark List Page
Server-rendered HTML page with form handling using html/template.
package main
import (
"html/template"
"log"
"net/http"
"strconv"
)
type Bookmark struct {
ID int
URL string
Title string
}
var bookmarks = []Bookmark{
{ID: 1, URL: "https://go.dev", Title: "Go"},
{ID: 2, URL: "https://pkg.go.dev", Title: "Go Packages"},
}
var nextID = 3
const listTmpl = `<!DOCTYPE html>
<html>
<head><title>Bookmarks</title></head>
<body>
<h1>Bookmarks ({{len .}})</h1>
<form method="POST" action="/bookmarks">
<input name="title" placeholder="Title" required>
<input name="url" type="url" placeholder="https://example.com" required>
<button type="submit">Add</button>
</form>
<ul>
{{range .}}
<li>
<a href="{{.URL}}">{{.Title}}</a>
<form method="POST" action="/bookmarks/{{.ID}}/delete" style="display:inline">
<button type="submit">✕</button>
</form>
</li>
{{else}}
<li>No bookmarks yet.</li>
{{end}}
</ul>
</body>
</html>`
var tmpl = template.Must(template.New("list").Parse(listTmpl))
func listPage(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
tmpl.Execute(w, bookmarks)
}
func createBookmark(w http.ResponseWriter, r *http.Request) {
title := r.FormValue("title")
url := r.FormValue("url")
if title != "" && url != "" {
bookmarks = append(bookmarks, Bookmark{ID: nextID, URL: url, Title: title})
nextID++
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func deleteBookmark(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.PathValue("id"))
for i, b := range bookmarks {
if b.ID == id {
bookmarks = append(bookmarks[:i], bookmarks[i+1:]...)
break
}
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", listPage)
mux.HandleFunc("POST /bookmarks", createBookmark)
mux.HandleFunc("POST /bookmarks/{id}/delete", deleteBookmark)
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}