|
6 | 6 |
|
7 | 7 | [](https://pkg.go.dev/github.com/hatmaxkit/hatmax) |
8 | 8 | [](https://github.com/hatmaxkit/hatmax/actions) |
9 | | -[](https://codecov.io/gh/hatmaxkit/hatmax) |
| 9 | +[](https://codecov.io/gh/hatmaxkit/hatmax) |
10 | 10 |
|
11 | | -**A lightweight Go library for building single-binary web applications.** |
| 11 | +**A composable Go toolkit for building web applications with consistent wiring, clear configuration boundaries, and predictable package integration.** |
12 | 12 |
|
13 | | -## What is HatMax? |
| 13 | +## Overview |
14 | 14 |
|
15 | | -HatMax is a focused library for building web applications that compile to a single executable and use Postgres as their primary database. It provides authentication primitives, lifecycle management, and structured logging as core features, letting you focus on your domain logic instead of reinventing infrastructure. |
| 15 | +HatMax provides practical, composable packages for building web applications in Go. |
16 | 16 |
|
17 | | -If you've built a few Go web apps and found yourself copying similar patterns (lifecycle management, auth flows, database setup, middleware chains), HatMax extracts those patterns into small, composable packages. It's designed for applications that benefit from simplicity: one binary, one database. |
| 17 | +It includes: |
18 | 18 |
|
19 | | -## Core Principles |
| 19 | +- Consistent constructors and dependency wiring across packages. |
| 20 | +- Practical building blocks that work together out of the box. |
| 21 | +- Composable roles and interfaces instead of hidden global state. |
| 22 | +- Postgres-first primitives for auth, scheduling, pubsub, and app lifecycle. |
20 | 23 |
|
21 | | -- **Single binary deployment** - Build applications that compile to one executable. Simple deployment, straightforward operations. |
22 | | -- **Just use Postgres** - Leverage Postgres for relational data, JSONB, full-text search, pub/sub, and queues. Start simple, add specialized tools only when truly needed. |
23 | | -- **PubSub** - Domain events with no external broker. Default implementation uses Postgres `LISTEN`/`NOTIFY`. |
24 | | -- **HTML + htmx** - Server-side rendering with htmx for dynamic interactions. Build modern UX with straightforward patterns. |
25 | | -- **Explicit over magic** - Every dependency visible in constructors, every middleware applied manually, every lifecycle hook opt-in. |
26 | | - |
27 | | -## What's Included |
28 | | - |
29 | | -- **Lifecycle management** - Component startup, shutdown, and route registration with automatic discovery |
30 | | -- **Authentication** - User signup, signin, sessions, password hashing, and middleware for protected routes |
31 | | -- **Structured logging** - Context-aware logging with multiple output formats |
32 | | -- **Configuration** - Load from YAML, environment variables, and command-line flags |
33 | | -- **Database** - Postgres connection pooling, migrations, transactions, and health checks |
34 | | -- **HTTP middleware** - Request tracking, logging, panic recovery, and CORS |
35 | | -- **Model utilities** - ID generation, timestamp helpers, and common patterns |
36 | | -- **Templates** - HTML rendering with embedded assets and htmx support |
37 | | -- **Type-safe queries** - Integration with sqlc for compile-time SQL validation |
38 | | -- **Testing patterns** - Comprehensive test coverage with practical examples |
39 | | - |
40 | | -## Architecture |
41 | | - |
42 | | -HatMax applications follow a straightforward pattern: |
43 | | - |
44 | | -``` |
45 | | -HTTP Request |
46 | | - ↓ |
47 | | -Handler (chi.Router) |
48 | | - ↓ |
49 | | -Service Layer (business logic) |
50 | | - ↓ |
51 | | -sqlc Queries (type-safe SQL) |
52 | | - ↓ |
53 | | -Postgres |
54 | | -``` |
55 | | - |
56 | | -### Direct Data Access |
57 | | - |
58 | | -Services use `sqlc`-generated code directly. Write SQL queries, generate type-safe Go code, and call those methods from your service layer. |
| 24 | +## Quick Start |
59 | 25 |
|
60 | 26 | ```go |
61 | | -type UserService struct { |
62 | | - queries *sqlc.Queries |
63 | | - log log.Logger |
64 | | -} |
65 | | - |
66 | | -func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) { |
67 | | - return s.queries.GetUserByID(ctx, id) |
| 27 | +package main |
| 28 | + |
| 29 | +import ( |
| 30 | + "context" |
| 31 | + "embed" |
| 32 | + "fmt" |
| 33 | + "os" |
| 34 | + |
| 35 | + "github.com/hatmaxkit/hatmax/app" |
| 36 | + "github.com/hatmaxkit/hatmax/config" |
| 37 | + "github.com/hatmaxkit/hatmax/db" |
| 38 | + "github.com/hatmaxkit/hatmax/log" |
| 39 | + "github.com/hatmaxkit/hatmax/mailer" |
| 40 | + "github.com/hatmaxkit/hatmax/pubsub/postgres" |
| 41 | +) |
| 42 | + |
| 43 | +//go:embed assets/* |
| 44 | +var assetsFS embed.FS |
| 45 | + |
| 46 | +func main() { |
| 47 | + cfg, err := config.Load("config.yml", "APP_", os.Args) // your env prefix |
| 48 | + if err != nil { |
| 49 | + fmt.Fprintf(os.Stderr, "cannot load config: %v\n", err) |
| 50 | + os.Exit(1) |
| 51 | + } |
| 52 | + |
| 53 | + ctx := context.Background() |
| 54 | + logger := log.NewLogger(cfg) |
| 55 | + router := app.NewRouter(logger, app.WithPing(), app.WithDebugRoutes()) |
| 56 | + |
| 57 | + // Infrastructure |
| 58 | + database := db.New(assetsFS, "postgres", cfg, logger) |
| 59 | + events := postgres.New(database, cfg, logger) |
| 60 | + mail := mailer.New(cfg, logger) |
| 61 | + |
| 62 | + // Application layer |
| 63 | + service := featname.NewService(database, events, mail, cfg, logger) |
| 64 | + handler := featname.NewHandler(service, cfg, logger) |
| 65 | + |
| 66 | + deps := []any{ |
| 67 | + database, |
| 68 | + events, |
| 69 | + mail, |
| 70 | + service, |
| 71 | + handler, |
| 72 | + } |
| 73 | + |
| 74 | + starts, stops, registrars := app.Setup(ctx, router, deps...) |
| 75 | + |
| 76 | + err = app.Start(ctx, logger, starts, stops, registrars, router) |
| 77 | + if err != nil { |
| 78 | + logger.Errorf("cannot start app: %v", err) |
| 79 | + os.Exit(1) |
| 80 | + } |
68 | 81 | } |
69 | 82 | ``` |
70 | 83 |
|
71 | | -### Clear Dependencies |
72 | | - |
73 | | -Constructor signatures reveal what each component needs to function. |
74 | | - |
75 | | -```go |
76 | | -func NewUserHandler( |
77 | | - authSvc *AuthService, |
78 | | - userSvc *UserService, |
79 | | - tmpl *TemplateManager, |
80 | | - cfg *Config, |
81 | | - log log.Logger, |
82 | | -) *UserHandler |
83 | | -``` |
84 | | - |
85 | | -### Lifecycle Discovery |
| 84 | +## Package Map |
86 | 85 |
|
87 | | -Components implement interfaces to declare their capabilities. The `Setup` function discovers these capabilities, and `Start` orchestrates initialization with rollback on failure. |
| 86 | +| Package | Role | |
| 87 | +| --------------------- | ---------------------------------------------------------------------- | |
| 88 | +| `app` | Lifecycle orchestration and component startup/shutdown | |
| 89 | +| `config` | Static configuration loading and structure | |
| 90 | +| `settings` | Dynamic runtime settings and attributes | |
| 91 | +| `db` | Postgres connection, migration helpers, DB wiring | |
| 92 | +| `auth` | Authentication, sessions, auth middleware primitives | |
| 93 | +| `mailer` | Pluggable mail delivery providers (SES, SendGrid, Mailgun, SMTP, Noop) | |
| 94 | +| `scheduler` | Background job scheduling and execution | |
| 95 | +| `pubsub` | Event publication/subscription (including Postgres implementation) | |
| 96 | +| `web` / `htmx` / `ui` | HTTP, template rendering, htmx helpers, UI primitives | |
88 | 97 |
|
89 | | -```go |
90 | | -type Startable interface { Start(context.Context) error } |
91 | | -type Stoppable interface { Stop(context.Context) error } |
92 | | -type RouteRegistrar interface { RegisterRoutes(chi.Router) } |
93 | | - |
94 | | -// In main.go |
95 | | -database := db.New(assetsFS, "postgres", cfg, logger) |
96 | | -authService := auth.NewService(database, cfg, logger) |
97 | | -userHandler := user.NewHandler(authService, assetsFS, cfg, logger) |
98 | | - |
99 | | -deps := []any{database, authService, userHandler} |
100 | | -starts, stops, registrars := app.Setup(ctx, router, deps...) |
101 | | -app.Start(ctx, logger, starts, stops, registrars, router) |
102 | | -``` |
103 | | - |
104 | | -## Persistence |
105 | | - |
106 | | -HatMax follows the "Just Use Postgres" philosophy, leveraging its capabilities to handle most application needs: |
107 | | - |
108 | | -- **Relational data** - Standard tables and foreign keys |
109 | | -- **Semi-structured data** - JSONB columns with indexing |
110 | | -- **Full-text search** - `tsvector` and `pg_trgm` |
111 | | -- **Pub/sub** - `LISTEN`/`NOTIFY` for real-time updates |
112 | | -- **Queues** - Advisory locks + queue tables for background jobs |
113 | | -- **Geospatial** - PostGIS extension for location data |
114 | | -- **Time-series** - TimescaleDB extension for metrics |
115 | | - |
116 | | -Queries are written in SQL and type-checked with `sqlc`. This keeps the data layer explicit and maintainable. As your application grows, you can integrate specialized tools where they provide clear value. |
117 | | - |
118 | | -## Scope |
119 | | - |
120 | | -HatMax is focused on single-binary applications with straightforward patterns: |
| 98 | +## Interfaces |
121 | 99 |
|
122 | | -- **Database** - Postgres-first approach with sqlc for type-safe queries |
123 | | -- **Architecture** - Monolithic applications that deploy as one executable |
124 | | -- **Presentation** - HTML templates with htmx for interactive experiences |
125 | | -- **Data layer** - Direct SQL queries, explicit service coordination |
126 | | -- **Authorization** - Core auth primitives as building blocks |
127 | | -- **Interface** - Standard HTTP handlers and middleware |
| 100 | +Small interfaces make components swappable: |
128 | 101 |
|
129 | | -## Documentation |
| 102 | +| Component | Interface | Implementations | |
| 103 | +|-----------|-----------|-----------------| |
| 104 | +| Mail | `Mailer` | SMTP, SES, SendGrid, Mailgun | |
| 105 | +| Events | `Publisher`, `Subscriber` | Postgres | |
| 106 | +| Jobs | `JobStore` | Postgres | |
130 | 107 |
|
131 | | -- [Features](docs/features.md) - Package overview |
132 | | -- [Gallery](docs/gallery.md) - Visual examples |
133 | | - |
134 | | -## Development |
135 | | - |
136 | | -### Running Tests |
137 | | - |
138 | | -```bash |
139 | | -# Run all tests |
140 | | -make test |
141 | | - |
142 | | -# Run tests with coverage by package |
143 | | -make test-coverage |
144 | | - |
145 | | -# Display coverage table by package |
146 | | -make test-coverage-summary |
147 | | - |
148 | | -# Generate HTML coverage report |
149 | | -make test-coverage-html |
150 | | - |
151 | | -# Run all quality checks (format, vet, test, coverage, lint) |
152 | | -make check |
153 | | -``` |
| 108 | +## Key Patterns |
154 | 109 |
|
155 | | -## Status |
| 110 | +- **Transactional startup**: If component N fails to start, components 0→N-1 stop in reverse order automatically. |
| 111 | +- **Two config layers**: Static config at startup, dynamic settings with schema validation at runtime. Use what you need. |
| 112 | +- **Just Use Postgres**: PubSub, scheduler, and sessions run on Postgres. Add Redis or RabbitMQ if you need them, but you probably don't. |
156 | 113 |
|
157 | | -HatMax is under active development. The library API is stabilizing but may change before v1.0. |
| 114 | +## Docs |
158 | 115 |
|
159 | | -**Current focus:** |
160 | | -- Core library packages (app, log, config, db, auth, middleware, model) |
161 | | -- Authentication primitives (signup, signin, sessions, middleware) |
162 | | -- Comprehensive test coverage and examples |
163 | | -- Example application demonstrating all features |
| 116 | +- [Features](docs/features.md) |
| 117 | +- [Gallery](docs/gallery.md) |
164 | 118 |
|
165 | 119 | ## License |
166 | 120 |
|
|
0 commit comments