๐ A production-ready, batteries-included Rust template for building full-stack applications fast.
Cornerstone: A Production-Ready, Full-Stack Rust Template
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.
๐ Table of Contents
- โจ Key Features
- ๐ช Making It Your Own
- ๐ Getting Started
- โ๏ธ Configuration
- ๐ ๏ธ Development Workflow
- ๐ณ Deployment with Docker
- ๐ Continuous Integration
- ๐๏ธ Project Structure
- โ๏ธ License
โจ Key Features
- 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:
- 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 Dockerfile and docker-compose for optimized, production-ready containers.
* GitHub Actions: CI pipeline that tests against both PostgreSQL and SQLite.
* pre-commit: Git hooks for automatic formatting and linting.
๐๏ธ Architecture Overview
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
๐ช Making It Your Own
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.
Part 1: Choosing Your Frontend
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.
Option A: I want to use SvelteKit (and remove Slint)
This is the most common path for web applications.
- Delete the Slint Crate:
frontend_slint/ directory.
- Update Workspace Configuration:
Cargo.toml, remove frontend_slint from the [workspace].members array.
# Cargo.toml
[workspace]
resolver = "2"
members = [
"backend",
- "frontend_slint",
"common",
]
- Clean Up Backend Features:
backend/Cargo.toml, you can remove the slint-ui feature 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:
backend/src/webserver.rs, the createstatic_router function has conditional compilation. You can remove the #[cfg(feature = "slint-ui")] block and the surrounding logic.
- Clean the
justfile:
build-slint.
* Simplify the copy-frontend and run-web commands by removing the slint conditions.
Option B: I want to use Slint (and remove SvelteKit)
This is the path for a desktop-focused application.
- Delete SvelteKit Project:
frontend_svelte/ directory.
- Clean Up Backend Features:
backend/Cargo.toml, remove the svelte-ui feature.
# 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:
slint-ui part and remove the svelte-ui part in backend/src/web_server.rs.
- Remove Type Generation:
common/src/bin/type_exporter.rs.
* In common/Cargo.toml, remove the type-exporter binary, the ts-rs and dprint-plugin-typescript dependencies, and the ts_export feature.
* In the justfile, remove the gen-types command.
- Clean the
justfile:
build-svelte, run-web svelte, run-web svelte-live.
* Simplify the copy-frontend and run-web commands.
- Clean the
Dockerfileand CI:
npm related steps from the Dockerfile and the CI workflow in .github/workflows/ci.yml.
Part 2: Choosing Your Database
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:
backend/Cargo.toml, remove the db-sqlite feature and update the default list.
# 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:
common/Cargo.toml, remove the db-sqlite feature.
- Simplify Database Code:
backend/src/db.rs contains conditional logic. You can reduce it to only the use statement for your chosen database.
* Simplify backend/build.rs and backend/src/main.rs by removing the #[cfg] blocks for the database you are not using.
- Delete Unused Migrations:
backend/migrations/sqlite/).
- Clean the
justfile:
db-migrate-sqlite, test-backend-sqlite, db-reset-sqlite).
- Clean the CI Workflow:
Part 3: Simplifying the CI Workflow
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.
Option A: I want to use PostgreSQL (and remove SQLite)
- Edit the CI Workflow:
.github/workflows/ci.yml, find the matrix within the test job.
* Remove the entire list item for SQLite.
# .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:
"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 }} # ...
Option B: I want to use SQLite (and remove PostgreSQL)
- Edit the CI Workflow:
.github/workflows/ci.yml, find the matrix within the test job and remove the list item for PostgreSQL.
* Since PostgreSQL is no longer used, you must also remove the entire services block 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 # ...
๐ Getting Started
Prerequisites
- 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.
sqlx-cli with 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.
Installation & Setup
- Clone the repository:
git clone https://github.com/gramistella/cornerstone.git
cd cornerstone
- Configure Environment:
.env file. This file is ignored by git and is used for local secrets.
cp .env.example .env
Open .env and set a strong APPJWTSECRET. Ensure the DATABASEURL points to your chosen database.
- Install Frontend Dependencies (for SvelteKit):
cd frontend_svelte
npm install
cd ..
- Setup the Database:
just db-migrate-sqlite
(Use just db-migrate-postgres if you've switched to PostgreSQL).
- (Optional) Install Git Hooks:
pre-commit install
โ๏ธ Configuration
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
APPwill override values from bothConfig.tomland.env. The double underscoreis used to denote nesting (e.g.,APPWEBPORT=8888overrides theportkey within the[web]table).
.env file > Config.toml.
Key Configuration Variables
The application will not start without a validAPPJWT_SECRETset in your environment. This is a critical security measure. Ensure you set it in your.envfile after copying it from.env.example.
APPJWT_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).DATABASEURLSQLITE: A separate variable for the SQLite connection string, used byjustcommands.
๐ ๏ธ Development Workflow
API Documentation
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:
#[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.
Web Development (SvelteKit with Hot-Reloading)
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-live
Web Development (Production Simulation)
To 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 svelte
Desktop Development (Slint)
To build and run the native Slint desktop application:
just run-web slint
Other Useful Commands
-
just 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.
๐ณ Deployment with Docker
A multi-stage Dockerfile is provided to build a minimal, optimized production image. It's designed to be flexible and efficient.
How it Works
- 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.
Building and Running
- Build the image:
.env file 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.
๐ Continuous Integration
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:
postgres:15 service is spun up within the job to run tests against a real database instance.
* SQLite: Tests are run against a file-based SQLite database.
- 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.
๐๏ธ Project Structure
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
โ๏ธ License
This project is licensed under the MIT License. See the LICENSE file for details.