{"id":13753954,"url":"https://github.com/rms1000watt/degeneres","last_synced_at":"2026-01-25T12:12:41.799Z","repository":{"id":92127702,"uuid":"91972065","full_name":"rms1000watt/degeneres","owner":"rms1000watt","description":"Degeneres, the boilerplate generator for REST-like servers in Go!","archived":false,"fork":false,"pushed_at":"2018-05-22T15:19:30.000Z","size":117,"stargazers_count":28,"open_issues_count":1,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-16T06:31:15.579Z","etag":null,"topics":["generator","golang","microservice","protobuf"],"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/rms1000watt.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,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-05-21T16:38:04.000Z","updated_at":"2024-06-20T16:30:57.000Z","dependencies_parsed_at":"2024-01-17T15:02:57.825Z","dependency_job_id":"fbe03554-0366-4187-8ee2-59d4fcc9112a","html_url":"https://github.com/rms1000watt/degeneres","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rms1000watt%2Fdegeneres","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rms1000watt%2Fdegeneres/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rms1000watt%2Fdegeneres/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rms1000watt%2Fdegeneres/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rms1000watt","download_url":"https://codeload.github.com/rms1000watt/degeneres/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253329069,"owners_count":21891572,"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":["generator","golang","microservice","protobuf"],"created_at":"2024-08-03T09:01:35.604Z","updated_at":"2026-01-25T12:12:41.763Z","avatar_url":"https://github.com/rms1000watt.png","language":"Go","funding_links":[],"categories":["protobuf"],"sub_categories":[],"readme":"## Degeneres\n\nDegeneres, the boilerplate generator for REST-like servers in Go!\n\n### Example\n\nAn example server generated with Degeneres can be seen at [http://github.com/rms1000watt/degeneres-test](http://github.com/rms1000watt/degeneres-test)\n\n### Motivation\n\nGolang is a fantastic language! However, you often find yourself writing a ton of boilerplate when writing the same functionality across your multiple struct types. Degeneres was built to generate the boilerplate whenever your structs change or you require different functionality on your structs. \n\nWhile gRPC (leveraging Protobuf) would handle a lot of this functionality, many developer tools and in-production systems aren't able to communicate to gRPC servers (cURL, Postman, Javascript fetch, paying business-customers, etc.) So, Degeneres leverages Protobuf definitions to generate REST-like servers with JSON serialization that majority of systems can communicate with.\n\n### Usage\n\n#### First Time Usage\n\nIn one terminal, get and build the project:\n\n```bash\ngo get -u -v github.com/rms1000watt/degeneres\ncd $(go env GOPATH)/src/github.com/rms1000watt/degeneres\ngo build\n```\n\nCreate a protobuf file at `pb/test.proto`:\n\n```protobuf\nsyntax = \"proto3\";\n\npackage pb;\n\noption (dg.version) = \"v0.1.0\";\noption (dg.author) = \"Ryan Smith\";\noption (dg.project_name) = \"Test Server\";\noption (dg.docker_path) = \"docker.io/rms1000watt/test-server\";\noption (dg.import_path) = \"github.com/rms1000watt/test-server\";\n\nservice Echo {\n    rpc Echo(EchoIn) returns (EchoOut) {\n        option (dg.method) = \"POST\";\n    }\n}\n\nmessage EchoIn {\n    string in = 1 [(dg.validate) = \"maxLength=100\", (dg.transform) = \"hash\"];\n}\n\nmessage EchoOut {\n    string out = 1;\n}\n```\n\nGenerate the server code and cd to it:\n\n```bash\n./degeneres generate -f pb/test.proto -o ../test-server\ncd ../test-server\n```\n\nYou should now have complete server code:\n\n```\n.\n├── Dockerfile\n├── License\n├── Readme.md\n├── cmd\n│   ├── echo.go\n│   ├── root.go\n│   └── version.go\n├── data\n│   ├── data.go\n│   └── input.go\n├── doc.go\n├── echo\n│   ├── config.go\n│   ├── echoHandler.go\n│   └── preServe.go\n├── helpers\n│   ├── handler.go\n│   ├── helpers.go\n│   ├── middlewares.go\n│   ├── transform.go\n│   ├── unmarshal.go\n│   └── validate.go\n├── main.go\n├── pb\n│   └── test.proto\n├── server\n│   ├── config.go\n│   └── echo.go\n└── vendor\n    └── vendor.json\n```\n\nInstall govendor and get vendored libraries:\n\n```bash\ngo get -u -v github.com/kardianos/govendor\ngovendor sync\n```\n\nBuild and start your server with debug level logging:\n\n```bash\ngo build\n./test-server echo --log-level debug\n```\n\nOpen a new terminal. Send a cURL to the server. (You should get a 200 with an empty JSON response: `{}`)\n\n```bash\ncurl -d '{\"in\":\"Hello World\"}' http://localhost:8080/echo\n```\n\nYou get an empty JSON response because the logic to go from Input -\u003e Output is up to you. Edit the handler to fill in the Output logic\n\n```bash\nopen $(go env GOPATH)/src/github.com/rms1000watt/test-server/echo/echoHandler.go\n```\n\nAnd add 1 line to echo the response in `EchoHandlerPOST`:\n\n```go\nechoOut.Out = echoIn.In\n```\n\nNow rebuild and run the server again:\n\n```bash\ncd $(go env GOPATH)/src/github.com/rms1000watt/test-server\ngo build\n./test-server echo --log-level debug\n```\n\nSend the cURL request again:\n\n```bash\ncurl -d '{\"in\":\"Hello World\"}' http://localhost:8080/echo\n```\n\nYou should get a JSON with a hashed value back!\n\n#### Repeated Usage\n\nNaturally, you'll want to update your protobuf file and regenerate.\n\nGo to your project:\n\n```bash\ncd $(go env GOPATH)/src/github.com/rms1000watt/test-server\n```\n\nUpdate your protobuf file `pb/test.proto`:\n\n```protobuf\nsyntax = \"proto3\";\n\npackage pb;\n\noption (dg.version) = \"v0.1.0\";\noption (dg.author) = \"Ryan Smith\";\noption (dg.project_name) = \"Test Server\";\noption (dg.docker_path) = \"docker.io/rms1000watt/test-server\";\noption (dg.import_path) = \"github.com/rms1000watt/test-server\";\n\nservice Echo {\n    option (dg.middleware.logger) = true;\n\n    rpc Echo(EchoIn) returns (EchoOut) {\n        option (dg.middleware.no_cache) = true;\n        option (dg.method) = \"POST\";\n    }\n}\n\nmessage EchoIn {\n    string in   = 1 [(dg.validate) = \"maxLength=100\", (dg.transform) = \"hash\"];\n    int64 age   = 2;\n    string name = 3;\n}\n\nmessage EchoOut {\n    string out  = 1;\n    int64 age   = 2;\n    string name = 3;\n}\n```\n\nRegenerate the code:\n\n```bash\ngo generate\n```\n\nUpdate the handler to go from Input -\u003e Ouput\n\n```bash\nopen $(go env GOPATH)/src/github.com/rms1000watt/test-server/echo/echoHandler.go\n```\n\nRebuild and run\n\n```bash\ngo build\n./test-server echo --log-level debug\n```\n\nSend a cURL \n\n```bash\ncurl -d '{\"in\":\"Hello World\",\"age\":88,\"name\":\"Darf\"}' http://localhost:8080/echo\n```\n\nShould be all good to go!\n\n### Features\n\nDegeneres generates boilerplate so you don't have to. It handles some field level validations \u0026 transformations and has some useful HTTP middleware. You can also generate self-signed certs.\n\n#### Validations\n\nValidations have the Protobuf field level option syntax:\n\n```proto\nstring first_name = 1 [(dg.validate) = \"minLength=2,maxLength=100\"];\n```\n\nString Validations:\n\n| Validation | Usage | Example | Description |\n| --- | --- | --- | --- |\n| Max Length | `maxLength=VALUE` | `maxLength=100` | Fails if len(input) \u003e maxLength |\n| Min Length | `minLength=VALUE` | `minLength=2` | Fails if len(input) \u003e maxLength |\n| Must Have Chars | `mustHaveChars=VALUE` | `mustHaveChars=aeiou` | Fails if chars in VALUE are not in input |\n| Can't Have Chars | `cantHaveChars=VALUE` | `cantHaveChars=aeiou` | Fails if chars in VALUE are in input |\n| Only Have Chars | `onlyHaveChars=VALUE` | `onlyHaveChars=aeiou` | Fails if input has chars not in VALUE |\n\nFloat and Int Validations:\n\n| Validation | Usage | Example | Description |\n| --- | --- | --- | --- |\n| Greater Than | `greaterThan=VALUE` | `greaterThan=100` | Fails if input \u003c VALUE |\n| Less Than | `lessThan=VALUE` | `lessThan=100` | Fails if input \u003e VALUE |\n\n\n#### Transformations\n\nTransformations have the Protobuf field level option syntax:\n\n```proto\nstring first_name = 1 [(dg.transform) = \"truncate=50,hash\"];\n```\n\nAnd can be combined with other options:\n\n```proto\nstring first_name = 1 [(dg.validate) = \"maxLength=100\", (dg.transform) = \"truncate=50,hash\"];\n```\n\nGeneral Trasformation:\n\n| Transformation | Usage | Example | Description |\n| --- | --- | --- | --- |\n| Default | `default=VALUE` | `default=Darf` | Sets input as VALUE if input is nil |\n\nString Transformations:\n\n| Transformation | Usage | Example | Description |\n| --- | --- | --- | --- |\n| Hash | `hash` | `hash` | Essentially `hexEncode(sha256(input))` |\n| Encrypt | `encrypt` | `encrypt` | `aesgcm.Seal` (**DONT USE DEFAULT VALUES OR SCHEME IN PRODUCTION!**) |\n| Decrypt | `decrypt` | `decrypt` | `aesgcm.Open` (**DONT USE DEFAULT VALUES OR SCHEME IN PRODUCTION!**) |\n| Trim Chars | `trimChars=VALUE` | `trimChars=xx` | Uses `strings.Trim(input, VALUE)` |\n| Trim Space | `trimSpace` | `trimSpace` | Uses `strings.TrimSpace(input)` |\n| Truncate | `truncate=VALUE` | `truncate=50` | Essentially `input[:VALUE]` |\n| Password Hash | `passwordHash` | `passwordHash` | argon2 password hashing (**PLEASE INSPECT CODE THOROUGHLY**) |\n\n#### Middleware\n\nMiddleware can be added as `service` or `rpc` options.\n\n```proto\noption (dg.middleware.no_cache) = true;\n```\n\n| Middleware | Usage | Description |\n| --- | --- | --- |\n| Logger | `dg.middleware.logger` | Logs the time for each request | \n| CORS | `dg.middleware.cors` | Adds CORS headers if `option (dg.origins)` is added | \n| Secure | `dg.middleware.secure` | Does TLS Redirect \u0026 adds HSTS, XSS Protection, Nosniff, Frame deny headers | \n| No Cache | `dg.middleware.no_cache` | Adds no cache headers | \n\n#### Self-Signed Keys\n\n```bash\n./degeneres generate certs\n```\n\n### Limitations\n\n- Server generation for Golang only\n- Less performant than gRPC (JSON vs Protobuf)\n\n### TODO\n\n- [x] Fix lexer to include `repeated`\n- [x] Move `data` to different dir\n- [x] Identify if message is input \u0026 create inputP\n- [x] Continue refactoring templates\n- [x] Check for `required` tag first then continue in order\n- [x] Use a logging package\n- [x] Use default Options method\n- [x] Convert generator warnings to errors\n- [x] CORS middleware\n- [x] Check true/false on middleware\n- [x] Vendoring in generated code\n- [x] More middleware: hsts, ssl redirect, xss protection, method logging\n- [x] Add Catch-all, root handler with debug \"path not found\"\n- [x] `go generate` to self regen generated code\n- [x] Copy proto file into generated project\n- [x] Pull handlers into separate package for easier regen\n- [ ] Generator validation on types to handle duplication infile and across imports\n- [ ] More docs\n- [ ] Docs to show all options\n- [ ] More examples\n- [ ] Generate unit tests for helpers\n- [ ] Generate server tests\n- [ ] Add validator to only accept fields in message\n- [x] Example repo in github\n- [x] Expvar\n- [ ] DB connection example (inversion of control)\n- [ ] Docker compose\n- [ ] Workout kinks in workflow\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frms1000watt%2Fdegeneres","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frms1000watt%2Fdegeneres","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frms1000watt%2Fdegeneres/lists"}