{"id":50669200,"url":"https://github.com/dmitrymomot/forge","last_synced_at":"2026-06-08T09:04:42.145Z","repository":{"id":336100113,"uuid":"1147334129","full_name":"dmitrymomot/forge","owner":"dmitrymomot","description":"Opinionated Go framework for building micro-SaaS applications","archived":false,"fork":false,"pushed_at":"2026-03-04T18:19:29.000Z","size":8949,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-04T02:05:33.621Z","etag":null,"topics":["framework","go","golang","http","microservices","saas","web-framework"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmitrymomot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-01T15:33:46.000Z","updated_at":"2026-03-04T18:19:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dmitrymomot/forge","commit_stats":null,"previous_names":["dmitrymomot/forge"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/dmitrymomot/forge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitrymomot%2Fforge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitrymomot%2Fforge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitrymomot%2Fforge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitrymomot%2Fforge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmitrymomot","download_url":"https://codeload.github.com/dmitrymomot/forge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitrymomot%2Fforge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34055309,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","go","golang","http","microservices","saas","web-framework"],"created_at":"2026-06-08T09:04:41.238Z","updated_at":"2026-06-08T09:04:42.139Z","avatar_url":"https://github.com/dmitrymomot.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Forge\n\n[![CI](https://github.com/dmitrymomot/forge/actions/workflows/ci.yml/badge.svg)](https://github.com/dmitrymomot/forge/actions/workflows/ci.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/dmitrymomot/forge.svg)](https://pkg.go.dev/github.com/dmitrymomot/forge)\n[![Go Report Card](https://goreportcard.com/badge/github.com/dmitrymomot/forge)](https://goreportcard.com/report/github.com/dmitrymomot/forge)\n[![License](https://img.shields.io/github/license/dmitrymomot/forge)](LICENSE)\n\nA simple, opinionated Go framework for building micro-SaaS applications.\n\nForge is designed around the principle of \"no magic\" — it uses explicit, readable code with no reflection or service containers. The framework provides a thin orchestration layer while keeping business logic in plain Go handlers.\n\n## Installation\n\n```bash\ngo get github.com/dmitrymomot/forge\n```\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n    \"log\"\n    \"github.com/dmitrymomot/forge\"\n)\n\nfunc main() {\n    app := forge.New(\n        forge.AppConfig{},\n        forge.WithHandlers(\u0026MyHandler{}),\n    )\n\n    if err := forge.Run(\n        forge.RunConfig{Address: \":8080\"},\n        forge.WithFallback(app),\n    ); err != nil {\n        log.Fatal(err)\n    }\n}\n\ntype MyHandler struct{}\n\nfunc (h *MyHandler) Routes(r forge.Router) {\n    r.GET(\"/\", h.index)\n}\n\nfunc (h *MyHandler) index(c forge.Context) error {\n    return c.JSON(200, map[string]string{\"message\": \"Hello, World!\"})\n}\n```\n\n## Core Concepts\n\n### Handlers\n\nHandlers implement the `Handler` interface to declare routes:\n\n```go\ntype AuthHandler struct {\n    repo *repository.Queries\n}\n\nfunc NewAuth(repo *repository.Queries) *AuthHandler {\n    return \u0026AuthHandler{repo: repo}\n}\n\nfunc (h *AuthHandler) Routes(r forge.Router) {\n    r.GET(\"/login\", h.showLogin)\n    r.POST(\"/login\", h.handleLogin)\n    r.POST(\"/logout\", h.handleLogout)\n}\n\nfunc (h *AuthHandler) showLogin(c forge.Context) error {\n    return c.Render(http.StatusOK, views.LoginPage())\n}\n```\n\n### Context\n\nThe `Context` interface embeds `context.Context`, so it can be passed directly to any function expecting a standard library context. It also provides built-in helpers for common tasks:\n\n```go\nfunc (h *Handler) getUser(c forge.Context) error {\n    // c satisfies context.Context — pass it to DB calls, HTTP clients, etc.\n    user, err := h.repo.GetUser(c, c.UserID())\n    if err != nil {\n        return err\n    }\n    return c.JSON(200, user)\n}\n```\n\nContext carries everything you need for a request — logging, cookies, flash messages, domain info:\n\n```go\nfunc (h *Handler) updateSettings(c forge.Context) error {\n    c.LogInfo(\"updating settings\", \"user\", c.UserID(), \"domain\", c.Domain())\n\n    // Flash messages for post-redirect-get\n    c.SetFlash(\"success\", \"Settings saved!\")\n    return c.Redirect(http.StatusSeeOther, \"/settings\")\n}\n\nfunc (h *Handler) showSettings(c forge.Context) error {\n    var msg string\n    c.Flash(\"success\", \u0026msg) // reads and deletes flash\n\n    return c.Render(http.StatusOK, views.Settings(msg))\n}\n```\n\n### Type-Safe Parameters\n\nGeneric helpers provide type-safe access to URL and query parameters:\n\n```go\nfunc (h *Handler) listItems(c forge.Context) error {\n    page := forge.QueryDefault[int](c, \"page\", 1)\n    limit := forge.QueryDefault[int](c, \"limit\", 20)\n    id := forge.Param[int64](c, \"id\")\n\n    items, err := h.repo.ListItems(c, page, limit)\n    if err != nil {\n        return err\n    }\n    return c.JSON(http.StatusOK, items)\n}\n```\n\nSupported types: `~string`, `~int`, `~int64`, `~float64`, `~bool`.\n\n### Data Binding \u0026 Validation\n\nBind request data into structs with automatic sanitization and validation:\n\n```go\ntype CreateContact struct {\n    Name  string `form:\"name\"  validate:\"required;max:100\"`\n    Email string `form:\"email\" validate:\"required;email\"`\n    Phone string `form:\"phone\" sanitize:\"trim;numeric\"`\n}\n\nfunc (h *Handler) createContact(c forge.Context) error {\n    var req CreateContact\n    if errs, err := c.Bind(\u0026req); err != nil {\n        return err\n    } else if errs != nil {\n        return c.Render(http.StatusUnprocessableEntity, views.Form(errs))\n    }\n    // req is sanitized and validated\n    return h.repo.CreateContact(c, req.Name, req.Email, req.Phone)\n}\n```\n\nAlso available: `c.BindJSON()` for API endpoints and `c.BindQuery()` for query parameters.\n\n### Sessions \u0026 Authentication\n\nEnable server-side session management with automatic creation:\n\n```go\napp := forge.New(\n    forge.AppConfig{},\n    forge.WithSession(postgresStore,\n        forge.WithSessionTTL(7 * 24 * time.Hour),\n        forge.WithMaxSessionsPerUser(3),\n        forge.WithSessionFingerprint(\n            forge.FingerprintCookie,\n            forge.FingerprintWarn,\n        ),\n    ),\n)\n```\n\nAuthenticate users with `AuthenticateSession` — it sets the user ID, rotates the session token, and marks the session as authenticated in one call:\n\n```go\nfunc (h *Handler) login(c forge.Context) error {\n    // ...validate credentials...\n    if err := c.AuthenticateSession(user.ID); err != nil {\n        return err\n    }\n    return c.Redirect(http.StatusSeeOther, \"/dashboard\")\n}\n```\n\nThen use the built-in identity methods — no need to manually read session keys:\n\n```go\nfunc (h *Handler) dashboard(c forge.Context) error {\n    if !c.IsAuthenticated() {\n        return c.Redirect(http.StatusSeeOther, \"/login\")\n    }\n\n    // c.UserID() returns the authenticated user's ID\n    user, err := h.repo.GetUser(c, c.UserID())\n    if err != nil {\n        return err\n    }\n\n    canEdit := c.IsCurrentUser(user.ID)\n    return c.Render(http.StatusOK, views.Dashboard(user, canEdit))\n}\n```\n\nFor custom session data, use `SessionGet` and `SessionSet`:\n\n```go\nforge.SessionSet(c, \"theme\", \"dark\")\ntheme, ok := forge.SessionGet[string](c, \"theme\")\n```\n\nSession management:\n\n```go\nc.DestroySession()                // Logout current device\nc.DestroyOtherSessions()          // Logout all other devices\nc.DestroyAllSessions(c.UserID())  // Logout everywhere\nsessions, _ := c.ListSessions(c.UserID()) // Show active sessions\n```\n\n### RBAC\n\nConfigure role-based access control:\n\n```go\napp := forge.New(\n    forge.AppConfig{},\n    forge.WithRoles(\n        forge.RolePermissions{\n            \"admin\":  {\"users.read\", \"users.write\", \"billing.manage\"},\n            \"member\": {\"users.read\"},\n        },\n        func(c forge.Context) string {\n            return forge.ContextValue[string](c, roleKey{})\n        },\n    ),\n)\n```\n\nCheck permissions in handlers:\n\n```go\nfunc (h *Handler) deleteUser(c forge.Context) error {\n    if !c.Can(\"users.write\") {\n        return forge.ErrForbidden(\"You do not have permission\")\n    }\n    return h.repo.DeleteUser(c, forge.Param[string](c, \"id\"))\n}\n\nfunc (h *Handler) adminPanel(c forge.Context) error {\n    c.LogInfo(\"admin access\", \"role\", c.Role(), \"user\", c.UserID())\n    return c.Render(http.StatusOK, views.Admin())\n}\n```\n\n### Background Jobs\n\nEnable background job processing with River:\n\n```go\napp := forge.New(\n    forge.AppConfig{},\n    forge.WithJobs(pgxPool,\n        job.Config{Workers: 2},\n        job.WithTask(EmailTask{}),\n        job.WithScheduledTask(CleanupTask{}),\n    ),\n)\n```\n\nDefine tasks using structural typing:\n\n```go\ntype EmailTask struct{}\n\nfunc (EmailTask) Name() string { return \"send_email\" }\n\nfunc (EmailTask) Handle(ctx context.Context, p struct{ Email string }) error {\n    // Send email...\n    return nil\n}\n```\n\nEnqueue jobs from handlers:\n\n```go\nfunc (h *Handler) signup(c forge.Context) error {\n    err := c.Enqueue(\"send_email\",\n        struct{ Email string }{Email: user.Email},\n        job.WithQueue(\"emails\"),\n        job.WithScheduledIn(1*time.Minute),\n    )\n    if err != nil {\n        return err\n    }\n    return c.Redirect(http.StatusSeeOther, \"/signup/confirm\")\n}\n```\n\n### File Storage\n\nEnable S3-compatible file storage:\n\n```go\ns, err := storage.New(storage.Config{\n    Endpoint:  \"s3.amazonaws.com\",\n    AccessKey: os.Getenv(\"AWS_ACCESS_KEY_ID\"),\n    SecretKey: os.Getenv(\"AWS_SECRET_ACCESS_KEY\"),\n    Bucket:    \"myapp-uploads\",\n    Region:    \"us-east-1\",\n})\nif err != nil {\n    log.Fatal(err)\n}\n\napp := forge.New(\n    forge.AppConfig{},\n    forge.WithStorage(s),\n)\n```\n\nUpload, download, and manage files directly from handlers:\n\n```go\nfunc (h *Handler) uploadAvatar(c forge.Context) error {\n    info, err := c.Upload(\"avatar\",\n        storage.WithPrefix(\"avatars\"),\n        storage.WithTenant(c.UserID()),\n        storage.WithValidation(\n            storage.MaxSize(5*1024*1024),\n            storage.ImageOnly(),\n        ),\n    )\n    if err != nil {\n        return err\n    }\n    // Save info.Key to database, generate URLs later\n    return c.JSON(http.StatusOK, map[string]string{\"key\": info.Key})\n}\n\nfunc (h *Handler) getAvatarURL(c forge.Context) error {\n    url, err := c.FileURL(avatarKey, storage.WithExpiry(1*time.Hour))\n    if err != nil {\n        return err\n    }\n    return c.JSON(http.StatusOK, map[string]string{\"url\": url})\n}\n```\n\n### HTMX-Aware Rendering\n\nContext automatically detects HTMX requests and renders accordingly:\n\n```go\nfunc (h *Handler) contacts(c forge.Context) error {\n    contacts, err := h.repo.ListContacts(c)\n    if err != nil {\n        return err\n    }\n\n    // HTMX request → renders just the partial\n    // Regular request → renders the full page with layout\n    return c.RenderPartial(http.StatusOK,\n        views.ContactsPage(contacts), // full page\n        views.ContactsList(contacts), // partial for HTMX\n    )\n}\n```\n\nCheck HTMX state: `c.IsHTMX()` returns `true` for HTMX-initiated requests. Redirects automatically use `HX-Redirect` headers when appropriate.\n\n### Server-Sent Events\n\nStream events to clients using channels:\n\n```go\nfunc (h *Handler) streamEvents(c forge.Context) error {\n    ch := make(chan forge.SSEEvent)\n    go func() {\n        defer close(ch)\n        for {\n            select {\n            case \u003c-c.Done():\n                return\n            case event := \u003c-eventChan:\n                ch \u003c- forge.SSEString(\"message\", event.Data)\n            }\n        }\n    }()\n    return c.SSE(ch)\n}\n```\n\n### Error Handling\n\nReturn errors from handlers using convenience constructors or the context helper:\n\n```go\nfunc (h *Handler) getUser(c forge.Context) error {\n    id := forge.Param[string](c, \"id\")\n    user, err := h.repo.GetUser(c, id)\n    if err == sql.ErrNoRows {\n        return forge.ErrNotFound(\"User not found\")\n    }\n    if err != nil {\n        // c.Error() creates an HTTPError with the given status and message\n        return c.Error(500, \"Failed to fetch user\", forge.WithError(err))\n    }\n    return c.JSON(http.StatusOK, user)\n}\n```\n\nCustomize error handling globally:\n\n```go\napp := forge.New(\n    forge.AppConfig{},\n    forge.WithErrorHandler(func(c forge.Context, err error) error {\n        if httpErr := forge.AsHTTPError(err); httpErr != nil {\n            return c.JSON(httpErr.StatusCode(), httpErr)\n        }\n        return c.JSON(http.StatusInternalServerError, map[string]string{\n            \"message\": \"Something went wrong\",\n        })\n    }),\n)\n```\n\n## Built-In Middlewares\n\nImport from `github.com/dmitrymomot/forge/middlewares`:\n\n- **RequestID** — Request tracking IDs\n- **Recover** — Panic recovery\n- **I18n** — Internationalization and localization\n- **JWT** — Token-based authentication\n- **CSRF** — Cross-site request forgery protection\n- **RateLimit** — Request rate limiting\n- **AuditLog** — Request and action logging\n- **CORS** — Cross-origin resource sharing\n- **Auth** — Authentication checks\n- **RBAC** — Role-based access control\n\n## Utility Packages\n\nAvailable in `github.com/dmitrymomot/forge/pkg`:\n\n- `binder` — Request binding with validation\n- `cache` — Caching utilities\n- `clientip` — Client IP extraction\n- `cookie` — Secure cookie management\n- `db` — Database utilities\n- `dnsverify` — DNS verification helpers\n- `fingerprint` — Browser fingerprinting\n- `geolocation` — IP geolocation\n- `hostrouter` — Multi-domain routing\n- `htmx` — HTMX helpers\n- `i18n` — Internationalization\n- `id` — ID generation (ULID, ShortID)\n- `job` — Background job processing\n- `jwt` — JWT utilities\n- `logger` — Structured logging\n- `mailer` — Email sending\n- `oauth` — OAuth 2.0 helpers\n- `qrcode` — QR code generation\n- `randomname` — Random name generation\n- `ratelimit` — Rate limiting\n- `redis` — Redis utilities\n- `sanitizer` — HTML sanitization\n- `secrets` — Secrets management\n- `slug` — URL slug generation\n- `storage` — File storage\n- `token` — Token generation\n- `totp` — TOTP/2FA\n- `useragent` — User agent parsing\n- `validator` — Input validation\n- `webhook` — Webhook utilities\n\n## Configuration\n\nLoad environment variables into structs:\n\n```go\ntype Config struct {\n    DatabaseURL string `env:\"DATABASE_URL,required\"`\n    Port        string `env:\"PORT\" envDefault:\":8080\"`\n    Debug       bool   `env:\"DEBUG\"`\n}\n\nvar cfg Config\nif err := forge.LoadConfig(\u0026cfg); err != nil {\n    log.Fatal(err)\n}\n```\n\n## Multi-Domain Routing\n\nCompose multiple apps with host-based routing:\n\n```go\napi := forge.New(\n    forge.AppConfig{BaseDomain: \"acme.com\"},\n    forge.WithHandlers(handlers.NewAPIHandler()),\n)\n\nwebsite := forge.New(\n    forge.AppConfig{BaseDomain: \"acme.com\"},\n    forge.WithHandlers(handlers.NewLandingHandler()),\n)\n\nif err := forge.Run(\n    forge.RunConfig{Address: \":8080\"},\n    forge.WithDomain(\"api.acme.com\", api),\n    forge.WithDomain(\"*.acme.com\", website),\n); err != nil {\n    log.Fatal(err)\n}\n```\n\n## Commands\n\n```bash\njust test             # Tests with race detection + coverage\njust bench            # Benchmarks with memory stats\njust lint             # vet, golangci-lint, nilaway, betteralign, modernize\njust fmt              # Format + organize imports\njust test-integration # Docker-based integration tests\n```\n\n## Claude Code Plugin\n\n[forge-skills](https://github.com/dmitrymomot/forge-skills) is a Claude Code plugin that accelerates Forge development with three skills:\n\n- **`/forge-init \u003capp-name\u003e`** — Scaffold a complete project (config, Docker, env, task runner) with selectable subsystems (Postgres, Redis, sessions, jobs, storage, templ, HTMX, Tailwind, mailer, OAuth)\n- **`/forge-build \u003cfeature\u003e`** — Generate handlers, DB migrations + sqlc queries, background jobs, auth flows, email templates, storage integration, SSE endpoints, and templ views\n- **`/templui \u003cdescription\u003e`** — Generate Go templ templates using the [templui](https://www.templui.com/) component library (41 components, Tailwind + HTMX-ready)\n\nInstall inside Claude Code (v1.0.33+):\n\n```\n/plugin marketplace add dmitrymomot/forge-skills\n/plugin install forge-skills@forge-skills\n```\n\n## Documentation\n\nFull API documentation is available via:\n\n```bash\ngo doc -all github.com/dmitrymomot/forge\n```\n\nOr online at [pkg.go.dev/github.com/dmitrymomot/forge](https://pkg.go.dev/github.com/dmitrymomot/forge)\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n## Design Principles\n\n- No reflection, no service containers, no magic\n- Packages receive values via parameters, not context\n- Public methods must not return unexported types\n- Framework provides utility packages; business logic belongs in consumer repos\n- All IDs generated using `pkg/id/` package exclusively\n\n## License\n\nApache 2.0 — see [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmitrymomot%2Fforge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmitrymomot%2Fforge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmitrymomot%2Fforge/lists"}