Cornerstone is a batteries-included, production-ready template for full-stack Rust applications. It provides a robust, modern, and memory-safe stack, allowing you to skip the boilerplate and focus immediately on writing business logic.
The core philosophy is to provide a solid foundation with sane defaults for a complete application, including a flexible backend, a choice of frontends, database interaction, authentication, and deployment tooling.
- β¨ Key Features
- πͺ Making It Your Own
- π Getting Started
- βοΈ Configuration
- π οΈ Development Workflow
- π³ Deployment with Docker
- π Continuous Integration
- ποΈ Project Structure
- βοΈ License
- Robust Backend: Built on
axumfor ergonomic and modular web services, withsqlxfor compile-time checked SQL queries. - Flexible Database: Out-of-the-box support for PostgreSQL and SQLite, selectable via feature flags.
- Dual Frontend Options:
- SvelteKit (Web): A modern, fast web framework for rich user interfaces, with type-safe API generation from your Rust code.
- Slint (Desktop/WASM): A declarative UI toolkit for building native desktop applications in the same Rust ecosystem.
- Secure Authentication: A complete JWT-based authentication system with an access and refresh token rotation strategy.
- Automatic API Documentation: Generated OpenAPI (Swagger) documentation via
utoipafor easy API testing and exploration. - API Rate Limiting: Protects your application from abuse with configurable, per-IP rate limiting using
tower_governor. - Developer-First Tooling:
just: A command runner for streamlined project tasks (build, test, run).- Docker: Multi-stage
Dockerfileanddocker-composefor optimized, production-ready containers. - GitHub Actions: CI pipeline that tests against both PostgreSQL and SQLite.
pre-commit: Git hooks for automatic formatting and linting.
Here is a high-level overview of the project's components and how they interact:
graph TD
User[π€ User/Developer]
subgraph Frontend["π₯οΈ Frontend Options"]
Svelte[SvelteKit Web App]
Slint[Slint Desktop/WASM]
end
subgraph Backend["βοΈ Backend (Rust)"]
Axum[Axum Web Server]
SQLx[SQLx Query Engine]
end
subgraph Database["πΎ Database Options"]
Postgres[PostgreSQL]
SQLite[SQLite]
end
User --> Svelte
User --> Slint
Svelte <--> Axum
Slint <--> Axum
Axum --> SQLx
SQLx --> Postgres
SQLx --> SQLite
classDef frontend fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef backend fill:#fff3e0,stroke:#f57c00,stroke-width:2px
classDef database fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef user fill:#e8f5e8,stroke:#388e3c,stroke-width:2px
class Svelte,Slint frontend
class Axum,SQLx backend
class Postgres,SQLite database
class User user
A template's primary purpose is to be changed. Once you've chosen your stack, it's highly recommended to remove the unused code to simplify your project.
Click here for a step-by-step guide to tailoring the template.
Decide whether you will use SvelteKit for a web application or Slint for a desktop/WASM application, and then follow the steps to remove the other.
This is the most common path for web applications.
-
Delete the Slint Crate:
- Delete the entire
frontend_slint/directory.
- Delete the entire
-
Update Workspace Configuration:
-
In the root
Cargo.toml, removefrontend_slintfrom the[workspace].membersarray.# Cargo.toml [workspace] resolver = "2" members = [ "backend", - "frontend_slint", "common", ]
-
-
Clean Up Backend Features:
-
In
backend/Cargo.toml, you can remove theslint-uifeature entirely.# backend/Cargo.toml [features] - default = ["svelte-ui", "db-sqlite"] + default = ["svelte-ui", "db-sqlite"] # Ensure this is correct for your DB svelte-ui = [] - slint-ui = [] # ...
-
-
Simplify the Backend Web Server:
- In
backend/src/web_server.rs, thecreate_static_routerfunction has conditional compilation. You can remove the#[cfg(feature = "slint-ui")]block and the surrounding logic.
- In
-
Clean the
justfile:- Remove Slint-specific commands like
build-slint. - Simplify the
copy-frontendandrun-webcommands by removing theslintconditions.
- Remove Slint-specific commands like
This is the path for a desktop-focused application.
-
Delete SvelteKit Project:
- Delete the entire
frontend_svelte/directory.
- Delete the entire
-
Clean Up Backend Features:
-
In
backend/Cargo.toml, remove thesvelte-uifeature.# backend/Cargo.toml [features] - default = ["svelte-ui", "db-sqlite"] + default = ["slint-ui", "db-sqlite"] # Ensure this is correct for your DB - svelte-ui = [] slint-ui = [] # ...
-
-
Simplify the Backend Web Server:
- Follow the same logic as in Option A, but keep the
slint-uipart and remove thesvelte-uipart inbackend/src/web_server.rs.
- Follow the same logic as in Option A, but keep the
-
Remove Type Generation:
- The TypeScript type generation is only for SvelteKit.
- Delete
common/src/bin/type_exporter.rs. - In
common/Cargo.toml, remove thetype-exporterbinary, thets-rsanddprint-plugin-typescriptdependencies, and thets_exportfeature. - In the
justfile, remove thegen-typescommand.
-
Clean the
justfile:- Remove Svelte-specific commands:
build-svelte,run-web svelte,run-web svelte-live. - Simplify the
copy-frontendandrun-webcommands.
- Remove Svelte-specific commands:
-
Clean the
Dockerfileand CI:- Remove all
npmrelated steps from theDockerfileand the CI workflow in.github/workflows/ci.yml.
- Remove all
The process is the same whether you keep PostgreSQL or SQLite. The following example assumes you are keeping PostgreSQL and removing SQLite.
-
Update Backend Features:
-
In
backend/Cargo.toml, remove thedb-sqlitefeature and update thedefaultlist.# backend/Cargo.toml [features] - default = ["svelte-ui", "db-sqlite"] + default = ["svelte-ui", "db-postgres"] # ... - db-sqlite = ["sqlx/sqlite", "common/db-sqlite"] db-postgres = ["sqlx/postgres", "common/db-postgres"]
-
-
Update Common Crate Features:
- In
common/Cargo.toml, remove thedb-sqlitefeature.
- In
-
Simplify Database Code:
- The file
backend/src/db.rscontains conditional logic. You can reduce it to only theusestatement for your chosen database. - Simplify
backend/build.rsandbackend/src/main.rsby removing the#[cfg]blocks for the database you are not using.
- The file
-
Delete Unused Migrations:
- Delete the directory for the database you are not using (e.g.,
backend/migrations/sqlite/).
- Delete the directory for the database you are not using (e.g.,
-
Clean the
justfile:- Remove all commands related to the unused database (e.g.,
db-migrate-sqlite,test-backend-sqlite,db-reset-sqlite).
- Remove all commands related to the unused database (e.g.,
-
Clean the CI Workflow:
- The CI pipeline runs tests against both databases. After removing one, you should update the workflow to only test against your chosen database. See Part 3 below for a detailed guide.
The CI pipeline in the template is configured to test against both PostgreSQL and SQLite using a matrix strategy. Once you've chosen a database, you can simplify the workflow by removing the unused database from the matrix. This is a small change that makes your CI pipeline faster.
-
Edit the CI Workflow:
-
In
.github/workflows/ci.yml, find thematrixwithin thetestjob. -
Remove the entire list item for
SQLite.```diff # .github/workflows/ci.yml # ... test: name: Test (${{ matrix.db.name }}) runs-on: ubuntu-latest needs: check-format strategy: fail-fast: false matrix: db: - - name: SQLite - type: sqlite - sqlx_features: native-tls,sqlite - url: "sqlite:test_ci.db" - name: PostgreSQL type: postgres sqlx_features: native-tls,postgres url: "postgres://postgres:password@localhost:5432/testdb" # ... ```
-
-
(Optional) Clean Up Steps:
-
You can now safely delete the step named
"Create SQLite Database File", since it is no longer needed.# .github/workflows/ci.yml # ... - name: Cache Cargo dependencies # ... - - name: Create SQLite Database File - if: matrix.db.type == 'sqlite' - run: touch test_ci.db - name: Run Database Migrations run: just db-migrate-${{ matrix.db.type }} # ...
-
- Edit the CI Workflow:
-
In
.github/workflows/ci.yml, find thematrixwithin thetestjob and remove the list item forPostgreSQL. -
Since PostgreSQL is no longer used, you must also remove the entire
servicesblock that defines the postgres container.# .github/workflows/ci.yml # ... test: name: Test (${{ matrix.db.name }}) runs-on: ubuntu-latest needs: check-format strategy: fail-fast: false matrix: db: - name: SQLite type: sqlite sqlx_features: native-tls,sqlite url: "sqlite:test_ci.db" - - name: PostgreSQL - type: postgres - sqlx_features: native-tls,postgres - url: "postgres://postgres:password@localhost:5432/testdb" # Since the PostgreSQL matrix entry is gone, this service is no longer needed. - services: - postgres: - image: postgres:15 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: password - POSTGRES_DB: testdb - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 env: # Set the DATABASE_URL for sqlx-cli and the application tests # ...
-
- Rust Toolchain: Install via rustup.
just: A command runner. Install withcargo install just.sqlx-cli: For database migrations. Install withcargo install sqlx-cli --no-default-features --features native-tls,sqlite,postgres.Note: This command installs
sqlx-cliwith support for both database types, which is useful when you first start. After you've chosen a database and removed the other, you can use a simpler command (e.g.,cargo install sqlx-cli --no-default-features --features native-tls,postgres).- Node.js & npm: Required for the SvelteKit frontend.
- Docker & Docker Compose: (Optional) For running the application in a container.
pre-commit: (Optional) For automatic git hooks. Install from pre-commit.com.
-
Clone the repository:
git clone https://github.com/gramistella/cornerstone.git cd cornerstone -
Configure Environment: Copy the example
.envfile. This file is ignored by git and is used for local secrets.cp .env.example .env
Open
.envand set a strongAPP_JWT__SECRET. Ensure theDATABASE_URLpoints to your chosen database. -
Install Frontend Dependencies (for SvelteKit):
cd frontend_svelte npm install cd ..
-
Setup the Database: The project defaults to SQLite. To run the initial migrations for it:
just db-migrate-sqlite
(Use
just db-migrate-postgresif you've switched to PostgreSQL). -
(Optional) Install Git Hooks: This will run formatting and linting checks before each commit.
pre-commit install
Application configuration is handled through a combination of a configuration file and environment variables, powered by the figment crate.
Config.toml: This file contains non-sensitive, default configuration values. You can commit this file to version control..env: This file is for local development secrets and environment-specific overrides. It is ignored by git and should never be committed.- Environment Variables: Any environment variable prefixed with
APP_will override values from bothConfig.tomland.env. The double underscore__is used to denote nesting (e.g.,APP_WEB__PORT=8888overrides theportkey within the[web]table).
The hierarchy of overrides is: Environment Variables > .env file > Config.toml.
The application will not start without a valid
APP_JWT__SECRETset in your environment. This is a critical security measure. Ensure you set it in your.envfile after copying it from.env.example.
APP_JWT__SECRET: (Required) A long, random string used to sign JWTs. This must be set in your.envfile or as an environment variable for production.DATABASE_URL: The connection string for your primary database. This is used bysqlx-clifor migrations and by the application at runtime. For Docker builds, this value is passed in during the build process (seedocker-compose.yml).DATABASE_URL_SQLITE: A separate variable for the SQLite connection string, used byjustcommands.
This template uses utoipa to automatically generate an OpenAPI (Swagger) specification for the backend API. When running in debug mode, you can access the interactive Swagger UI at:
The documentation is generated directly from the Rust code via the #[utoipa::path] macros on your API handlers (e.g., in backend/src/web_server.rs and backend/src/auth.rs). When you change an endpoint, remember to update its corresponding macro to keep the documentation in sync.
This project uses just as a command runner for common tasks.
This is the recommended way to develop the web application. It runs the backend server and the SvelteKit dev server concurrently.
# Frontend (with HMR): http://localhost:5173
# Backend API: http://localhost:8080
just run-web svelte-liveTo build the static SvelteKit app and have the Rust server serve it, simulating a production environment:
# Access the full app at http://localhost:8080
just run-web svelteTo build and run the native Slint desktop application:
just run-web slintjust test: Run the entire Rust test suite.just lint: Check the workspace for warnings and errors with Clippy.just gen-types: Important! Regenerate TypeScript types infrontend_svelteafter changing shared Rust structs in thecommoncrate.just db-prepare: Highly Recommended! Checks all SQL queries in thebackendagainst a running database to ensure they are valid at compile time.just db-reset-sqlite: Delete and recreate the local SQLite database.
A multi-stage Dockerfile is provided to build a minimal, optimized production image. It's designed to be flexible and efficient.
- Builder Stage: This stage sets up the complete build environment, installing Rust, Node.js,
sqlx-cli, and other dependencies. - Optimized Caching: It caches
npmandcargodependencies in separate layers to dramatically speed up subsequent builds. - Dynamic Build: The Dockerfile dynamically determines which frontend and database to use by inspecting the
defaultfeatures inbackend/Cargo.toml. This means you don't have to edit theDockerfileafter removing an unused frontend or database! - Runtime Stage: This final stage creates a tiny production image by copying only the compiled server binary and necessary static assets from the builder stage.
-
Build the image: Ensure your
.envfile is configured, as its variables are used during the build.docker-compose build
-
Run the container:
docker-compose up
The service will be available at http://localhost:8080.
The project includes a comprehensive CI pipeline using GitHub Actions, defined in .github/workflows/ci.yml.
On every push or pull request to the main branch, the CI pipeline automatically performs the following checks:
- Formatting: Ensures all Rust code adheres to the standard
rustfmtstyle (cargo fmt --check). - Matrix Testing: It runs two parallel test jobs to validate the application against both supported databases:
- PostgreSQL: A
postgres:15service is spun up within the job to run tests against a real database instance. - SQLite: Tests are run against a file-based SQLite database.
- PostgreSQL: A
- Linting: Runs
cargo clippyto catch common mistakes and improve code quality for each database configuration. - Backend Tests: Executes the full integration test suite (
cargo test -p backend). - Release Build: Compiles the entire workspace in release mode to ensure it builds successfully for production.
This setup guarantees that your application remains robust and compatible with its supported database configurations. Once you have chosen a specific database for your project, you can simplify this workflow. See the "Making It Your Own" section for a guide.
The project is a Cargo workspace with a clean separation of concerns.
cornerstone/
βββ .github/ # GitHub Actions CI workflows
βββ backend/ # The Rust Axum web server
β βββ migrations/ # SQLx database migrations
β βββ src/ # Backend source code
β βββ static/ # Where the built frontend is served from
βββ common/ # Shared Rust code (DTOs, utils)
βββ frontend_slint/ # The Slint desktop frontend crate
βββ frontend_svelte/ # The SvelteKit web frontend project
β βββ src/lib/types.ts # Auto-generated types from Rust!
βββ .env # Local environment variables (ignored by git)
βββ Config.toml # Default application configuration
βββ justfile # Command runner recipes
βββ Dockerfile # For building a production container image
This project is licensed under the MIT License. See the LICENSE file for details.