Stick
Stick is a Rust web application starter template built on Axum, Askama, and MongoDB. Unlike traditional MVC architectures that organize code by technical layers (controllers, models, views), Stick is organized by vertical slices (features or use-cases). All files related to a specific domain feature—such as authentication or task management—live together in a single module.
This setup is ideal for medium-to-large projects where horizontal layers become hard to navigate, and compiling templates at runtime is too risky.
Technical Architecture
1. Vertical Slice Layout
Each feature slice is self-contained. For example, the authentication domain has the following structure:
src/auth/models.rs: BSON data structures and claims.src/auth/repository.rs: Database operations and query logic.src/auth/handlers.rs: Request/response lifecycle logic.src/auth/extractors.rs: Axum extractors for session state.templates/auth/: HTML markup templates rendered at compile-time.
2. Key Stack Decisions
- Axum (v0.8): Handles routing, middleware, and request extraction.
- Askama (v0.16): Evaluates and compiles HTML templates into Rust code at compile time. If you reference a variable or field that doesn't exist, the project fails to compile, catching UI rendering bugs before deployment.
- MongoDB: Standard Rust driver configured with BSON serialization.
- Tailwind CSS: Pre-compiled utility styling using a Node-based wrapper process.
- Authentication: Managed via JWT (JSON Web Tokens) stored in secure, encrypted
HttpOnlycookies.
Core Features Included
Self-Provisioning Administrator
On startup, the application checks if the users collection in the MongoDB database is empty. If no users are found, it generates a secure, random 16-character alphanumeric password, hashes it with bcrypt, and creates a default admin account. The credentials are logged directly to standard output:
======================================================
CREATED INITIAL ADMINISTRATOR ACCOUNT:
Username: admin
Password: [GeneratedPassword]
======================================================
Decoupled Identity vs. Domain Entities
Stick strictly separates infrastructure/user models from business domain models:
- Users (
User): Manage authentication, roles (is_admin), and settings under/auth. - Developers (
Developer): Plain domain entities managed under/developersthat represent team members.
User Management Panel (Administrators Only)
Accessible under /auth/users by logged-in administrators. The panel allows:
- Viewing all registered users and their administrative permissions.
- Editing user profiles, resetting passwords, and toggling administrator roles.
- Deleting users (with safeguards preventing administrators from deleting their own active accounts or revoking their own admin permissions).
- Registering new users.
Self-Service Account Settings
Any authenticated user can change their own password by clicking their username in the navigation bar, which directs them to /auth/password.
Setup and Running
Prerequisites
- Rust: Toolchain v1.75+ (for native async traits).
- Node.js & npm: Required to build Tailwind CSS.
- MongoDB: Running locally on
mongodb://localhost:27017.
Local Setup
- Copy the environment configuration:
cp .env.example .env - Build Tailwind CSS styling:
npm install npx tailwindcss -i src/input.css -o static/tailwind.css - Run the development server:
The server will start listening at
cargo runhttp://127.0.0.1:3000.
Running with Docker
A multi-stage Dockerfile is provided to compile Tailwind, compile the Rust binary, and bundle a lightweight Debian run container.
- Build the image:
docker build -t stick . - Start the container (assumes MongoDB is running on the host machine):
docker run --name stick-app --rm --network="host" \ -e DATABASE_URL="mongodb://127.0.0.1:27017" \ -e DATABASE_NAME="stick_db" \ -e JWT_SECRET="super_secret_template_signing_key_that_is_at_least_32_characters_long" \ -e HOST="127.0.0.1" \ -e PORT="3009" \ stick
Developer Guide: Adding a Feature Slice
To add a new feature (e.g. projects):
- Create a module folder:
src/projects/. - Define models in
models.rsand database access functions inrepository.rs. - Add request handlers in
handlers.rs. - Create a router configuration in
src/projects/mod.rsexposing a routing module setup:pub fn router<S>() -> Router<S> where Config: axum::extract::FromRef<S>, MongoUserRepository: axum::extract::FromRef<S>, S: Clone + Send + Sync + 'static, { Router::new() .route("/projects", get(handlers::get_projects)) } - Place HTML layouts under
templates/projects/extending thebase.htmllayout. - Register and merge the router in
src/main.rs:let app = Router::new() .merge(main_view::router()) .merge(auth::router()) .merge(projects::router()) // Merged domain router .with_state(state);