{"id":15293385,"url":"https://github.com/jarrettv/go-japi","last_synced_at":"2025-05-07T04:24:38.219Z","repository":{"id":41298471,"uuid":"482738751","full_name":"jarrettv/go-japi","owner":"jarrettv","description":"Japi is a fast \u0026 simple HTTP API library that automatically marshals JSON payloads and uses RFC7807 for problem details","archived":false,"fork":false,"pushed_at":"2022-05-31T03:36:15.000Z","size":3771,"stargazers_count":19,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-19T18:30:42.108Z","etag":null,"topics":["go","golang","json","json-api","middleware","router"],"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/jarrettv.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}},"created_at":"2022-04-18T06:24:36.000Z","updated_at":"2024-06-23T23:23:15.000Z","dependencies_parsed_at":"2022-09-26T20:52:58.132Z","dependency_job_id":null,"html_url":"https://github.com/jarrettv/go-japi","commit_stats":null,"previous_names":["jarrettv/go-minimal"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jarrettv%2Fgo-japi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jarrettv%2Fgo-japi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jarrettv%2Fgo-japi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jarrettv%2Fgo-japi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jarrettv","download_url":"https://codeload.github.com/jarrettv/go-japi/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252811514,"owners_count":21807954,"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":["go","golang","json","json-api","middleware","router"],"created_at":"2024-09-30T16:47:13.575Z","updated_at":"2025-05-07T04:24:38.195Z","avatar_url":"https://github.com/jarrettv.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# japi is a JSON HTTP API go library\r\n\r\nJapi is a fast \u0026 simple HTTP API library that will automatically marshal JSON payloads to/from \r\nyour request and response structs. It follows [RFC7807](https://datatracker.ietf.org/doc/html/rfc7807) \r\nstandard for returning useful problem details.\r\n\r\nThis library focuses on happy path to minimize code and dependencies. For more complex use cases, \r\nwe recommend sticking to a larger web framework. However, this library supports the standard \r\nnet/http ecosystem.\r\n\r\nThis library requires Go 1.18 to work as it utilizes generics.\r\n\r\nThis library was forked from https://github.com/AbeMedia/go-don\r\n\r\n## Contents\r\n\r\n- [Basic Example](#basic-example)\r\n- [Configuration](#configuration)\r\n- [Request parsing](#request-parsing)\r\n- [Customize response](#customize-response)\r\n- [Problem details](#problem-details)\r\n- [Sub-routers](#sub-routers)\r\n- [Middleware](#middleware)\r\n\r\n## Basic Example\r\n\r\n```go\r\npackage main\r\n\r\nimport (\r\n  \"context\"\r\n  \"errors\"\r\n  \"fmt\"\r\n  \"net/http\"\r\n\r\n  \"github.com/jarrettv/go-japi\"\r\n)\r\n\r\ntype GreetRequest struct {\r\n  Name string `path:\"name\"`         // Get name from the URL path.\r\n  Age  int    `header:\"X-User-Age\"` // Get age from HTTP header.\r\n}\r\n\r\ntype GreetResponse struct {\r\n  // Remember to add tags for automatic marshalling\r\n  Greeting string `json:\"data\"`\r\n}\r\n\r\nfunc Greet(ctx context.Context, req GreetRequest) (*GreetResponse, error) {\r\n  if req.Name == \"\" {\r\n    return nil, problem.Validation(map[string]string{\r\n      \"name\": \"required\",\r\n    })\r\n  }\r\n  res := \u0026GreetResponse{\r\n    Greeting: fmt.Sprintf(\"Hello %s, you're %d years old.\", req.Name, req.Age),\r\n  }\r\n\r\n  return res, nil\r\n}\r\n\r\nfunc Pong(context.Context, japi.Empty) (string, error) {\r\n  return \"pong\", nil\r\n}\r\n\r\nfunc main() {\r\n  r := japi.New(nil)\r\n  r.Get(\"/ping\", japi.H(Pong)) // Handlers are wrapped with `japi.H`.\r\n  r.Post(\"/greet/:name\", japi.H(Greet))\r\n  r.ListenAndServe(\":8080\")\r\n}\r\n```\r\n\r\n## Configuration\r\n\r\nJapi is configured by passing in the `Config` struct to `japi.New`. We recommend you setup `ProblemConfig` at a minimum.\r\n\r\n```go\r\nr := japi.New(\u0026japi.Config{\r\n  ProblemConfig: problem.ProblemConfig{\r\n    ProblemTypeUrlFormat: \"https://example.com/errors/%s\",\r\n    ProblemInstanceFunc: func(ctx context.Context) string {\r\n      return fmt.Sprintf(\"https://example.com/trace/%d\", time.Now().UnixMilli())\r\n    },\r\n  },\r\n})\r\n```\r\n### RouteLogFunc\r\n\r\nA function to easily log the route name and route variables.\r\n\r\n### ProblemLogFunc\r\n\r\nA function to easily log when problems occur.\r\n\r\n### ProblemConfig.ProblemTypeUrlFormat\r\n\r\nThe format for the problem details type URI. See [RFC7807](https://datatracker.ietf.org/doc/html/rfc7807)\r\n\r\n### ProblemConfig.ProblemInstanceFunc\r\n\r\nA function for generating a unique trace URI. Defaults to a timestamp. See [RFC7807](https://datatracker.ietf.org/doc/html/rfc7807)\r\n\r\n## Request parsing\r\n\r\nAutomatically unmarshals values from headers, URL query, URL path \u0026 request body into your request\r\nstruct.\r\n\r\n```go\r\ntype MyRequest struct {\r\n  // Get from the URL path.\r\n  ID int64 `path:\"id\"`\r\n\r\n  // Get from the URL query.\r\n  Filter string `query:\"filter\"`\r\n\r\n  // Get from the JSON or form body.\r\n  Content float64 `form:\"bar\" json:\"bar\"`\r\n\r\n  // Get from the HTTP header.\r\n  Lang string `header:\"Accept-Language\"`\r\n}\r\n```\r\n\r\nPlease note that using a pointer as the request type negatively affects performance.\r\n\r\n## Customize Response\r\n\r\nImplement the `StatusCoder` and `Headerer` interfaces to customise headers and response codes.\r\n\r\n```go\r\ntype MyResponse struct {\r\n  Foo  string `json:\"foo\"`\r\n}\r\n\r\n// Set a custom HTTP response code.\r\nfunc (nr *MyResponse) StatusCode() int {\r\n  return 201\r\n}\r\n\r\n// Add custom headers to the response.\r\nfunc (nr *MyResponse) Header() http.Header {\r\n  header := http.Header{}\r\n  header.Set(\"foo\", \"bar\")\r\n  return header\r\n}\r\n```\r\n\r\n## Problems\r\n\r\nReturn a `problem.Problem` error when something goes wrong. For example:\r\n\r\n```go\r\nreturn nil, problem.Unexpected(err) // 500\r\n// or\r\nreturn nil, problem.NotFound() // 404\r\n// or\r\nreturn nil, problem.NotPermitted(username) // 403\r\n// or\r\nreturn nil, problem.Validation(map[string]string{ // 400\r\n  \"name\": \"required\",\r\n})\r\n// or\r\nreturn nil, problem.RuleViolantion(\"item is on backorder\") // 400\r\n// or\r\nreturn nil, problem.NotCurrent() // 407\r\n```\r\n\r\n\r\n## Sub-routers\r\n\r\nYou can create sub-routers using the `Group` function:\r\n\r\n```go\r\nr := japi.New(nil)\r\nsub := r.Group(\"/api\")\r\nsub.Get(\"/hello\")\r\n```\r\n\r\n## Middleware\r\n\r\nJapi uses the standard http middleware format of\r\n`func(http.RequestHandler) http.RequestHandler`.\r\n\r\nFor example:\r\n\r\n```go\r\nfunc loggingMiddleware(next http.Handler) http.Handler {\r\n  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)  {\r\n    log.Println(r.URL)\r\n    next(ctx)\r\n  })\r\n}\r\n```\r\n\r\nIt is registered on a router using `Use` e.g.\r\n\r\n```go\r\nr := japi.New(nil)\r\nr.Post(\"/\", japi.H(handler))\r\nr.Use(loggingMiddleware)\r\n```\r\n\r\nMiddleware registered on a group only applies to routes in that group and child groups.\r\n\r\n```go\r\nr := japi.New(nil)\r\nr.Get(\"/login\", japi.H(loginHandler))\r\nr.Use(loggingMiddleware) // applied to all routes\r\n\r\napi := r.Group(\"/api\")\r\napi.Get(\"/hello\", japi.H(helloHandler))\r\napi.Use(authMiddleware) // applied to routes `/api/hello` and `/api/v2/bye`\r\n\r\n\r\nv2 := api.Group(\"/v2\")\r\nv2.Get(\"/bye\", japi.H(byeHandler))\r\nv2.Use(corsMiddleware) // only applied to `/api/v2/bye`\r\n\r\n```\r\n\r\nTo pass values from the middleware to the handler extend the context e.g.\r\n\r\n```go\r\nfunc myMiddleware(next http.Handler) http.Handler {\r\n  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)  {\r\n    ctx := context.WithValue(r.Context(), ContextUserKey, \"my_user\")\r\n    next.ServeHTTP(w, r.WithContext(ctx))\r\n  })\r\n}\r\n```\r\n\r\nThis can now be accessed in the handler:\r\n\r\n```go\r\nuser := ctx.Value(ContextUserKey).(string)\r\n```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjarrettv%2Fgo-japi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjarrettv%2Fgo-japi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjarrettv%2Fgo-japi/lists"}