{"id":17523064,"url":"https://github.com/mainmatter/gerust","last_synced_at":"2025-05-16T17:09:21.722Z","repository":{"id":211070384,"uuid":"727329357","full_name":"mainmatter/gerust","owner":"mainmatter","description":"Gerust is a project generator for Rust backend projects. It takes care of the accidental complexity so you can stay focused on what matters.","archived":false,"fork":false,"pushed_at":"2025-05-12T03:30:37.000Z","size":2145,"stargazers_count":122,"open_issues_count":13,"forks_count":10,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-05-14T00:46:22.921Z","etag":null,"topics":["backend","rust"],"latest_commit_sha":null,"homepage":"https://gerust.rs","language":"Liquid","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/mainmatter.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,"zenodo":null}},"created_at":"2023-12-04T16:40:34.000Z","updated_at":"2025-05-11T07:55:00.000Z","dependencies_parsed_at":"2024-01-10T16:41:07.425Z","dependency_job_id":"e30af03d-c322-48a0-b9d9-cb37d64aad43","html_url":"https://github.com/mainmatter/gerust","commit_stats":{"total_commits":460,"total_committers":6,"mean_commits":76.66666666666667,"dds":"0.21304347826086956","last_synced_commit":"8c22b0648053b3366322b78916bde4f2dfef97ec"},"previous_names":["marcoow/pacesetter"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mainmatter%2Fgerust","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mainmatter%2Fgerust/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mainmatter%2Fgerust/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mainmatter%2Fgerust/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mainmatter","download_url":"https://codeload.github.com/mainmatter/gerust/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254092876,"owners_count":22013295,"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":["backend","rust"],"created_at":"2024-10-20T13:22:56.361Z","updated_at":"2025-05-16T17:09:21.699Z","avatar_url":"https://github.com/mainmatter.png","language":"Liquid","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gerust\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://gerust.rs/img/logo-dark-mode.svg\"\u003e\n  \u003cimg alt=\"Gerust logo\" src=\"https://gerust.rs/img/logo.svg\"\u003e\n\u003c/picture\u003e\n\nGerust provides an architecture and tooling for Rust backend projects. It takes care of the accidental complexity that comes with writing backends with Rust so you can stay focused on the essence of the system you're building:\n\n* Separating distinct parts of the system into separate crates\n* Organizing files into a logical folder structure\n* Maintaining and running database migrations\n* Isolating test cases that access the database\n* Tracing and error handling\n* and much more\n\nFor now, Gerust is just a project generator that creates the files and structure to get you started. There is no runtime dependency on Gerust – all the code that goes into your project remains under your control.\n\nGerust projects are based on [axum](https://crates.io/crates/axum) and use [sqlx](https://crates.io/crates/sqlx) and PostgreSQL for data storage (if data storage is used at all).\n\n\u003e [!NOTE]\n\u003e This project has been created by [Mainmatter](https://mainmatter.com/rust-consulting/).  \n\u003e Check out our [landing page](https://mainmatter.com/rust-consulting/) if you're looking for Rust consulting or training!\n\n## Installation\n\nGerust can be installed with cargo:\n\n```\ncargo install gerust\n```\n\n## Creating a new project\n\nA new project is created with the `gerust` command, e.g.:\n\n```\ngerust my-app\n```\n\nBy default, Gerust will generate an empty project with the complete project structure as described below but without any actual entities, controllers, etc. If you're just getting started looking at Gerust, creating a full project, complete with example implementations of all concepts via `--full` might be a better starting point:\n\n```\ngerust my-app --full\n```\n\nFor projects that do not need database access, there is also the `--minimal` option that will generate a project without any of the concepts and structure related to database access – no `db` crate, no [sqlx](https://crates.io/crates/sqlx) dependency.\n\n## Project Structure\n\nGerust uses [Cargo workspaces](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html) to separate distinct parts of the system into separate crates:\n\n```\n.\n├── cli    // CLI tools for e.g. running DB migrations or generating project files\n├── config // Defines the `Config` struct and handles building the configuration from environment-specific TOML files and environment variables\n├── db     // Encapsulates database access, migrations, as well as entity definitions and related code (if the project uses a database)\n├── macros // Contains macros, e.g. for application tests\n└── web    // The web interface as well as tests for it\n```\n\nLet's see what these crates are resonsible for and how they work in detail:\n\n### The `web` crate\n\nThe `web` crate contains the main [axum](https://crates.io/crates/axum) application, providing the web interface of the system. It contains the controllers with the implementations of the exposed endpoints, as well as any middlewares. The `web` crate also contains the application's main executable, which when starting up, will determine the environment the application runs in, load the configuration, initialize the app state, set up tracing and error handling, and bind the server to the configured interface.\n\nThe crate uses a simple folder structure:\n\n```\nweb\n├── controllers // Controllers implement request handlers for the exposed endpoints\n├── middlewares // Tower middlewares for pre-processing requests before they are passed to the request handlers\n├── lib.rs      // Code for starting up the server\n├── main.rs     // Main entrypoint of the application\n├── routes.rs   // Mapping of request handlers to routes\n├── state.rs    // Definition and construction of the application state\n└── tests       // Application tests\n```\n\n#### Testing\n\nApplication tests that cover the entire stack of the system including middlewares, controller, as well as database access are maintained in the `web` crate.\n\nTesting backends is typically straight forward: request a particular endpoint with a particular method and potentially query string and/or request body and assert the response is what you expect. However, things become more complicated when the server you're testing uses a database. In your tests, you then need to seed the database with test data to establish a well-defined state for the test. You also need to clean up afterwards or better, use isolated database states for the different tests so they don't interfere with each other. There are several mechanisms for that like transactions, cleanup scripts, etc.\n\nGerust uses an approach for test isolation that allows parallel execution of tests without adding a ton of complexity: every test runs in its own database. These test-specific databases are automatically created as copies of the main test database and destroyed after the test has completed. All that is made easily available via the `[db_test]` macro:\n\n```rs\npub struct DbTestContext {\n    /// The axum application that is being tested.\n    pub app: Router,\n    /// A connection pool connected to the test-specific database; the app is set up to use this database automatically\n    pub db_pool: DbPool,\n}\n\n#[db_test]\nasync fn test_read_all(context: \u0026DbTestContext) {\n    let task_changeset: TaskChangeset = Faker.fake();\n    create_task(task_changeset.clone(), \u0026context.db_pool) // create a task in the database\n        .await\n        .unwrap();\n\n    let response = context\n        .app\n        .request(\"/tasks\")\n        .method(Method::GET)\n        .send()\n        .await;                                           // load all tasks\n\n    assert_that!(response.status(), eq(StatusCode::OK));\n\n    let tasks: TasksList = response.into_body().into_json::\u003cTasksList\u003e().await;\n    assert_that!(tasks, len(eq(1)));\n    assert_that!(                                         // assert the task created above is returned (as the application uses the same database)\n        tasks.first().unwrap().description,\n        eq(task_changeset.description)\n    );\n}\n```\n\nThe concept of changesets as well as the database access utilities like `create_task`, are explained below.\n\n### The `db` crate\n\nThe `db` crate only exists for projects that use a database and contains all functionality related to database access from entity definitions, functions for reading and writing data, as well as migrations. Gerust uses [sqlx](https://crates.io/crates/sqlx) and PostgreSQL without any additional ORM on top. Instead, it defines entities as simple structs along with functions for retrieving and persisting those entities. Validations are implemented via changesets that can get applied to or be converted to entities if they are valid:\n\n```rs\n#[derive(Serialize, Debug, Deserialize)]\npub struct Task {                                            // a Task entity with UUID id and text description\n    pub id: Uuid,\n    pub description: String,\n}\n\n#[derive(Deserialize, Validate, Clone)]\npub struct TaskChangeset {                                   // the changeset definition for the Task entity; it requires description to have a minimum length of 1\n    #[validate(length(min = 1))]\n    pub description: String,\n}\n\npub async fn load(                                           // Function for loading a Task for an id\n    id: Uuid,\n    executor: impl sqlx::Executor\u003c'_, Database = Postgres\u003e,\n) -\u003e Result\u003cTask, crate::Error\u003e {\n    match sqlx::query_as!(Task, \"SELECT id, description FROM tasks WHERE id = $1\", id)\n        .fetch_optional(executor)\n        .await\n        .map_err(|e| crate::Error::DbError(e.into()))?\n    {\n        Some(task) =\u003e Ok(task),\n        None =\u003e Err(crate::Error::NoRecordFound),\n    }\n}\n\npub async fn create(                                         // Function for creating a Task in the database\n    task: TaskChangeset,\n    executor: impl sqlx::Executor\u003c'_, Database = Postgres\u003e,\n) -\u003e Result\u003cTask, crate::Error\u003e {\n    task.validate()?; // Validate the changeset and return Err(…) if it isn't valid\n\n    let record = sqlx::query!(                               // Store the data in the database\n        \"INSERT INTO tasks (description) VALUES ($1) RETURNING id\",\n        task.description\n    )\n    .fetch_one(executor)\n    .await\n    .map_err(|e| crate::Error::DbError(e.into()))?;\n\n    Ok(Task {                                                // Return a Task entity\n        id: record.id,\n        description: task.description,\n    })\n}\n```\n\nDatabase queries are checked for correctness at compile time using sqlx's [compile-time checked queries](https://github.com/launchbadge/sqlx/blob/main/README.md#sqlx-is-not-an-orm).\n\nThe crate's folder structure consists of 3 main folders:\n\n```\ndb\n├── migrations       // Database migrations as plain SQL files\n├── src\n    ├── entities     // Entity structs, changesets and related functions for retrieving and persisting records (see example above)\n    └── test-helpers // Functions for retrieving and persisting records that are only relevant for tests (these are defined behind the `test-helpers` feature)\n```\n\nTest helpers allow to make specific database access functions available only for application tests but not for actual application code. If e.g. the system does not allow for creating new user accounts but tests need to be able to create users, a `create_user` function could be defined in `db/src/test_helpers/users.rs` in the `db` crate.\n\n### The `config` crate\n\nThe `config` crate contains the `Config` struct that holds all configuration values at runtime as well as code for parsing the configuration based on a hierarchy of TOML files and environment variables. The `Config` struct contains fields for the server and database configuration (if the application uses a database) and can be extended freely:\n\n```rs\n#[derive(Deserialize, Clone, Debug)]\npub struct Config {\n    pub server: ServerConfig,\n    pub database: DatabaseConfig, // The database configuration only exists for projects that use a database\n    // add your config settings here…\n}\n```\n\nThe values for the server and database configuration are read from the `APP_SERVER__IP`, `APP_SERVER__PORT`, and `APP_DATABASE__URL` environment variables. Any application-specific settings are read from `app.toml` as well as environment-specific file, e.g. `production.toml` such that settings in the environment-specific files override those in `app.toml`.\n\nThe main files and folders in the crate are:\n\n```\nconfig\n├── environments\n|   ├── development.toml // Configuration settings specific for the development environment\n|   ├── production.toml  // Configuration settings specific for the production environment\n|   └── test.toml        // Configuration settings specific for the test environment\n├── src\n|   └── lib.rs           // Contains the `Config` struct and code for constructing it based on the configuration files and environment variables\n└── app.toml             // Basis configuration settings that will be overridden by the same settings in the respective environment-specific configuration file\n```\n\n### The `cli` crate\n\nThe `cli` crate contains the `db` binary for running database operations such as executing migrations (this binary only exists for projects that use a database) as well as the `generate` binary for generating project files such as entities, controllers, tests, or middlewares. The workspace is configured so that those binaries can be executed with just `cargo db` and `cargo generate`:\n\n```\n» cargo db\nA CLI tool to manage the project's database.\n\nUsage: db [OPTIONS] \u003cCOMMAND\u003e\n\nCommands:\n  drop     Drop the database\n  create   Create the database\n  migrate  Migrate the database\n  reset    Reset (drop, create, migrate) the database\n  seed     Seed the database\n  prepare  Generate query metadata to support offline compile-time verification\n  help     Print this message or the help of the given subcommand(s)\n\nOptions:\n  -e, --env \u003cENV\u003e  Choose the environment (development, test, production). [default: development]\n      --no-color   Disable colored output.\n      --debug      Enable debug output.\n  -h, --help       Print help\n  -V, --version    Print version\n```\n\n```\n» cargo generate\nA CLI tool to generate project files.\n\nUsage: generate [OPTIONS] \u003cCOMMAND\u003e\n\nCommands:\n  middleware            Generate a middleware\n  controller            Generate a controller\n  controller-test       Generate a test for a controller\n  migration             Generate a migration\n  entity                Generate an entity\n  entity-test-helper    Generate an entity test helper\n  crud-controller       Generate an example CRUD controller\n  crud-controller-test  Generate a test for a CRUD controller\n  help                  Print this message or the help of the given subcommand(s)\n\nOptions:\n      --no-color  Disable colored output.\n      --debug     Enable debug output.\n  -h, --help      Print help\n  -V, --version   Print version\n```\n\nYou would typically not have to make any changes to the `cli` crate.\n\n### The `macros` crate\n\nThe `macros` crate contains the implementation of the `db_test` macro. You would typically not have to make any changes to the `cli` crate.\n\n## Testing \u0026 CI\n\nProjects generated by Gerust come with a complete CI setup for GitHub Actions that includes:\n\n* checking the format of all Rust source files\n* running Clippy on the entire project\n* running all tests in all crates\n\n## Gerust development\n\nWhen making changes to Gerust itself and you'd like to manually test out your changes, you can generate a sandbox app with:\n\n```\ncargo run -- --full my-app\n```\n\nThen edit with your favorite editor:\n\n```\n${EDITOR} my-app\n```\n\nAnd:\n\n- Make changes to generated code and test it.\n- Backport changes to gerust\n- Generate new app and validate changes\n\n```\ncargo run -- -f my-new-app\n```\n\nRinse:\n\n```\nrm -rf my-app my-new-app\n```\n\n_Note: the generated CI configuration uses offline query validation during its Clippy job. On first check-in, and each time the SQL queries in the db crate get updated, ensure `cargo db prepare` is run._\n\n## What's a \"Gerust\"?\n\n\"Gerust\" is a play on \"Gerüst\", the German word for \"framework\" and Rust – thanks to [@skade](https://github.com/skade) who had the idea originally and allowed us to use it!\n\n## License\n\nGerust is developed by and © Mainmatter GmbH and contributors. It is released under the [MIT License](./LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmainmatter%2Fgerust","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmainmatter%2Fgerust","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmainmatter%2Fgerust/lists"}