Skip to content
/ muxt Public

Register http.ServeMux handlers from your Go Templates

License

Notifications You must be signed in to change notification settings

crhntr/muxt

Repository files navigation

Muxt Go Reference Go

Given main.go and index.gohtml, muxt will generate template_routes.go.

index.gohtml

<!DOCTYPE html>
<html lang="en">
{{block "head" "example"}}
<head>
    <meta charset='UTF-8'/>
    <title>{{.}}</title>
    <script src='https://unpkg.com/[email protected]' integrity='sha384-QWGpdj554B4ETpJJC9z+ZHJcA/i59TyjxEPXiiUgN2WmTyV5OEZWCD6gQhgkdpB/' crossorigin='anonymous'></script>
    <script src='https://unpkg.com/[email protected]/response-targets.js'></script>

    <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css'>
</head>
{{end}}
<body hx-ext='response-targets'>
<main class='container'>
    <table>
        <thead>
        <tr>
            <th>Fruit</th>
            <th>Count</th>
        </tr>
        </thead>
        <tbody hx-target="closest tr" hx-swap="outerHTML">

        {{- define "fruit row" -}}
        <tr>
            <td>{{ .Name }}</td>
            <td id="count" hx-get='/fruits/{{.Name}}/edit'>{{ .Value }}</td>
        </tr>
        {{- end -}}

        {{range .}}
        {{template "fruit row" .}}
        {{end}}

        {{- define "GET /{$} List(ctx)" -}}
        {{template "index.gohtml" .}}
        {{- end -}}

        {{- define "GET /fruits/{id}/edit GetFormEditRow(id)" -}}
        <tr>
            <td>{{ .Row.Name }}</td>
            <td>
                <form hx-patch='/fruits/{{.Row.Name}}'>
                    <input aria-label='Count' type='number' name='count' value='{{ .Row.Value }}' step='1' min='0'>
                    <input type='submit' value='Update'>
                </form>
                <p id='error'>{{.Error}}</p>
            </td>
        </tr>
        {{- end -}}

        {{- define "PATCH /fruits/{id} SubmitFormEditRow(id, form)" }}
        {{- if .Error -}}
        {{template  "GET /fruits/{id}/edit GetFormEditRow(id)" .}}
        {{- else -}}
        {{template "fruit row" .Row}}
        {{- end -}}
        {{ end -}}

        </tbody>
    </table>
</main>
</body>
</html>

{{define "GET /help"}}
<!DOCTYPE html>
<html lang='us-en'>
{{template "head" "Help"}}
<body>
<main class='container'>
    Hello, help!
</main>
</body>
</html>
{{end}}

main.go

In your Go code focus on your domain. Let muxt generate handlers and wire them up with the HTTP mux (multiplexer).

package main

import (
	"context"
	"embed"
	"fmt"
	"html/template"
	"log"
	"net/http"
)

//go:embed *.gohtml
var templateSource embed.FS

var templates = template.Must(template.ParseFS(templateSource, "*"))

type Backend struct {
	data []Row
}

type EditRowPage struct {
	Row   Row
	Error error
}

type EditRow struct {
	Value int `name:"count"`
}

func (b *Backend) SubmitFormEditRow(fruitID int, form EditRow) EditRowPage {
	if fruitID < 0 || fruitID >= len(b.data) {
		return EditRowPage{Error: fmt.Errorf("fruit not found")}
	}
	row := b.data[fruitID]
	row.Value = form.Value
	return EditRowPage{Error: nil, Row: row}
}

func (b *Backend) GetFormEditRow(fruitID int) EditRowPage {
	if fruitID < 0 || fruitID >= len(b.data) {
		return EditRowPage{Error: fmt.Errorf("fruit not found")}
	}
	return EditRowPage{Error: nil, Row: b.data[fruitID]}
}

type Row struct {
	Name  string
	Value int
}

func (b *Backend) List(_ context.Context) []Row { return b.data }

//go:generate muxt generate --receiver-static-type Backend

func main() {
	backend := &Backend{
		data: []Row{
			{Name: "Peach", Value: 10},
			{Name: "Plum", Value: 20},
			{Name: "Pineapple", Value: 2},
		},
	}
	mux := http.NewServeMux()
	routes(mux, backend)
	log.Fatal(http.ListenAndServe(":8080", mux))
}

template_routes.go

This file is generated by running go generate in the same directory as main.go

// Code generated by muxt. DO NOT EDIT.

package main

import (
	"context"
	"net/http"
	"strconv"
	"bytes"
)

type RoutesReceiver interface {
	SubmitFormEditRow(fruitID int, form EditRow) EditRowPage
	GetFormEditRow(fruitID int) EditRowPage
	List(_ context.Context) []Row
}

func routes(mux *http.ServeMux, receiver RoutesReceiver) {
	mux.HandleFunc("PATCH /fruits/{id}", func(response http.ResponseWriter, request *http.Request) {
		idParsed, err := strconv.Atoi(request.PathValue("id"))
		if err != nil {
			http.Error(response, err.Error(), http.StatusBadRequest)
			return
		}
		id := idParsed
		request.ParseForm()
		var form EditRow
		{
			value, err := strconv.Atoi(request.FormValue("count"))
			if err != nil {
				http.Error(response, err.Error(), http.StatusBadRequest)
				return
			}
			form.Value = value
		}
		data := receiver.SubmitFormEditRow(id, form)
		execute(response, request, true, "PATCH /fruits/{id} SubmitFormEditRow(id, form)", http.StatusOK, data)
	})
	mux.HandleFunc("GET /fruits/{id}/edit", func(response http.ResponseWriter, request *http.Request) {
		idParsed, err := strconv.Atoi(request.PathValue("id"))
		if err != nil {
			http.Error(response, err.Error(), http.StatusBadRequest)
			return
		}
		id := idParsed
		data := receiver.GetFormEditRow(id)
		execute(response, request, true, "GET /fruits/{id}/edit GetFormEditRow(id)", http.StatusOK, data)
	})
	mux.HandleFunc("GET /help", func(response http.ResponseWriter, request *http.Request) {
		execute(response, request, true, "GET /help", http.StatusOK, request)
	})
	mux.HandleFunc("GET /{$}", func(response http.ResponseWriter, request *http.Request) {
		ctx := request.Context()
		data := receiver.List(ctx)
		execute(response, request, true, "GET /{$} List(ctx)", http.StatusOK, data)
	})
}
func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {
	buf := bytes.NewBuffer(nil)
	if err := templates.ExecuteTemplate(buf, name, data); err != nil {
		http.Error(response, err.Error(), http.StatusInternalServerError)
		return
	}
	if writeHeader {
		response.Header().Set("content-type", "text/html; charset=utf-8")
		response.WriteHeader(code)
	}
	_, _ = buf.WriteTo(response)
}