{"id":22322614,"url":"https://github.com/josestg/httpkit","last_synced_at":"2025-10-29T14:14:09.298Z","repository":{"id":202757284,"uuid":"708054290","full_name":"josestg/httpkit","owner":"josestg","description":"HTTP Kit for Go Backend Engineers","archived":false,"fork":false,"pushed_at":"2023-10-21T12:59:51.000Z","size":26,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-31T06:47:20.318Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/josestg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-10-21T11:40:19.000Z","updated_at":"2023-10-21T13:03:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"5c769e82-0c1c-4c42-9f7b-ec33946793b8","html_url":"https://github.com/josestg/httpkit","commit_stats":null,"previous_names":["josestg/httpkit"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josestg%2Fhttpkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josestg%2Fhttpkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josestg%2Fhttpkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josestg%2Fhttpkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/josestg","download_url":"https://codeload.github.com/josestg/httpkit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245591621,"owners_count":20640692,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-12-04T01:07:56.049Z","updated_at":"2025-10-29T14:14:04.253Z","avatar_url":"https://github.com/josestg.png","language":"Go","readme":"# HTTP Kit for Go Backend Engineers\n\nThis kit is my personal utility, and I have used it in both my professional and hobby projects. I hope you will find it useful as well. I would be happy to receive your feedback and suggestions.\n\n## Features\n\n- [x] Modifies the [httprouter](github.com/julienschmidt/httprouter) to allow the handler to return errors.\n- [x] HTTP Server with Graceful Shutdown.\n- [x] Helpers for creating middleware for both net/http and the modified httprouter.\n- [x] Helpers for request decoding and response encoding.\n- [x] A middleware for recording logs. This middleware can be used to implement the [Canonical Log Line](https://stripe.com/blog/canonical-log-lines).\n\n## TODOS\n\n- [ ] Add a Content Negotiation Middleware\n- [ ] Add more encoders and decoders.\n- [ ] Add more common middlewares.\n- [ ] Add Open Telemetry support.\n\n\n## Examples\n\n### Creating a Server with Graceful Shutdown\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/josestg/httpkit\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"syscall\"\n\t\"time\"\n)\n\nfunc main() {\n\tlog := slog.Default()\n\n\tmux := httpkit.NewServeMux(\n\t\t// Register an error handler that will be called when there is still unresolved error after all the middlewares\n\t\t// and the handler have been called. We can think this as the last hope when all else fails.\n\t\thttpkit.Opts.LastResortErrorHandler(LastResortErrorHandler(log)),\n\n\t\t// Set the global middlewares for the mux. These middlewares will be applied to all the handlers that are\n\t\t// registered to this mux.\n\t\t//\n\t\t// For middlewares that are applied to a specific handler, `mux.Route` takes variadic `httpkit.MuxMiddleware`\n\t\t// as the last argument, which will be applied to that handler only.\n\t\thttpkit.Opts.Middleware(GlobalMiddleware()),\n\n\t\t// And more options are available under `httpkit.Opts` namespace.\n\t)\n\n\t// Using `Route` instead of `HandleFunc` will be more convenient when working with Swagger docs because the swagger\n\t// docs and the route definition will be close to each other. Which will make it easier to keep them in sync.\n\tmux.Route(httpkit.Route{\n\t\tMethod: http.MethodPost,\n\t\tPath:   \"/\",\n\t\tHandler: func(w http.ResponseWriter, r *http.Request) error {\n\t\t\tdata := struct {\n\t\t\t\tMessage string `json:\"message\"`\n\t\t\t}{}\n\n\t\t\tif err := httpkit.ReadJSON(r.Body, \u0026data); err != nil {\n\t\t\t\treturn fmt.Errorf(\"could not read json: %w\", err)\n\t\t\t}\n\n\t\t\t// do something with the data.\n\t\t\tdata.Message += \" --updated\"\n\t\t\treturn httpkit.WriteJSON(w, data, http.StatusOK)\n\t\t},\n\t})\n\n\t// this middleware will be applied to the router itself. Meaning that it will be called before and after\n\t// the router calls the handler.\n\tmid := httpkit.ReduceNetMiddleware(\n\t\t// record both request and response body, along with latency and status code.\n\t\thttpkit.LogEntryRecorder,\n\n\t\t// another example of a middleware that will be applied to the router itself.\n\t\tfunc(next http.Handler) http.Handler {\n\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tlog.Info(\"before router\")\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\tentry, ok := httpkit.GetLogEntry(w)\n\t\t\t\tif ok {\n\t\t\t\t\tlog.Info(\"after router\", \"method\", r.Method, \"path\", r.URL.Path,\n\t\t\t\t\t\t\"status\", entry.StatusCode, \"latency\", time.Duration(entry.RespondedAt-entry.RequestedAt))\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t)\n\n\t// apply the middleware to the router.\n\thandler := mid.Then(mux)\n\n\tsrv := http.Server{\n\t\tAddr:         \"0.0.0.0:8080\",\n\t\tHandler:      handler,\n\t\tReadTimeout:  5 * time.Second,\n\t\tWriteTimeout: 10 * time.Second,\n\t}\n\n\t// create a graceful server runner.\n\trun := httpkit.NewGracefulRunner(\n\t\t\u0026srv,\n\t\thttpkit.RunOpts.WaitTimeout(10*time.Second),              // wait for 10 seconds before force shutdown.\n\t\thttpkit.RunOpts.Signals(syscall.SIGINT, syscall.SIGTERM), // listen to SIGINT and SIGTERM signals.\n\t\thttpkit.RunOpts.EventListener(func(evt httpkit.RunEvent, data string) { // listen to events and log them.\n\t\t\tswitch evt {\n\t\t\tdefault:\n\t\t\t\tlog.Info(data)\n\t\t\tcase httpkit.RunEventAddr:\n\t\t\t\tlog.Info(\"http server listening\", \"addr\", data)\n\t\t\tcase httpkit.RunEventSignal:\n\t\t\t\tlog.Info(\"http server received shutdown signal\", \"signal\", data)\n\t\t\t}\n\t\t}),\n\t)\n\n\tif err := run.ListenAndServe(); err != nil {\n\t\tlog.Error(\"http server error\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc LastResortErrorHandler(log *slog.Logger) httpkit.LastResortErrorHandler {\n\treturn func(w http.ResponseWriter, r *http.Request, err error) {\n\t\tlog.Error(\"last resort error handler\", \"error\", err)\n\t\tdata := map[string]string{\n\t\t\t\"message\": http.StatusText(http.StatusInternalServerError),\n\t\t}\n\t\tif wErr := httpkit.WriteJSON(w, data, http.StatusInternalServerError); wErr != nil {\n\t\t\tlog.Error(\"could not write json\", \"unresolved_error\", err, \"write_error\", wErr)\n\t\t}\n\t}\n}\n\nfunc GlobalMiddleware() httpkit.MuxMiddleware {\n\treturn httpkit.ReduceMuxMiddleware(\n\t\tglobalMiddlewareOne,\n\t\tglobalMiddlewareTwo,\n\t)\n}\n\nfunc globalMiddlewareOne(next httpkit.Handler) httpkit.Handler {\n\treturn httpkit.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {\n\t\t// do something before calling the next handler.\n\t\terr := next.ServeHTTP(w, r)\n\t\t// do something after calling the next handler.\n\t\treturn err\n\t})\n}\n\nfunc globalMiddlewareTwo(next httpkit.Handler) httpkit.Handler {\n\treturn httpkit.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {\n\t\t// do something before calling the next handler.\n\t\terr := next.ServeHTTP(w, r)\n\t\t// do something after calling the next handler.\n\t\treturn err\n\t})\n}\n\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjosestg%2Fhttpkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjosestg%2Fhttpkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjosestg%2Fhttpkit/lists"}