3.2 KiB
malago
personal anime tracking platform. go/htmx rewrite.
stack
- go standard library (
net/http) - htmx + templ templates
- sqlite + sqlc
- tailwind (dark theme)
- jikan api (myanimelist)
structure
cmd/server/ main entry
internal/
auth/ sessions, passwords
database/ sqlc generated, migrations
handlers/ http handlers by domain
middleware/ auth, logging
jikan/ api client
templates/ templ components
migrations/ sql files
static/ css, js
go patterns
errors
always handle errors explicitly. wrap with context using fmt.Errorf:
if err != nil {
return fmt.Errorf("failed to fetch user: %w", err)
}
use errors.Is and errors.As to check wrapped errors.
early returns
reduce nesting. check errors first, return early:
func getUser(id string) (*User, error) {
if id == "" {
return nil, ErrInvalidID
}
user, err := db.FindUser(id)
if err != nil {
return nil, err
}
return user, nil
}
defer for cleanup
always close resources with defer:
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
interfaces
accept interfaces, return structs. keep interfaces small:
type Reader interface {
Read(p []byte) (n int, err error)
}
naming
- short, lowercase package names:
auth,jikan,db MixedCapsfor exports,mixedCapsfor internal- getters:
Owner()notGetOwner() - interfaces: single method = method name +
ersuffix (Reader,Writer)
zero values
design structs so zero value is useful:
var buf bytes.Buffer // ready to use, no init needed
buf.WriteString("hello")
composition over inheritance
embed types to compose behavior:
type Handler struct {
db *database.Queries
jikan *jikan.Client
logger *slog.Logger
}
htmx
check for htmx requests:
func isHTMX(r *http.Request) bool {
return r.Header.Get("HX-Request") == "true"
}
return partials for htmx, full pages otherwise. use hx-swap-oob for multiple updates. trigger toasts with HX-Trigger header.
templ
render components directly to response:
func (h *Handler) Home(w http.ResponseWriter, r *http.Request) {
templates.HomePage(data).Render(r.Context(), w)
}
pass data explicitly. keep components focused. use layouts.
database
sqlc generates type-safe queries. always use parameterized queries.
tables: user, session, account, anime, watch_list_entry
watch statuses: watching, completed, on_hold, dropped, plan_to_watch
jikan api
rate limit: 3 req/sec max. stagger batch requests. cache in local db.
commands
make dev # hot reload (air)
make build # binary
make test # tests
make migrate # run migrations
make sqlc # generate code
make create-user # cli user creation
env
DATABASE_FILE=malago.db
SESSION_SECRET=min_32_chars_random
PORT=3000
avoid
- panics in handlers
- forgetting
defer resp.Body.Close() - unstaggered jikan requests (429 errors)
- globals for config/state
- large monolithic templates