{"id":13764084,"url":"https://github.com/mustafaakin/gongular","last_synced_at":"2026-01-27T15:35:07.033Z","repository":{"id":47961730,"uuid":"61715151","full_name":"mustafaakin/gongular","owner":"mustafaakin","description":"A different approach to Go web frameworks","archived":false,"fork":false,"pushed_at":"2020-07-05T14:40:50.000Z","size":715,"stargazers_count":501,"open_issues_count":7,"forks_count":17,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-05-10T17:41:35.373Z","etag":null,"topics":["dependency-injection","framework","go","middleware","web"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mustafaakin.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":"2016-06-22T11:52:42.000Z","updated_at":"2025-02-23T05:40:45.000Z","dependencies_parsed_at":"2022-08-12T15:20:24.660Z","dependency_job_id":null,"html_url":"https://github.com/mustafaakin/gongular","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/mustafaakin/gongular","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafaakin%2Fgongular","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafaakin%2Fgongular/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafaakin%2Fgongular/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafaakin%2Fgongular/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mustafaakin","download_url":"https://codeload.github.com/mustafaakin/gongular/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafaakin%2Fgongular/sbom","scorecard":{"id":668745,"data":{"date":"2025-08-11","repo":{"name":"github.com/mustafaakin/gongular","commit":"a428565ae628319fad27292deb302f8a936a4f8c"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":3,"reason":"Found 8/25 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/mustafaakin/gongular/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/mustafaakin/gongular/test.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 15 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"13 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2022-0236 / GHSA-h86h-8ppg-mxmh","Warn: Project is vulnerable to: GO-2021-0238 / GHSA-83g2-8m93-v3w7","Warn: Project is vulnerable to: GO-2022-0288","Warn: Project is vulnerable to: GO-2022-0969 / GHSA-69cg-p879-7622","Warn: Project is vulnerable to: GO-2022-1144 / GHSA-xrjj-mj9h-534m","Warn: Project is vulnerable to: GO-2023-1571 / GHSA-vvpx-j8f3-3w6h","Warn: Project is vulnerable to: GO-2023-1988 / GHSA-2wrh-6pvc-2jm9","Warn: Project is vulnerable to: GO-2023-2102 / GHSA-4374-p667-p6c8","Warn: Project is vulnerable to: GHSA-qppj-fm5r-hxr3","Warn: Project is vulnerable to: GO-2024-2687 / GHSA-4v7x-pqxf-cx7m","Warn: Project is vulnerable to: GO-2024-3333","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T19:10:01.999Z","repository_id":47961730,"created_at":"2025-08-21T19:10:01.999Z","updated_at":"2025-08-21T19:10:01.999Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28815423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T12:25:15.069Z","status":"ssl_error","status_checked_at":"2026-01-27T12:25:05.297Z","response_time":168,"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":["dependency-injection","framework","go","middleware","web"],"created_at":"2024-08-03T15:01:13.205Z","updated_at":"2026-01-27T15:35:07.018Z","avatar_url":"https://github.com/mustafaakin.png","language":"Go","funding_links":[],"categories":["web框架","Web Frameworks","Go","Web框架","web框架`web 框架`","Utility"],"sub_categories":["版本控制","Advanced Console UIs","HTTP Clients","实用程序/Miscellaneous","Utility/Miscellaneous","版本控制`版本控制相关库`","Fail injection","交流","\u003cspan id=\"高级控制台用户界面-advanced-console-uis\"\u003e高级控制台用户界面 Advanced Console UIs\u003c/span\u003e"],"readme":"![gongular](https://raw.githubusercontent.com/mustafaakin/gongular/master/logo.png)\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/mustafaakin/gongular)](https://goreportcard.com/report/github.com/mustafaakin/gongular)\n[![GoDoc](https://godoc.org/github.com/mustafaakin/gongular?status.svg)](https://godoc.org/github.com/mustafaakin/gongular)\n\n**Note:** gongular recently updated, and if you are looking for the previous version it is tagged as [v.1.0](https://github.com/mustafaakin/gongular/tree/v1.0) \n\ngongular is an HTTP Server Framework for developing APIs easily. It is like Gin Gonic, but it features Angular-like (or Spring like) dependency injection and better input handling. Most of the time, user input must be transformed into a structured data then it must be validated. It takes too much time and is a repetitive work, gongular aims to reduce that complexity by providing request-input mapping with tag based validation.\n\n**Note:** gongular is an opinionated framework and it heavily relies on reflection to achieve these functionality. While there are tests to ensure it works flawlessly, I am open to contributions and opinions on how to make it better. \n\n## Features\n\n* Automatic Query, POST Body, URL Param binding to structs with easy validation\n* Easy and simple dependency injection i.e passing DB connections and other values\n* Custom dependency injection with user specified logic, i.e as User struct from a session\n* Route grouping that allows reducing duplicated code\n* Middlewares that can do preliminary work before routes, groups which might be helpful for authentication checks, logging etc.\n* Static file serving \n* Very fast thanks to httprouter\n\n## Simple Usage\n\ngongular aims to be simple as much as possible while providing flexibility. The below example is enough to reply user with its IP.\n\n```go\ntype WelcomeMessage struct {}\nfunc(w *WelcomeMessage) Handle(c *gongular.Context) error {\n    c.SetBody(c.Request().RemoteAddr)\n}\n\ng := gongular.NewEngine()\ng.GET(\"/\", \u0026WelcomeMessage{})\ng.ListenAndServe(\":8000\")\n```\n\n## How to Use\n\nAll HTTP handlers in gongular are structs with `Handle(c *gongular.Context) error` function or in other words `RequestHandler` interface, implemented. Request handler objects are flexible. They can have various fields, where some of the fields with specific names are special. For instance, if you want to bind the path parameters, your handler object must have field named `Param` which is a flat struct. Also you can have a `Query` field which also maps to query parameters. `Body` field lets you map to JSON body, and `Form` field lets you bind into form submissions with files.\n\n```go\ntype MyHandler struct {\n    Param struct {\n        UserID int       \n    }\n    Query struct {\n        Name  string\n        Age   int\n        Level float64\n    }\n    Body struct {\n        Comment string\n        Choices []string\n        Address struct {\n            City    string\n            Country string\n            Hello   string            \n        }\n    }\n}\nfunc(m *MyHandler) Handle(c *gongular.Context) error {\n    c.SetBody(\"Wow so much params\")\n    return nil\n}\n```\n\n## Path Parameters\n\nWe use julienschmidt/httprouter to multiplex requests and do parametric binding to requests. So the format :VariableName, *somepath is supported in paths. Note that, you can use valid struct tag to validate parameters.\n\n```go\ntype PathParamHandler struct {\n    Param struct {\n        Username string\n    }\n}\nfunc(p *PathParamHandler) Handle(c *Context) error {\n    c.SetBody(p.Param.Username)\n    return nil\n}\n```\n\n## Query Parameters\n\nQuery parameter is very similar to path parameters, the only difference the field name should be `Query` and it should also be a flat struct with no inner parameters or arrays. Query params are case sensitive and use the exact name of the struct property by default. You can use the `q` struct tag to specify the parameter key\n\n```go\ntype QueryParamHandler struct {\n    Query struct {\n        Username string `q:\"username\"`\n        Age int\n    }\n}\nfunc(p *QueryParamHandler) Handle(c *Context) error {\n    println(p.Param.Age)\n    c.SetBody(p.Param.Username)\n    return nil\n}\n```\n\n## JSON Request Body \n\nJSON request bodies can be parsed similar to query parameters, but JSON body can be an arbitrary struct.\n\n```go\ntype BodyParamHandler struct {\n    Body struct {\n        Username string\n        Age int\n        Preferences []string\n        Comments []struct {\n        \tOwnerID int\n        \tMessage string\n        }\n    }\n}\nfunc(p *BodyParamHandler) Handle(c *Context) error {\n    println(p.Body.Age)\n    c.SetBody(p.Body.Preferences + len(c.Body.Comments))\n    return nil\n}\n```\n\n## Forms and File Uploading\n\nPlease note that `Body` and `Form` cannot be both present in the same handler, since the gongular would confuse what to do with the request body.\n\n```go\ntype formHandler struct {\n\tForm struct {\n\t\tAge      int\n\t\tName     string\n\t\tFavorite string\n\t\tFraction float64\n\t}\n}\n\nfunc (q *formHandler) Handle(c *Context) error {\n\tc.SetBody(fmt.Sprintf(\"%d:%s:%s:%.2f\",\n\t\tq.Form.Age, q.Form.Name, q.Form.Favorite, q.Form.Fraction))\n\treturn nil\n}\n\ne.GetRouter().POST(\"/submit\", \u0026formHandler{})\n```\n\n### File Uploading\n\nFor uploaded files, we use a special struct to hold them in the Form value of the request struct. `UploadedFile` holds the `multipart.File` and the `multipart.Header`, you can do anything you want with them.\n\n```go\ntype UploadedFile struct {\n\tFile   multipart.File\n\tHeader *multipart.FileHeader\n}\n```\n\nYou can use it in the handler like the following:\n\n```go\ntype formUploadTest struct {\n\tForm struct {\n\t\tSomeFile     *UploadedFile\n\t\tRegularValue int\n\t}\n}\n\nfunc (f *formUploadTest) Handle(c *Context) error {\n\ts := sha256.New()\n\tio.Copy(s, f.Form.SomeFile.File)\n\tresp := fmt.Sprintf(\"%x:%d\", s.Sum(nil), f.Form.RegularValue)\n\tc.SetBody(resp)\n\treturn nil\n}\n\ne.GetRouter().POST(\"/upload\", \u0026formUploadTest{})\n```\n\n## Routes and Grouping\n\nRoutes can have multiple handlers, called middleware, which might be useful in grouping the requests and doing preliminary work before some routes. For example, the following grouping and routing is valid:  \n\n```go\ntype simpleHandler struct{}\n\nfunc (s *simpleHandler) Handle(c *Context) error {\n\tc.SetBody(\"hi\")\n\treturn nil\n}\n\n// The middle ware that will fail if you supply 5 as a user ID\ntype middlewareFailIfUserId5 struct {\n\tParam struct {\n\t\tUserID int\n\t}\n}\n\nfunc (m *middlewareFailIfUserId5) Handle(c *Context) error {\n\tif m.Param.UserID == 5 {\n\t\tc.Status(http.StatusTeapot)\n\t\tc.SetBody(\"Sorry\")\n\t\tc.StopChain()\n\t}\n\treturn nil\n}\n\nr := e.GetRouter()\n\ng := r.Group(\"/api/user/:UserID\", \u0026middlewareFailIfUserId5{})\ng.GET(\"/name\", \u0026simpleHandler{})\ng.GET(\"/wow\", \u0026simpleHandler{})\n\n/* \n The example responses:\n\n /api/user/5/name -\u003e Sorry \n /api/user/4/name -\u003e hi\n /api/user/1/wow  -\u003e hi\n*/\n```\n\n\n## Field Validation\n\nWe use asaskevich/govalidator as a validation framework. If the supplied input does not pass the validation step, http.StatusBadRequest (400) is returned the user with the cause. Validation can be used in Query, Param, Body or Form type inputs. An example can be seen as follows:\n\n```go\ntype QueryParamHandler struct {\n    Query struct {\n        Username string `valid:\"alpha\"`\n        Age int\n    }\n}\nfunc(p *QueryParamHandler) Handle(c *Context) error {\n    println(p.Param.Age)\n    c.SetBody(p.Param.Username)\n    return nil\n}\n```\n\nIf a request with a non valid username field is set, it returns a `ParseError`.\n\n## Dependency Injection\n\nOne of the thing that makes gongular from other frameworks is that it provides safe value injection to route handlers. It can be used to store database connections, or some other external utility that you want that to be avilable in your handler, but do not want to make it global, or just get it from some other global function that might pollute the space. Supplied dependencies are provided as-is to route handlers and they are private to supplied router, nothing is global.\n\n### Basic Injection\n\nGongular allows very basic injection: You provide a value to gongular.Engine, and it provides you to your handler if you want it in your handler function. It is not like a Guice or Spring like injection, it does not resolve dependencies of the injections, it just provides the value, so that you do not use global values, and it makes the testing easier, since you can just test your handler function by mocking the interfaces you like.\n\n```go\ntype myHandler struct {\n\tParam struct {\n\t\tUserID uint\n\t}\n\tDatabase *sql.DB\n}\n\nfunc (i *myHandler) Handle(c *Context) error {\n\tc.SetBody(fmt.Sprintf(\"%p:%d\", i.Database, i.Param.UserID))\n\treturn nil\n}\n\ndb := new(sql.DB)\ne.Provide(db)\ne.GetRouter().GET(\"/my/db/interaction/:UserID\", \u0026myHandler{})\n```\n\n### Keyed Injection\n\nThe basic injection works great, but if you want to supply same type of value more than once, you have to use keyed injection so that gongular can differ.\n\n```go\ntype injectKey struct {\n\tVal1 int `inject:\"val1\"`\n\tVal2 int `inject:\"val2\"`\n}\n\nfunc (i *injectKey) Handle(c *Context) error {\n\tc.SetBody(i.Val1 * i.Val2)\n\treturn nil\n}\n\ne.ProvideWithKey(\"val1\", 71)\ne.ProvideWithKey(\"val2\", 97)\n\ne.GetRouter().GET(\"/\", \u0026injectKey{})\n```\n\n### Custom Injection\n\nSometimes, providing values as is might not be sufficient for you. You can chose to ping the database, create a transaction, get a value from a pool, and these requires implementing a custom logic. Gongular allows you to write a `CustomProvideFunction` which allows you to provide your preferred value with any logic you like.  \n\n```go\ntype injectCustom struct {\n\tDB *sql.DB\n}\n\nfunc (i *injectCustom) Handle(c *Context) error {\n\tc.SetBody(fmt.Sprintf(\"%p\", i.DB))\n\treturn nil\n}\n\ne := newEngineTest()\n\nvar d *sql.DB\ne.CustomProvide(\u0026sql.DB{}, func(c *Context) (interface{}, error) {\n    d = new(sql.DB)\n    return d, nil\n})\n\ne.GetRouter().GET(\"/\", \u0026injectCustom{})\n```\n\n### Unsafe Injection\n\nThe default `Provide` functions allow you to inject implementations only. Injection of interfaces will not work. During injection, the injector will search for a provided type and fail. For example the following code will not work:\n\n```go\ntype injectKey struct {\n\tDB MySQLInterface `inject:\"db\"`\n}\n\nfunc (i *injectKey) Handle(c *Context) error {\n\tc.SetBody(\"yay\")\n\treturn nil\n}\n\ne.ProvideWithKey(\"db\", \u0026sql.DB{})\n\ne.GetRouter().GET(\"/\", \u0026injectKey{})\n```\n\nThis will cause an injector error. If you want to inject interfaces you must use `ProvideUnsafe`. `ProvideUnsafe` is a strict key/value injection. You cannot provide multiple values for the same key.\n\nExample usage:\n\n\n```go\ntype injectKey struct {\n\tDB MySQLInterface `inject:\"db\"`\n}\n\nfunc (i *injectKey) Handle(c *Context) error {\n\tc.SetBody(\"yay\")\n\treturn nil\n}\n\ne.ProvideUnsafe(\"db\", initializeDB())\n\n// This would cause a panic\n// e.ProvideUnsafe(\"db\", \u0026sql.DB{})\n\ne.GetRouter().GET(\"/\", \u0026injectKey{})\n```\n\n\n## gongular.Context struct\n\n* `context.SetBody(interface{})` : Sets the response body to be serialized.  \n* `context.Status(int)` : Sets the status of the response if not previously set\n* `context.MustStatus(int)` : Overrides the previously written status\n* `context.Request()` : Returns the underlying raw HTTP Request\n* `context.Header(string,string)` : Sets a given response header.   \n* `context.Finalize()` : Used to write the response to client, normally should not be used other than in PanicHandler since gongular takes care of the response.\n* `context.Logger()` : Returns the logger of the context.\n\n## Route Callback\n\nThe route callback, set globally for the engine, allows you to get the stats for the completed request. It contains common info, including the request logs and the matched handlers, how much time it took in each handler, the total time, the total response size written and the final status code, which can be useful for you to send it to another monitoring service, or just some Elasticsearch for log analysis.\n\n```go\ntype RouteStat struct {\n    Request       *http.Request\n    Handlers      []HandlerStat\n    MatchedPath   string\n    TotalDuration time.Duration\n    ResponseSize  int\n    ResponseCode  int\n    Logs          *bytes.Buffer\n}\n```\n\n## Error Handler\n\nIn case you return an error from your function, or another error occurs which makes the request unsatisfiable, `gongular.Engine` calls the error handler function, in which defaults to the following handler:\n\n```go\nvar defaultErrorHandler = func(err error, c *Context) {\n\tc.logger.Println(\"An error has occurred:\", err)\n\n\tswitch err := err.(type) {\n\tcase InjectionError:\n\t\tc.MustStatus(http.StatusInternalServerError)\n\t\tc.logger.Println(\"Could not inject the requested field\", err)\n\tcase ValidationError:\n\t\tc.MustStatus(http.StatusBadRequest)\n\t\tc.SetBody(map[string]interface{}{\"ValidationError\": err})\n\tcase ParseError:\n\t\tc.MustStatus(http.StatusBadRequest)\n\t\tc.SetBody(map[string]interface{}{\"ParseError\": err})\n\tdefault:\n\t\tc.SetBody(err.Error())\n\t\tc.MustStatus(http.StatusInternalServerError)\n\t}\n\n\tc.StopChain()\n}\n```\n\n## WebSockets\n\nGongular supports websocket connections as well. The handler function is similar to regular route handler interface, but it also allows connection termination if you wish with the `Before` handler.\n\n```go\ntype WebsocketHandler interface {\n\tBefore(c *Context) (http.Header, error)\n\tHandle(conn *websocket.Conn)\n}\n```\n\nFirst of all, handle function does not return an error, since it is a continuous execution. User is responsible for all the websocket interaction. Secondly, Before is a filter applied just before upgrading the request to websocket. It can be useful for filtering the request and returning an error would not open a websocket but close it with an error. The http.Header is for answering with a http.Header which allows setting a cookie. Can be omitted if not desired.\n\nThe nice thing about WebsocketHandler is that it supports Param and Query requests as well, so that all the binding and validation can be done before the request, and you can use it in your handler. \n\n```go\ntype wsTest struct {\n\tParam struct {\n\t\tUserID int\n\t}\n\tQuery struct {\n\t\tTrack    bool\n\t\tUsername string\n\t}\n}\n\nfunc (w *wsTest) Before(c *Context) (http.Header, error) {\n\treturn nil, nil\n}\n\nfunc (w *wsTest) Handle(conn *websocket.Conn) {\n\t_, msg, err := conn.ReadMessage()\n\tif err != nil {\n\t\tconn.Close()\n\t}\n\n\ttoSend := fmt.Sprintf(\"%s:%d:%s:%t\", msg, w.Param.UserID, w.Query.Username, w.Query.Track)\n\tconn.WriteMessage(websocket.TextMessage, []byte(toSend))\n\tconn.Close()\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmustafaakin%2Fgongular","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmustafaakin%2Fgongular","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmustafaakin%2Fgongular/lists"}