{"id":14987724,"url":"https://github.com/baderkha/easy-gin","last_synced_at":"2026-02-12T07:31:37.460Z","repository":{"id":153992660,"uuid":"631176202","full_name":"baderkha/easy-gin","owner":"baderkha","description":"Adds support for DTOs using the gin framework","archived":false,"fork":false,"pushed_at":"2023-06-14T17:23:06.000Z","size":308,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-18T16:40:53.693Z","etag":null,"topics":["clean-architecture","dto-pattern","generics-in-golang","gin","gin-gonic","golang","repository-pattern"],"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/baderkha.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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}},"created_at":"2023-04-22T07:10:05.000Z","updated_at":"2025-09-03T18:15:30.000Z","dependencies_parsed_at":null,"dependency_job_id":"bdc7b00e-39de-4fec-bf1d-ca3379455593","html_url":"https://github.com/baderkha/easy-gin","commit_stats":{"total_commits":26,"total_committers":3,"mean_commits":8.666666666666666,"dds":"0.34615384615384615","last_synced_commit":"8f53ad8806f13608aede66e4be4b0b1b11d87897"},"previous_names":["baderkha/easygin"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/baderkha/easy-gin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baderkha%2Feasy-gin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baderkha%2Feasy-gin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baderkha%2Feasy-gin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baderkha%2Feasy-gin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/baderkha","download_url":"https://codeload.github.com/baderkha/easy-gin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baderkha%2Feasy-gin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29361454,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-12T01:03:07.613Z","status":"online","status_checked_at":"2026-02-12T02:00:06.911Z","response_time":55,"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":["clean-architecture","dto-pattern","generics-in-golang","gin","gin-gonic","golang","repository-pattern"],"created_at":"2024-09-24T14:15:16.498Z","updated_at":"2026-02-12T07:31:37.447Z","avatar_url":"https://github.com/baderkha.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# easy-gin\n\u003cimg src=\"https://img.shields.io/badge/Test Coverage-94.4%25-brightgreen\"/\u003e\n\u003cp align=\"center\"\u003e \u003cimg src=\"./logo/logo.png\" width=\"200px\" height=\"200px\"/\u003e \u003c/p\u003e\n\nA zero magic way to implement [DTO pattern](https://www.okta.com/identity-101/dto/) with the [Gin Framework](https://github.com/gin-gonic/gin). \n\n**NOTE**\nThis package is meant for *REST APIs* only.\n\n## Why ?\n- Without easy-gin : \n\t- bad seperation of concern \n\t\t- doing validation and business logic\n\t- not easily unit testable without having to either wrap the context or use an http recorder\n\t- repetitive code with binds and checking error ..etc\n\t\n\n  ```go\n   type UserInput struct {\n      UserID            string `json:\"user_id\" uri:\"user_id\"`\n    }\n\n    func main(){\n      en := gin.Default()\n      en.POST(\"/:user_id\", func(ctx *gin.Context) {\n          var u UserInput\n          // bind\n          err := ctx.BindQuery(\u0026u)\n          if err != nil {\n            ctx.JSON(http.StatusBadRequest, err.Error())\n            return\n          }\n          // some extra validation logic\n          // i know you can add rules in the struct , but this can be replaced with a validation from db ...etc\n          if u.UserID == \"\" {\n            ctx.JSON(http.StatusBadRequest, errors.New(\"user id is missing\"))\n            return\n          }\n\n          // do somethign with data\n\n          ctx.JSON(http.StatusOK, fmt.Sprintf(\"user with id %s has been processed\", u.UserID))\n\n\t    })\n    }\n  ```\n - easy-gin way : \n \t- unit testable\n \t\t- your test is input output \n \t\t- no need to mock or use external recorders ..etc \n \t- seperation of concern \n \t\t- validation is handled by the UserInput DTO\n \t\t- Business Logic is handled by the handler  \n\n\n  ``` go\n    var _ easygin.IRequest = \u0026UserInput{} // this struct implements IRequest \n    type UserInput struct {\n      UserID            string `json:\"user_id\" uri:\"user_id\"` // still use the bind methods from gin !\n    }\n    // add custom validation logic not restricted by struct tags\n    func (u UserInput) Validate() error {\n      if u.UserID == \"\" {\n        return errors.New(\"user id not set\")\n      }\n      return nil\n    }\n    // you can use this to wrap your error if validation failed from gin or your custom validation\n    func (u UserInput) ValidationErrorFormat(err error) any {\n      return map[string]any{\n        \"err\": err.Error(),\n      }\n    }\n    \n    func HandleUsers(u UserInput) *easygin.Response {\n      // do something with the input ...\n      // focus on your domain logic rather than validation ...etc\n      return easygin.\n        Res(fmt.Sprintf(\"user with id %s has been processed\",u.UserID)).\n        Status(http.StatusOK)\n    }\n    \n    func main() {\n      en := gin.Default()\n      // by default the second argument is optional \n      // if not provided it will atempt all bind methods (JSON,QUERY,URI) (this will incur a performance hit)\n      en.POST(\"/:user_id\", easygin.To(HandleUsers,easygin.BindURI)) \n      en.Run(\":80\")\n    }\n  ```\n \n## Installation \n*Note* you need golang v1.8 and above to install this utility as under the hood it uses generics\n```\ngo get -u github.com/baderkha/easy-gin/v1/easygin\n```\n\n## Documentation\n\n### Quick Setup\n\n- Step 1 : Create a DTO object that implements the easygin.IRequest interface \n\n\t```go \n\ttype UpdateUserRequest struct {\n\t\tID string `uri:\"user_id\"` // regular gin binding from instructions\n\t\tName string `json:\"name\"` // binds from json body\n\t\tUserType string `form:\"user_type\"` // binds from query parameter\n\t}\n\t\n\tfunc (u UpdateUserRequest) Validate() error {\n\t   // your custom validation here , consider using a struct validator like [go-validator](https://github.com/go-playground/validator)\n\t   // also you can just have your validation done via tags if it's simple stuff and just return nil here\n\t   return nil\n\t}\n\t\n\tfunc (u UpdateUserRequest) ValidationErrorFormat(err error) any {\n\t\t// if you want your response to be the error string return\n\t\treturn err.Error()\n\t\t// if you want your response to be a wrapped with an object (map option)\n\t\treturn map[string]any{\n\t\t\t\"err\":err.Error()\n\t\t}\n\t\t// if you want your response to be a wrapped with an object (struct option)\n\t\treturn struct {\n\t\t\tError   string `json:\"err\"`\n\t\t\tMessage string `json:\"server_message\"`\n\t\t}{\n\t\t\tError:   err.Error(),\n\t\t\tMessage: \"failed validation\",\n\t\t}\n\t}\n\t```\n- Step 2 : Create your easygin Handler\n\t``` go\n\t// argument must not be a pointer !\n\tfunc HandleUserUpdate(u UpdateUserRequest) *easygin.Response {\n\t\t// process the data \n\t\t// ....\n\t\t\n\t\t// once ready to respond to client\n\t\tres := easygin.Res(map[string]any{\"wow\":\"ok\"})\n\t\t\n\t\treturn res // this will default with a 200 response code \n\t\t\n\t\treturn res.Status(201) // you can override it yourself , so you can use this to handle errors \n\t}\n\t```\n- Step 3 : Add it to your routes\n\t``` go\n\tfunc main() {\n\t\ten := gin.Default()\n\t\t\n\t\t// option a default binding\n\t\t// although this looks cleaner this will have a performance hit if you do not need to bind from everything else\n\t\ten.PATCH(\"/:user_id\",easygin.To(HandleUserUpdate)) \n\t\t\n\t\t// option b recommended\n\t\t// only bind from ...\n\t\t// preferable  ,always define where you're binding from\n\t\ten.PATCH(\"/:user_id\",easygin.To(HandleUserUpdate,easygin.BindURI,easygin.BindJSON,easygin.BindQuery))\n\t}\n\t\t\n\t```\n\nThat's it , this should now work and bind from all the different parts of the http request\n\n### Gin Key/Value binding\n\nYou might be thinking , what if you had an object that is passed by a middleware via `ctx.Set(_key,_value)` ? \n\nThis package will allow you to also bind from that object (*key word being object , you cannot use a **primitive** value*)\n\nExample\n\n```go\ntype LoginInfo struct {\n\tUserID string // must have the same name as your dto field\n}\n\ntype UserInput struct {\n\tUserID string\n}\n\nfunc (u UserInput) Validate() error {\n\tif u.UserID == \"\" {\n\t\treturn errors.New(\"user id not set\")\n\t}\n\treturn nil\n}\n\nfunc (u UserInput) ValidationErrorFormat(err error) any {\n\treturn map[string]any{\n\t\t\"err\": err.Error(),\n\t}\n}\n\n\nfunc AuthMiddleware(ctx *gin.Context) {\n\tctx.Set(\"user_login_info\", LoginInfo{\n\t\tUserID: \"123\",\n\t})\n\tctx.Next()\n}\n\nfunc HandleUsers(u UserInput) *easygin.Response {\n\treturn easygin.Res(u.UserID) // will return back 123 , which was passed from the auth middlewar \n}\n\nfunc main() {\n\ten := gin.Default()\n\t// BindContext expects the key you used when you used the set function \n\ten.GET(\"_login_info\", AuthMiddleware, easygin.To(HandleUsers, easygin.BindContext(\"user_login_info\"))) \n\ten.Run(\":80\")\n}\n\n```\n\n### Method Documentation\n\n[Click here](https://pkg.go.dev/github.com/baderkha/easy-gin/v1/easygin)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaderkha%2Feasy-gin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbaderkha%2Feasy-gin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaderkha%2Feasy-gin/lists"}