{"id":20016668,"url":"https://github.com/webmafia/papi","last_synced_at":"2026-02-26T12:07:42.566Z","repository":{"id":259207664,"uuid":"869589040","full_name":"webmafia/papi","owner":"webmafia","description":"Performant API framework for Go","archived":false,"fork":false,"pushed_at":"2026-02-18T17:34:45.000Z","size":540,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-18T21:05:07.421Z","etag":null,"topics":["api","framework","go","golang"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/webmafia/papi","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/webmafia.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":"security/errors.go","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":"2024-10-08T14:47:20.000Z","updated_at":"2026-02-05T15:15:35.000Z","dependencies_parsed_at":"2024-10-24T18:28:57.237Z","dependency_job_id":"8a9a6e75-1f2f-4290-afc9-d6677c1ab26d","html_url":"https://github.com/webmafia/papi","commit_stats":null,"previous_names":["webmafia/papi"],"tags_count":62,"template":false,"template_full_name":null,"purl":"pkg:github/webmafia/papi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webmafia%2Fpapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webmafia%2Fpapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webmafia%2Fpapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webmafia%2Fpapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webmafia","download_url":"https://codeload.github.com/webmafia/papi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webmafia%2Fpapi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29858466,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-26T08:51:08.701Z","status":"ssl_error","status_checked_at":"2026-02-26T08:50:19.607Z","response_time":89,"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":["api","framework","go","golang"],"created_at":"2024-11-13T08:12:30.297Z","updated_at":"2026-02-26T12:07:42.551Z","avatar_url":"https://github.com/webmafia.png","language":"Go","readme":"# papi\n\u003cimg alt=\"Papi\" src=\"./docs/papi.webp\" align=\"right\" /\u003e\n\nPerformant API framework in Go.\n- Reduced boilerplate\n- Only reflection on startup - no reflection during runtime\n- Highly optimized, with almost zero allocations during runtime\n- Static typing of routes\n- Automatic generation of OpenAPI documentation\n- Automatic validation (based on OpenAPI schema rules)\n- Encourages dependency injection\n\n**WARNING: This hasn't reached version 1.0.0 and is not production ready yet.**\n\n## Installation\n```sh\ngo get github.com/webmafia/papi\n```\n\n## Usage\nSee the [example](./example) for a full example of how it's used.\n\n### Routing\nPapi uses generics in routes to leverage static typing - this also makes it possible to generate an OpenAPI documentation automatically that is always 100% up to date with the code. As generics in Go only can be used on public types and functions, the following methods are public in the package:\n\n```go\npapi.GET[I, O any](api *papi.API, r papi.Route[I, O]) (err error)\npapi.PUT[I, O any](api *papi.API, r papi.Route[I, O]) (err error)\npapi.POST[I, O any](api *papi.API, r papi.Route[I, O]) (err error)\npapi.DELETE[I, O any](api *papi.API, r papi.Route[I, O]) (err error)\n```\nThe `I` and `O` generic types are input (request) and output (response), respectively. \n\nIt might look strange at first, but the resulting code gets pretty neat:\n```go\ntype req struct {}\n\npapi.GET(api, papi.Route[req, domain.User]{\n\tPath: \"/users/{id}\",\n\n\t// A handler always accepts a request- and response type, and returns any error occured.\n\tHandler: func(ctx *papi.RequestCtx, req *req, resp *domain.User) (err error) {\n\t\tresp.ID = 123\n\t\tresp.Name = \"John Doe\"\n\n\t\treturn\n\t},\n})\n```\n\nBy passing pointers of the request and response to the handler, no allocation nor unnecessary copying is needed. The response is often domain model structs, but can be any type.\n\nBut how about the request input? In the example above it's an empty struct, but let's explore this in the next section.\n\n### Request input\nThe request input type can have any name, but it must always be a struct. This allows us to use struct tags for some magic:\n```go\ntype req struct{\n\tId int `param:\"id\"`\n}\n```\n\nIf you look at the previous example, you'll see that the `Path` field contains a parameter in the format `{id}`. As we've tagged our `Id` field above with `param:\"id\"`, any value passed in the path will end up here. Also, as the type of the field is an `int`, only integers will be accepted - this is validated automatically.\n\nThe following tags are supported in the request input:\n\n| Tag           | Meaning                            | Example source  | Example destination      |\n| ------------- | ---------------------------------- | --------------- | ------------------------ |\n| `param:\"*\"`   | URL parameters                     | `/users/{id}`   | `123`                    |\n| `query:\"*\"`   | Search query parameters            | `?foo=bar,baz`  | `[]string{\"bar\",\"baz\"}`  |\n| `body:\"json\"` | PUT and POST bodies in JSON format | `{\"foo\":\"bar\"}` | `MyStruct{ Foo: \"bar\" }` |\n\nNote that string types are not copied, which means that any values in `req` must not be used outside the handler.\n\n### Validation\nSometimes the type is not enought. That's why we support OpenAPI's schema rules. Take this example:\n```go\ntype req struct{\n\tOrderBy string `query:\"orderby\" enum:\"name,email\"`\n\tOrder   string `query:\"order\" enum:\"asc,desc\"`\n}\n```\n\nThe following validation tags are supported in the request input (as well as in any nested structs):\n\n| Tag                | Int / Float            | String                 | Slice                              | Array                |\n| ------------------ | ---------------------- | ---------------------- | ---------------------------------- | -------------------- |\n| `min:\"*\"`          | Minimum value          | Minimum length         | Minimum length                     | -                    |\n| `max:\"*\"`          | Maximum value          | Maximum length         | Maximum length                     | -                    |\n| `enum:\"*,*,*\"`     | One of specific values | One of specific values | -                                  | -                    |\n| `pattern:\"*\"`      | -                      | Regular expression     | -                                  | -                    |\n| `default:\"*\"`      | Sets default if zero   | Sets default if zero   | Sets default if zero               | Sets default if zero |\n| `flags:\"required\"` | Must be non-zero       | Must be non-zero       | Must have at least 1 non-zero item | Must be non-zero     |\n\nPlease note:\n- If slices and arrays don't support a tag, it's passed to their children.\n- Pointers to any type is only required to be non-nil when required.\n\n### Routing groups \u0026 OpenAPI operations\nWhen creating an API you usually want to inject any dependencies, e.g. a User service for any user-related routes - or \"operations\" as they are called in the OpenAPI specfication. Also, each operation is required to have an API-unique identiier (Operation ID), and is usually grouped by a tag.\n\nPapi solves all this with what we call a routing group, which basically is an arbitrary struct with methods matching the `func(*papi.API) error` signature:\n\n```go\ntype Users struct{}\n\nfunc (r Users) GetUserByID(api *papi.API) (err error) {\n\ttype req struct {\n\t\tId int `param:\"id\"`\n\t}\n\n\treturn papi.GET(api, papi.Route[req, User]{\n\t\tPath: \"/users/{id}\",\n\n\t\tHandler: func(ctx *papi.RequestCtx, req *req, resp *domain.User) (err error) {\n\t\t\tresp.ID = 123\n\t\t\tresp.Name = \"John Doe\"\n\n\t\t\treturn\n\t\t},\n\t})\n}\n\nfunc main() {\n\t// API initialization and error handling is left out for brevity\n\n\terr := api.RegisterRoutes(Users{})\n}\n```\n\nWhat happens here:\n- As `GetUserByID` matches the `func(*papi.API) error`, this will be called on registration.\n- A valid OpenAPI Operation ID will be generated from the method's name, resulting in `get-users-by-id`.\n- A descriptive summary of the route will also be generated from the method's name, result in `Get user by ID`.\n- The `req` type won't leak outside the route.\n- All OpenAPI operations will be assigned a tag matching the group's name, in this case `Users`.\n- We are able to inject any dependency into the `Users` struct, and use them in the routes.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebmafia%2Fpapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebmafia%2Fpapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebmafia%2Fpapi/lists"}