{"id":36482233,"url":"https://github.com/ifreddyrondon/bastion","last_synced_at":"2026-01-12T01:03:08.751Z","repository":{"id":57531077,"uuid":"111224500","full_name":"ifreddyrondon/bastion","owner":"ifreddyrondon","description":"Defend your go api from the sieges 🏰","archived":false,"fork":false,"pushed_at":"2019-05-28T04:13:24.000Z","size":3161,"stargazers_count":8,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-06-21T08:15:54.278Z","etag":null,"topics":["go","golang","middleware","render","router","server"],"latest_commit_sha":null,"homepage":"https://godoc.org/github.com/ifreddyrondon/bastion","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/ifreddyrondon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2017-11-18T17:22:46.000Z","updated_at":"2024-01-19T10:25:00.000Z","dependencies_parsed_at":"2022-09-05T10:01:54.367Z","dependency_job_id":null,"html_url":"https://github.com/ifreddyrondon/bastion","commit_stats":null,"previous_names":["ifreddyrondon/gobastion"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/ifreddyrondon/bastion","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ifreddyrondon%2Fbastion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ifreddyrondon%2Fbastion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ifreddyrondon%2Fbastion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ifreddyrondon%2Fbastion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ifreddyrondon","download_url":"https://codeload.github.com/ifreddyrondon/bastion/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ifreddyrondon%2Fbastion/sbom","scorecard":{"id":482184,"data":{"date":"2025-08-11","repo":{"name":"github.com/ifreddyrondon/bastion","commit":"9e596beec6eb06adf9a8a4e8a39d3961dd9ae0e2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","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":0,"reason":"Found 0/21 approved changesets -- score normalized to 0","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":"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":-1,"reason":"No tokens found","details":null,"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":"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":"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":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"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":"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":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"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":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 28 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"}}]},"last_synced_at":"2025-08-19T16:54:44.587Z","repository_id":57531077,"created_at":"2025-08-19T16:54:44.587Z","updated_at":"2025-08-19T16:54:44.587Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28330168,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T00:36:25.062Z","status":"ssl_error","status_checked_at":"2026-01-12T00:36:15.229Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":["go","golang","middleware","render","router","server"],"created_at":"2026-01-12T01:03:08.670Z","updated_at":"2026-01-12T01:03:08.743Z","avatar_url":"https://github.com/ifreddyrondon.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bastion\n\n[![Documentation](https://godoc.org/github.com/ifreddyrondon/bastion?status.svg)](http://godoc.org/github.com/ifreddyrondon/bastion)\n[![Coverage Status](https://coveralls.io/repos/github/ifreddyrondon/bastion/badge.svg?branch=master)](https://coveralls.io/github/ifreddyrondon/bastion?branch=master)\n[![Go Report Card](https://goreportcard.com/badge/github.com/ifreddyrondon/bastion)](https://goreportcard.com/report/github.com/ifreddyrondon/bastion)\n[![CircleCI](https://circleci.com/gh/ifreddyrondon/bastion.svg?style=svg)](https://circleci.com/gh/ifreddyrondon/bastion)\n\nDefend your API from the sieges. Bastion offers an \"augmented\" Router instance.\n\nIt has the minimal necessary to create an API with default handlers and middleware that help you raise your API easy and fast.\nAllows to have commons handlers and middleware between projects with the need for each one to do so. It's also included some \nuseful/optional subpackages: [middleware](https://github.com/ifreddyrondon/bastion/blob/master/middleware) and [render](https://github.com/ifreddyrondon/bastion/blob/master/render). We hope you enjoy it too!\n\n## Installation\n\n`go get -u github.com/ifreddyrondon/bastion`\n\n## Examples\n\nSee [_examples/](https://github.com/ifreddyrondon/bastion/blob/master/_examples/) for a variety of examples.\n\n**As easy as:**\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n\n    \"github.com/ifreddyrondon/bastion\"\n    \"github.com/ifreddyrondon/bastion/render\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\trender.JSON.Send(w, map[string]string{\"message\": \"hello bastion\"})\n}\n\nfunc main() {\n\tapp := bastion.New()\n\tapp.Get(\"/hello\", handler)\n\t// By default it serves on :8080 unless a\n\t// ADDR environment variable was defined.\n\tapp.Serve()\n\t// app.Serve(\":3000\") for a hard coded port\n}\n```\n\n## Router\n\nBastion use [go-chi](https://github.com/go-chi/chi) as a router making it easy to modularize the applications. \nEach Bastion instance accepts a URL `pattern` and chain of `handlers`. The URL pattern supports \nnamed params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters can be fetched \nat runtime by calling `chi.URLParam(r, \"userID\")` for named parameters and `chi.URLParam(r, \"*\")` \nfor a wildcard parameter.\n\n### NewRouter\n\nNewRouter return a router as a subrouter along a routing path.\n\nIt's very useful to split up a large API as many independent routers and compose them as a single service.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/go-chi/chi\"\n\t\"github.com/ifreddyrondon/bastion\"\n\t\"github.com/ifreddyrondon/bastion/render\"\n)\n\n// Routes creates a REST router for the todos resource\nfunc routes() http.Handler {\n\tr := bastion.NewRouter()\n\n\tr.Get(\"/\", list)    // GET /todos - read a list of todos\n\tr.Post(\"/\", create) // POST /todos - create a new todo and persist it\n\tr.Route(\"/{id}\", func(r chi.Router) {\n\t\tr.Get(\"/\", get)       // GET /todos/{id} - read a single todo by :id\n\t\tr.Put(\"/\", update)    // PUT /todos/{id} - update a single todo by :id\n\t\tr.Delete(\"/\", delete) // DELETE /todos/{id} - delete a single todo by :id\n\t})\n\n\treturn r\n}\n\nfunc list(w http.ResponseWriter, r *http.Request) {\n\trender.Text.Response(w, http.StatusOK, \"todos list of stuff..\")\n}\n\nfunc create(w http.ResponseWriter, r *http.Request) {\n\trender.Text.Response(w, http.StatusOK, \"todos create\")\n}\n\nfunc get(w http.ResponseWriter, r *http.Request) {\n\tid := chi.URLParam(r, \"id\")\n\trender.Text.Response(w, http.StatusOK, fmt.Sprintf(\"get todo with id %v\", id))\n}\n\nfunc update(w http.ResponseWriter, r *http.Request) {\n\tid := chi.URLParam(r, \"id\")\n\trender.Text.Response(w, http.StatusOK, fmt.Sprintf(\"update todo with id %v\", id))\n}\n\nfunc delete(w http.ResponseWriter, r *http.Request) {\n\tid := chi.URLParam(r, \"id\")\n\trender.Text.Response(w, http.StatusOK, fmt.Sprintf(\"delete todo with id %v\", id))\n}\n\nfunc main() {\n\tapp := bastion.New()\n\tapp.Mount(\"/todo/\", routes())\n\tfmt.Fprintln(os.Stderr, app.Serve())\n}\n```\n\n## Middlewares\n\nBastion comes equipped with a set of commons middleware handlers, providing a suite of standard `net/http` middleware.\nThey are just stdlib net/http middleware handlers. There is nothing special about them, which means the router and all \nthe tooling is designed to be compatible and friendly with any middleware in the community.\n\n### Core middleware\n\nName | Description\n---- | -----------\nLogger | Logs the start and end of each request with the elapsed processing time.\nRequestID | Injects a request ID into the context of each request.\nRecovery | Gracefully absorb panics and prints the stack trace.\nInternalError | Intercept responses to verify if his status code is \u003e= 500. If status is \u003e= 500, it'll response with a [default error](#InternalErrMsg). IT allows to response with the same error without disclosure internal information, also the real error is logged.\n\n### Auxiliary middleware\n\nName | Description\n---- | -----------\nListing | Parses the url from a request and stores a [listing.Listing](https://github.com/ifreddyrondon/bastion/blob/master/middleware/listing/listing.go#L11) on the context, it can be accessed through middleware.GetListing.\nWrapResponseWriter | provides an easy way to capture http related metrics from your application's http.Handlers or event hijack the response. \n\nCheckout for references, examples, options and docu in [middleware](https://github.com/ifreddyrondon/bastion/blob/master/middleware) or [chi](https://github.com/go-chi/chi/tree/master#middlewares) for more middlewares. \n\n## Register on shutdown\n\nYou can register a function to call on shutdown. This can be used to gracefully shutdown connections. By default the shutdown execute the server shutdown.\n\nBastion listens if any **SIGINT**, **SIGTERM** or **SIGKILL** signal is emitted and performs a graceful shutdown.\n\nIt can be added with `RegisterOnShutdown` method of the bastion instance, it can accept variable number of functions.\n\n### Register on shutdown example\n\n```go\npackage main\n\nimport (\n    \"log\"\n\n    \"github.com/ifreddyrondon/bastion\"\n)\n\nfunc onShutdown() {\n    log.Printf(\"My registered on shutdown. Doing something...\")\n}\n\nfunc main() {\n    app := bastion.New()\n    app.RegisterOnShutdown(onShutdown)\n    app.Serve(\":8080\")\n}\n```\n\n## Options\n\nOptions are used to define how the application should run, it can be set through optionals functions when using `bastion.New()`.\n\n```go\npackage main\n\nimport (\n    \"github.com/ifreddyrondon/bastion\"\n)\n\nfunc main() {\n\t// turn off pretty print logger and sets 500 errors message\n\tbastion.New(bastion.DisablePrettyLogging(), bastion.InternalErrMsg(`Just another \"500 - internal error\"`))\n}\n```\n\n### InternalErrMsg\n\nRepresent the message returned to the user when a http 500 error is caught by the InternalError middleware. \nDefault `looks like something went wrong`.\n\n- `InternalErrMsg(msg string)` set the message returned to the user when catch a 500 status error.\n\n### DisableInternalErrorMiddleware\n\nBoolean flag to disable the [internal error middleware](https://github.com/go-chi/chi/tree/master#middlewares). Default `false`.\n\n- `DisableInternalErrorMiddleware()` turn off internal error middleware.\n\n### DisableRecoveryMiddleware\n\nBoolean flag to disable [recovery middleware](https://github.com/go-chi/chi/tree/master#middlewares). Default `false`.\n\n- `DisableRecoveryMiddleware()` turn off recovery middleware.\n\n### DisablePingRouter\n\nBoolean flag to disable the ping route. Default `false`.\n\n- `DisablePingRouter()` turn off ping route.\n\n### DisableLoggerMiddleware\n\nBoolean flag to disable the logger middleware. Default `false`.\n\n- `DisableLoggerMiddleware()` turn off logger middleware.\n\n### DisablePrettyLogging\n\nBoolean flag to don't output a colored human readable version on the out writer. Default `false`.\n\n- `DisablePrettyLogging()` turn off the pretty logging.\n\n### LoggerLevel\n\nDefines log level. Default `debug`. Allows for logging at the following levels (from highest to lowest):\n\n- panic, 5\n- fatal, 4\n- error, 3\n- warn, 2\n- info, 1\n- debug, 0\n\n- `LoggerLevel(lvl string)` set the logger level.\n\n```go\npackage main\n\nimport (\n    \"github.com/ifreddyrondon/bastion\"\n)\n\nfunc main() {\n\tbastion.New(bastion.LoggerLevel(bastion.ErrorLevel))\n\t// or\n\tbastion.New(bastion.LoggerLevel(\"error\"))\n}\n```\n\n### LoggerOutput\n\nWhere the logger output write. Default `os.Stdout`.\n\n- `LoggerOutput(w io.Writer)` set the logger output writer.\n\n### ProfilerRoutePrefix \n\nOptional path prefix for profiler subrouter. If left unspecified, `/debug/` is used as the default path prefix.\n\n- `ProfilerRoutePrefix(prefix string)` set the prefix path for the profile router.\n\n### EnableProfiler \n\nBoolean flag to enable the profiler subrouter in production mode.\n\n- `EnableProfiler()` turn on profiler subrouter.\n\n### Mode\n\nMode in which the App is running. Default is \"debug\". \nCan be set using `Mode(string)` option or with **ENV** vars `GO_ENV` or `GO_ENVIRONMENT`. `Mode(mode string)` has more priority \nthan the ENV variables. \n\nWhen **production** mode is on, the request logger IP, UserAgent and Referer are enable, the logger level is set \nto `error` (is not set with LoggerLevel option), the profiler routes are disable (is not set with EnableProfiler option) \nand the logging pretty print is disabled.\n\n- `Mode(mode string)` set the mode in which the App is running.\n\n```go\npackage main\n\nimport (\n    \"github.com/ifreddyrondon/bastion\"\n)\n\nfunc main() {\n\tbastion.New(bastion.Mode(bastion.DebugMode))\n\t// or\n\tbastion.New(bastion.Mode(\"production\"))\n}\n```\n\n## Testing\n\nBastion comes with battery included testing tools to perform End-to-end test over your endpoint/handlers.\n\nIt uses [github.com/gavv/httpexpect](https://github.com/gavv/httpexpect) to incrementally build HTTP requests,\ninspect HTTP responses and inspect response payload recursively.\n\n### Quick start\n\n1. Create the bastion instance with the handler you want to test.\n2. Import from `bastion.Tester`\n3. It receive a `*testing.T` and `*bastion.Bastion` instances as params.\n4. Build http request.\n5. Inspect http response.\n6. Inspect response payload.\n\n```go\npackage main_test\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/ifreddyrondon/bastion\"\n\t\"github.com/ifreddyrondon/bastion/_examples/todo-rest/todo\"\n    \"github.com/ifreddyrondon/bastion/render\"\n)\n\nfunc setup() *bastion.Bastion {\n\tapp := bastion.New()\n\tapp.Mount(\"/todo/\", todo.Routes())\n\treturn app\n}\n\nfunc TestHandlerCreate(t *testing.T) {\n\tapp := setup()\n\tpayload := map[string]interface{}{\n\t\t\"description\": \"new description\",\n\t}\n\n\te := bastion.Tester(t, app)\n\te.POST(\"/todo/\").WithJSON(payload).Expect().\n\t\tStatus(http.StatusCreated).\n\t\tJSON().Object().\n\t\tContainsKey(\"id\").ValueEqual(\"id\", 0).\n\t\tContainsKey(\"description\").ValueEqual(\"description\", \"new description\")\n}\n```\n\nGo and check the [full test](https://github.com/ifreddyrondon/bastion/blob/master/_examples/todo-rest/todo/handler_test.go) for [handler](https://github.com/ifreddyrondon/bastion/blob/master/_examples/todo-rest/todo/handler.go) and complete [app](https://github.com/ifreddyrondon/bastion/tree/master/_examples/todo-rest) 🤓\n\n## Render\n\nEasily rendering JSON, XML, binary data, and HTML templates responses \n\n### Usage\nIt can be used with pretty much any web framework providing you can access the `http.ResponseWriter` from your handler.\nThe rendering functions simply wraps Go's existing functionality for marshaling and rendering data.\n\n```go\npackage main\n\nimport (\n\t\"encoding/xml\"\n\t\"net/http\"\n\n\t\"github.com/ifreddyrondon/bastion\"\n\t\"github.com/ifreddyrondon/bastion/render\"\n)\n\ntype ExampleXML struct {\n\tXMLName xml.Name `xml:\"example\"`\n\tOne     string   `xml:\"one,attr\"`\n\tTwo     string   `xml:\"two,attr\"`\n}\n\nfunc main() {\n\tapp := bastion.New()\n\n\tapp.Get(\"/data\", func(w http.ResponseWriter, req *http.Request) {\n\t\trender.Data.Response(w, http.StatusOK, []byte(\"Some binary data here.\"))\n\t})\n\n\tapp.Get(\"/text\", func(w http.ResponseWriter, req *http.Request) {\n\t\trender.Text.Response(w, http.StatusOK, \"Plain text here\")\n\t})\n\n\tapp.Get(\"/html\", func(w http.ResponseWriter, req *http.Request) {\n\t\trender.HTML.Response(w, http.StatusOK, \"\u003ch1\u003eHello World\u003c/h1\u003e\")\n\t})\n\n\tapp.Get(\"/json\", func(w http.ResponseWriter, req *http.Request) {\n\t\trender.JSON.Response(w, http.StatusOK, map[string]string{\"hello\": \"json\"})\n\t})\n\n\tapp.Get(\"/json-ok\", func(w http.ResponseWriter, req *http.Request) {\n\t\t// with implicit status 200\n\t\trender.JSON.Send(w, map[string]string{\"hello\": \"json\"})\n\t})\n\n\tapp.Get(\"/xml\", func(w http.ResponseWriter, req *http.Request) {\n\t\trender.XML.Response(w, http.StatusOK, ExampleXML{One: \"hello\", Two: \"xml\"})\n\t})\n\n\tapp.Get(\"/xml-ok\", func(w http.ResponseWriter, req *http.Request) {\n\t\t// with implicit status 200\n\t\trender.XML.Send(w, ExampleXML{One: \"hello\", Two: \"xml\"})\n\t})\n\n\tapp.Serve()\n}\n```\n\nCheckout more references, examples, options and implementations in [render](https://github.com/ifreddyrondon/bastion/blob/master/render).\n\n## Binder\n\nTo bind a request body or a source input into a type, use a binder. It's currently support binding of JSON, XML and YAML.\nThe binding execute `Validate()` if the type implements the `binder.Validate` interface after successfully bind the type.\n\nThe goal of implement `Validate` is to endorse the values linked to the type. This library intends for you to handle \nyour own validations error.\n\n### Usage\n\n```go\npackage main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/ifreddyrondon/bastion\"\n\t\"github.com/ifreddyrondon/bastion/binder\"\n\t\"github.com/ifreddyrondon/bastion/render\"\n)\n\ntype address struct {\n\tAddress *string `json:\"address\" xml:\"address\" yaml:\"address\"`\n\tLat     float64 `json:\"lat\" xml:\"lat\" yaml:\"lat\"`\n\tLng     float64 `json:\"lng\" xml:\"lng\" yaml:\"lng\"`\n}\n\nfunc (a *address) Validate() error {\n\tif a.Address == nil || *a.Address == \"\" {\n\t\treturn errors.New(\"missing address field\")\n\t}\n\treturn nil\n}\n\nfunc main() {\n\tapp := bastion.New()\n\tapp.Post(\"/decode-json\", func(w http.ResponseWriter, r *http.Request) {\n\t\tvar a address\n\t\tif err := binder.JSON.FromReq(r, \u0026a); err != nil {\n\t\t\trender.JSON.BadRequest(w, err)\n\t\t\treturn\n\t\t}\n\t\trender.JSON.Send(w, a)\n\t})\n\tapp.Post(\"/decode-xml\", func(w http.ResponseWriter, r *http.Request) {\n\t\tvar a address\n\t\tif err := binder.XML.FromReq(r, \u0026a); err != nil {\n\t\t\trender.JSON.BadRequest(w, err)\n\t\t\treturn\n\t\t}\n\t\trender.JSON.Send(w, a)\n\t})\n\tapp.Post(\"/decode-yaml\", func(w http.ResponseWriter, r *http.Request) {\n\t\tvar a address\n\t\tif err := binder.YAML.FromReq(r, \u0026a); err != nil {\n\t\t\trender.JSON.BadRequest(w, err)\n\t\t\treturn\n\t\t}\n\t\trender.JSON.Send(w, a)\n\t})\n\tapp.Serve()\n}\n```\n\nCheckout more references, examples, options and implementations in [binder](https://github.com/ifreddyrondon/bastion/blob/master/binder).\n\n## Logger\n\nBastion have an internal JSON structured logger powered by [github.com/rs/zerolog](github.com/rs/zerolog). \nIt can be accessed from the context of each request `l := bastion.LoggerFromCtx(ctx)`. The request id is logged for \nevery call to the logger.\n\n```go\npackage main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/ifreddyrondon/bastion\"\n\t\"github.com/ifreddyrondon/bastion/render\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\tl := bastion.LoggerFromCtx(r.Context())\n\tl.Info().Msg(\"handler\")\n\n\trender.JSON.Send(w, map[string]string{\"message\": \"hello bastion\"})\n}\n\nfunc main() {\n\tapp := bastion.New()\n\tapp.Get(\"/hello\", handler)\n\tapp.Serve()\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fifreddyrondon%2Fbastion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fifreddyrondon%2Fbastion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fifreddyrondon%2Fbastion/lists"}