{"id":36522384,"url":"https://github.com/thnxdev/happy","last_synced_at":"2026-01-26T16:04:05.638Z","repository":{"id":65732864,"uuid":"596251697","full_name":"thnxdev/happy","owner":"thnxdev","description":"Happy is an opinionated tool for generating request-handler boilerplate for Go 😊","archived":false,"fork":false,"pushed_at":"2026-01-20T18:23:36.000Z","size":35,"stargazers_count":23,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-01-21T02:38:49.763Z","etag":null,"topics":["go","http"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thnxdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"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":"2023-02-01T19:35:46.000Z","updated_at":"2026-01-20T18:17:21.000Z","dependencies_parsed_at":"2023-02-19T06:55:17.322Z","dependency_job_id":"fe5bc78f-6d3d-48a4-b3b4-9c7e6f04b985","html_url":"https://github.com/thnxdev/happy","commit_stats":{"total_commits":14,"total_committers":2,"mean_commits":7.0,"dds":0.3571428571428571,"last_synced_commit":"fcaa5f215dccfc08f9c46fff1d0272d4e3e45ec3"},"previous_names":["thnxdev/genhapi"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/thnxdev/happy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thnxdev%2Fhappy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thnxdev%2Fhappy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thnxdev%2Fhappy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thnxdev%2Fhappy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thnxdev","download_url":"https://codeload.github.com/thnxdev/happy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thnxdev%2Fhappy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28782097,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T13:55:28.044Z","status":"ssl_error","status_checked_at":"2026-01-26T13:55:26.068Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["go","http"],"created_at":"2026-01-12T02:52:19.776Z","updated_at":"2026-01-26T16:04:05.633Z","avatar_url":"https://github.com/thnxdev.png","language":"Go","readme":"# happy is an opinionated tool for generating request-handler boilerplate for Go 😊 [![CI](https://github.com/thnxdev/happy/actions/workflows/ci.yml/badge.svg)](https://github.com/thnxdev/happy/actions/workflows/ci.yml)\n\nhappy automatically generates `http.RequestHandler` boilerplate for routing to Go\nmethods annotated with comment directives. The generated code decodes the\nincoming HTTP request into the method's parameters, and encodes method return\nvalues to HTTP responses.\n\nhappy only supports JSON request/response payloads. That said, see\n[below](#escape-hatch) for workarounds that can leverage just happy's routing.\n\nhappy's generated code relies on only the standard library.\n\nHere's an example annotated method that happy will generate a request handler for:\n\n```go\n//happy:api GET /users/:id\nfunc (u *UsersService) GetUser(id string) (User, error) {\n\tfor _, user := range u.users {\n\t\tif user.ID == id {\n\t\t\treturn user, nil\n\t\t}\n\t}\n\treturn User{}, Errorf(http.StatusNotFound, \"user %q not found\", id)\n}\n```\n\nThe generated request handler will map the path component `:id` to the parameter\n`id`, and JSON encode the response payload or error.\n\nSee [below](#example) for a full example.\n\n# Status\n\nhappy is usable but has some limitations and missing features:\n\n- Does not support pointers to structs for JSON request payloads, only values.\n- Limited (int and string) type support for path and query parameters.\n- Does not support embedded structs for query parameters.\n- A command to dump the API as an OpenAPI schema.\n\n# Protocol\n\nhappy's protocol is in the form of Go comment directives. Each directive must be\nplaced on a method, not a free function.\n\nAnnotations and methods are in the following form:\n\n```go\n//happy:api \u003cmethod\u003e \u003cpath\u003e [\u003coption\u003e[=\u003cvalue\u003e] ...]\nfunc (s Struct) Method([pathVar0, pathVar1 string][, req Request]) ([\u003cresponse\u003e, ][error]) { ... }\n```\n\n## Options\n\nOptions are key+value pairs appended to the end of an annotation and are exposed\nvia the following generated method on the service struct:\n\n```go\nHandlerOptions(r *http.Request) map[string]string\n```\n\nThis method will return the metadata map associated with the inbound request, or\n`nil`.\n\n### Middleware\n\nA handy pattern is to create a wrapping `http.Handler` that injects options\ninto the inbound request context like so:\n\n```go\nvar Options struct{}\n\ntype OptionHandler interface {\n\tHandlerOptions(*http.Request) map[string]string\n}\n\nfunc OptionsMiddleware(options OptionHandler, next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tctx := context.WithValue(r.Context(), Options, options.HandlerOptions(r))\n\t\tr = r.WithContext(ctx)\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n```\n\nMiddleware can then make decisions based on the values of options. For example, a hypothetical `auth` option might be used like so:\n\n```go\nfunc AuthMiddleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, auth := r.Context.Value(Options)[\"auth]\n\t\tif !auth {\n\t\t\tnext.ServeHttp(w, r)\n\t\t\treturn\n\t\t}\n\t\t// Authenticate endpoint...\n\t\tcookie, err := r.Cookie(\"auth\")\n\t\t// ...\n\t})\n}\n```\n\n## Request signature\n\nThe `\u003cpath\u003e` value supports variables in the form `:\u003cname\u003e` which are mapped\ndirectly to method parameters of the same name. These parameters must implement\n`encoding.TextUnmarshaler` or be of type `string` or `int`.\n\neg.\n\n```go\n//happy:api GET /users/:id\nfunc (u *UsersService) GetUser(id string) (User, error) { ... }\n```\n\nIn addition to path variables and the request payload, happy can pass any of the\nfollowing types to your handler method:\n\n- `*http.Request`\n- `http.ResponseWriter`\n- `context.Context` from the incoming `*http.Request`\n- `io.Reader` for the request body\n\n### Request payload decoding\n\nFinally, a single extra struct parameter can be specified, which will be decoded\nfrom the request payload. For PUT/POST request the \"payload\" is the request\nbody, for all other request types the \"payload\" is the URL query parameters. \n\neg.\n\n```go\ntype Paginate struct {\n\tSize int\n}\n\n//happy:api GET /users/\nfunc (u *UsersService) ListUsers(pagination Paginate) ([]User, error) {\n\t// ...\n}\n```\n\n#### Query parameter decoding\n\nFor query parameters, embedded structs are not supported and fields may\n(currently) only be of types `bool`, `int` and `string`.\n\nThe name of the query parameter will be the name of the Go field with the first\nletter lower-cased. This can be overridden with the field tag `query:\"\u003cname\u003e\"`.\n\n## Response signature\n\nThe return signature of the method is in the form:\n\n```\n[([\u003cresponse\u003e, ][error])]\n```\n\nThat is, the method may return a response, an error, both, or nothing.\n\n Depending on the type of the `\u003cresponse\u003e` value, the response will be encoded\n in the following ways:\n\n | Type | Encoding |\n | ---- | -------- |\n | `nil`/omitted | 204 No Content |\n | `string` | `text/html` |\n | `[]byte` | `application/octet-stream` |\n | `io.Reader` | `application/octet-stream` |\n | `io.ReadCloser` | `application/octet-stream` |\n | `*http.Response` | Response structure is used as-is. |\n | `*` | `application/json` |\n\n## Error handling\n\nIf the method returns an error, happy will generate code to check the error\nand return an error response. If the error value implements `http.Handler` that\nwill be used to generate the response, otherwise a 500 response will be\ngenerated.\n\nA rudimentary HTTP error type might look like this:\n\n```go\ntype Error struct {\n\tCode int\n\tMsg string\n}\n\nfunc (e Error) Error() string { return fmt.Sprintf(\"%d: %s\", e.Code, e.Msg) }\nfunc (e Error) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\thttp.Error(w, w.Msg, e.Code)\n}\n```\n\nAdditionally, if the receiver implements the following interface it will be used\nto write errors:\n\n```go\ntype ErrorHandler interface {\n}\n```\n\n## Escape hatch\n\nIf happy's default request/response handling is not to your liking, you can still\nleverage happy's routing by accepting `*http.Request` and `http.ResponseWriter`\nas parameters:\n\n```go\n//happy:api POST /users\nfunc (s Struct) CreateUser(r *http.Request, w http.ResponseWriter) { ... }\n```\n\n# Example\n\nCreate a `main.go` with the following content and run `go generate`. happy will\ncreate a `main_api.go` file implementing `http.Handler` for `*Service`.\n\n```go\n//go:generate happy\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// An Error that implements http.Handler to write structured JSON errors.\ntype Error struct {\n\tcode    int\n\tmessage string\n}\n\nfunc Errorf(code int, format string, args ...interface{}) error {\n\treturn Error{code, fmt.Sprintf(format, args...)}\n}\n\nfunc (e Error) Error() string { return e.message }\n\nfunc (e Error) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.WriteHeader(e.code)\n\tjson.NewEncoder(w).Encode(map[string]string{\"error\": e.message})\n}\n\ntype User struct {\n\tID   int    `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\ntype Service struct {\n\tusers []User\n}\n\n//happy:api GET /users/:id\nfunc (s *Service) GetUser(id int) (User, error) {\n\tfor _, user := range s.users {\n\t\tif user.ID == id {\n\t\t\treturn user, nil\n\t\t}\n\t}\n\treturn User{}, Errorf(http.StatusNotFound, \"user %q not found\", id)\n}\n\n//happy:api GET /users\nfunc (s *Service) ListUsers() ([]User, error) {\n\treturn s.users, nil\n}\n\n//happy:api POST /users\nfunc (s *Service) CreateUser(user User) error {\n\tfor _, u := range s.users {\n\t\tif u.ID == user.ID {\n\t\t\treturn Errorf(http.StatusConflict, \"user %d already exists\", user.ID)\n\t\t}\n\t}\n\ts.users = append(s.users, user)\n\treturn Errorf(http.StatusCreated, \"user %d created\", user.ID)\n}\n\nfunc main() {\n\tservice := \u0026Service{\n\t\tusers: []User{{ID: 1, Name: \"Alice\"}, {ID: 2, Name: \"Bob\"}},\n\t}\n\thttp.ListenAndServe(\":8080\", service)\n}\n```\n\n*happy's annotations are vaguely inspired by [Encore](https://encore.dev/).*","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthnxdev%2Fhappy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthnxdev%2Fhappy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthnxdev%2Fhappy/lists"}