{"id":19302398,"url":"https://github.com/dryewo/cyrus","last_synced_at":"2025-12-12T01:29:49.417Z","repository":{"id":57713864,"uuid":"109044856","full_name":"dryewo/cyrus","owner":"dryewo","description":"A very opinionated Clojure project template.","archived":false,"fork":false,"pushed_at":"2019-01-23T21:00:53.000Z","size":153,"stargazers_count":12,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-01T22:38:39.618Z","etag":null,"topics":["clojure","cyrus","leiningen","mount-lite","postgresql","swagger1st","template"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dryewo.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-10-31T19:46:03.000Z","updated_at":"2021-03-15T09:30:24.000Z","dependencies_parsed_at":"2022-08-25T11:02:06.779Z","dependency_job_id":null,"html_url":"https://github.com/dryewo/cyrus","commit_stats":null,"previous_names":["dryewo/miley-cyrus"],"tags_count":38,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dryewo%2Fcyrus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dryewo%2Fcyrus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dryewo%2Fcyrus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dryewo%2Fcyrus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dryewo","download_url":"https://codeload.github.com/dryewo/cyrus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249939612,"owners_count":21348550,"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":["clojure","cyrus","leiningen","mount-lite","postgresql","swagger1st","template"],"created_at":"2024-11-09T23:21:48.238Z","updated_at":"2025-12-12T01:29:49.380Z","avatar_url":"https://github.com/dryewo.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A very opinionated Clojure project template\n\n[![Build Status](https://travis-ci.org/dryewo/cyrus.svg?branch=master)](https://travis-ci.org/dryewo/cyrus)\n[![Clojars Project](https://img.shields.io/clojars/v/cyrus/lein-template.svg)](https://clojars.org/cyrus/lein-template)\n\nIncludes:\n\n* [mount-lite] for state management\n* dev/user.clj for REPL-driven development\n* [timbre] + [dovetail] for logging\n* +http: [aleph] + [Compojure] (and [Ring])\n* +db: PostgreSQL, [conman], [migratus], [HugSQL]\n* +ui: Simple web UI, rendering HTML using [hiccup]\n* +[nrepl]: NREPL server for remote debugging\n* +[swagger1st] (RESTful API), includes +http\n* [Twelve-Factor App] configuration management with [cyrus-config]\n* useful tweaks\n\n## Usage\n\n```\n$ lein new cyrus org.example.footeam/bar-project +http +db +nrepl\n```\n\nRead below for the list of available options.\n\nAdditionally, you can use `+all` option that includes everything.\n\n## Contents\n\n### State management and dependency injection\n\n[mount-lite] does great job managing start and stop order for application components:\n\n```clj\n;; Start everything in the correct order (first database, then webserver, etc.)\n(m/start)\n\n;; Stop everything in the reverse order\n(m/stop)\n```\n\n### Logging\n\nLogging is supported via [timbre].  \nAdditionally, [dovetail] helper library provides useful tweaks:\n* Log level per namespace is configured in core.clj using `log/set-ns-log-levels!`. The resulting log level \nper namespace is the higher of global and specific settings.\n* All logging functions support throwable as their first argument.\n* All formatted values (`%s`) are automatically `pr-str`ed. \n* Overall log level can be overridden by setting `LOG_LEVEL` environment variable.\n\n### HTTP server\n\n`+http` option adds a component that starts an HTTP server ([aleph]).\nExample routes are provided as well as reasonable default middleware.\n\n### Swagger1st API\n\n`+swagger1st` adds `/api` route that is handled by a separate library: [Swagger1st].  \nIt allows request routing and parameter parsing based on [OpenAPI] definition in YAML format.\nHighly recommended for any more or less serious API service.\n\nAdditionally exposes Swagger UI and spec:\n* `/api/ui`\n* `/api/swagger.json` \n\nThis option automatically includes `+http`.\n\nA variant of this option `+swagger1st-oauth2` includes OAuth2 protection of API endpoints, checking\naccess tokens against Introspection Endpoint configured by `TOKENINFO_URL` environment variable.\nSee [fahrscheine-bitte] for more information.\n\n### DB access\n\n`+db` option adds a component that includes database access layer, built on:\n\n* [HugSQL] to generate access functions. Additionally, some interceptors are provided\n  to convert `camel_case` column names to `:kebab-case` keywords in result maps.\n* [conman] for connection pool.\n* [migratus] for schema migrations.\n  Migrations are always applied when the DB component starts up.\n* PostgreSQL JDBC driver is included.\n* Example schema is generated and example unit tests are provided.\n\nPostgreSQL 9.6 for development and testing can be launched in a Docker container:\n\n```sh\n./make.sh db\n```\n\n### Web UI\n\n`+ui` option enables a very simplistic admin UI, served under `/ui`. HTML is rendered using [hiccup]. Static resources\nare served from `resources/ui` resource path, `style.css` is provided as an example.\n\nA variant of this option `+ui-oauth2` includes OAuth2 login with example configuration for GitHub.\nAuthentication which can be configured to be optional or mandatory.\nSee [cyrus-ui-oauth2] for more information.\n\n### NREPL\n\n`+nrepl` adds a NREPL server that is started before the main application is. It has to be enabled\nby setting `NREPL_ENABLED=true`. Default port is `55000`, can be changed by setting `NREPL_PORT`. \n\n### Configuration management\n\nThe app can only be configured through environment variables (following [Twelve-Factor App] manifesto).\nEach environment variable has a corresponding `cfg/def`, which makes a checked and conformed value of that variable\navailable as a global constant (var):\n\n```clj\n(cfg/def port \"Port for HTTP server to listen on.\"\n              {:var-name \"HTTP_PORT\"\n               :spec     int?\n               :default  8090})\n```\n\nOn application startup all defined configuration constants are checked for validation errors and a summary is printed to console:\n\n```\n INFO [main] m.core - Config loaded:\n#'my.nrepl/bind: \"0.0.0.0\" from NREPL_BIND in :default // Network interface for NREPL server to bind to.\n#'my.nrepl/port: 55000 from NREPL_PORT in :default // Port for NREPL server to listen on.\n#'my.http/port: 8090 from HTTP_PORT in :default // Port for HTTP server to listen on.\n#'my.db/password: \u003cSECRET\u003e because DB_PASSWORD is not set // Database password.\n#'my.db/username: \"postgres\" from DB_USERNAME in :default // Database username.\n#'my.db/jdbc-url: \"jdbc:postgresql://localhost:5432/postgres\" from DB_JDBC_URL in :default // Coordinates of the database. Should start with `jdbc:postgresql://`.\n#'my.authenticator/tokeninfo-url: nil because TOKENINFO_URL is not set // URL to check access tokens against. If not set, tokens won't be checked.\n```\n\nFor REPL-driven development a function is provided to override some configuration constants without restarting the REPL:\n\n```clj\n(cfg/reload-with-override! {\"HTTP_PORT\" 8888})\n```\n\nPlease refer to the full documentation on [cyrus-config] home page.\n\n### user.clj\n\nTo facilitate REPL-driven development, `user.clj` contains functions, available directly from `user` namespace after REPL is started:\n\n* `(start)`, which calls `(m/start)`, first re-reading environment variable overrides from `dev-env.edn`.  \n  This is done to enable adjusting environment variables without restarting REPL.\n* `(stop)`, which calls `(m/stop)`  (and just in case reloads configuration too).\n* `(reset)`, which calls `(stop)`, `(refresh)` and then `(start)`.\n* `(tests)`, which runs all tests in all test namespaces.\n* functions from `clojure.tools.namespace`:\n    * wrapped `(refresh)`, which stops started states before reloading and then starts them again \n      (so that you never lose references to running components).\n    * `(refresh-all)`, which reloads all the code in the project.\n\n## Development\n\nIn order to try the template out without releasing to clojars, install it to the local `~/.m2` and specify `--snapshot` flag:\n\n```\n$ lein install\n$ cd target    # Or any other directory\n$ lein new cyrus org.example.footeam/bar-project --snapshot -- +swagger1st +nrepl +db +http\n```\n\n## Testing\n\n```\n$ ./itest.sh\n```\n\n## FAQ\n\n**Q.** Why another template? There is already [Luminus], which is more feature-rich.  \n**A.** While Luminus is a great project, made with a lot of love, it already carries an opinion, which in some points is \n different from mine. Also, even without additional options it generates too much (not always you need front-end, configuration \n management with cprop is IMHO an overkill etc.)  \n Leiningen templates are not extensible, so I had to make my own, reusing best parts of Luminus.\n \n**Q.** Why [mount-lite] and not [component]?  \n**A.** [mount-lite] is chosen over [component] for its lean spirit and ease of use. While in component you have to explicitly\ndefine dependencies between parts of the system, mount-lite (and mount) figure this out by scanning namespace declarations.\nThis is a great example of DRY (don't repeat yourself) principle.  \nIn mount-lite all states (components) have global names (like normal vars created with `def`) and\nare *dereferable* (implementing `IDeref`) — that means, you have to explicitly `@` or `deref` to access them.\n\n**Q.** Why [mount-lite] and not [mount]?  \n**A.** [mount-lite] is chosen over [mount], because:\n* mount supports dereferable states via `(in-cljc-mode)`, but when you try to `deref` a stopped state, mount automatically starts it.\n This leads to surprises and sometimes to impossibility to stop a running system (when you have a background process that\n accesses some state even after you execute `(m/stop)`. The state just starts again, including the states it depends on).  \n mount-lite in this case just throws an exception.\n* In mount it is only possible to start a part of the system (for testing it) by explicitly listing\n states that you need. In mount-lite there is \"start-up-to\" functionality that automatically figures out\n which states does this specific state need and starts only them (see examples in tests in a generated project).\n       \n\n\n## License\n\nCopyright © 2017 Dmitrii Balakhonskii\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n[squeeze]: https://github.com/dryewo/squeeze\n[cyrus-config]: https://github.com/dryewo/cyrus-config\n[mount]: https://github.com/tolitius/mount\n[mount-lite]: https://github.com/aroemers/mount-lite\n[timbre]: https://github.com/ptaoussanis/timbre\n[dovetail]: https://github.com/dryewo/dovetail\n[aleph]: https://github.com/ztellman/aleph\n[Compojure]: https://github.com/weavejester/compojure\n[Ring]: https://github.com/ring-clojure/ring\n[conman]: https://github.com/luminus-framework/conman\n[migratus]: https://github.com/yogthos/migratus\n[HugSQL]: https://www.hugsql.org/\n[nrepl]: https://github.com/clojure/tools.nrepl\n[swagger1st]: https://github.com/zalando-stups/swagger1st\n[Hystrix]: https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-clj\n[component]: https://github.com/stuartsierra/component\n[OpenAPI]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md\n[Twelve-Factor App]: https://12factor.net/config\n[prismatic/schema]: https://github.com/plumatic/schema\n[environ]: https://github.com/weavejester/environ\n[Luminus]: https://github.com/luminus-framework/luminus-template\n[fahrscheine-bitte]: https://github.com/dryewo/fahrscheine-bitte\n[hiccup]: https://github.com/weavejester/hiccup\n[cyrus-ui-oauth2]: https://github.com/dryewo/cyrus-ui-oauth2\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdryewo%2Fcyrus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdryewo%2Fcyrus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdryewo%2Fcyrus/lists"}