This project is under active development.
Recently migrated from Prisma ORM to SQL first approach with Cornucopia.
Also added rich text editing with leptoslate, a Slate-compatible editor written in Rust for Leptos, so the whole stack stays Rust and the stored Slate JSON keeps working.\
Intro: https://youtu.be/6eMWAI1D-XA
Demo site: https://lapa.chepura.space
Screenshots below are outdated and to be updated.
I want to have practical full-stack setup to build websites and services.
Utilising type safety and performance of Rust opens the door for new era of web dev, that is taking off.
Ecosystem and standardized approach is helpful to develop scalable and future-proof apps.
Some benefits:
- strict types
- enforced error and value management (Result and Option)
- predictable performance (no garbage collector)
- native performance
- single bundler (cargo)
- straight path to WebAssembly
https://leptos.dev
leptos-rs/leptos
A cutting-edge, high-performance frontend framework SSR+SPA. Using reactive signals.
tokio-rs/axum
Backend framework built with Tokio, Tower, and Hyper. Focuses on ergonomics and modularity.
- project
- SEO site
- admin dashboard
- CLI with clap: migrate, settings-init, user-add, user-disable, user-enable, user-set-role
- ops scripts: build/deploy, systemd units, ferron config, migrations, backups
- site
- SSR + SPA hydrated
- open graph meta tags
- prod features
- ratelimit with benwis/tower-governor
- compression with tower-http/compression
- precompression with ryanfowler/precompress see ./ops scripts
- admin auth and session with
- axum_session AscendingCreations/AxumSessions
- axum_session_auth AscendingCreations/AxumSessionsAuth
- rich text content editor integration with alexichepura/leptoslate
- post
- CRUDL
- publish_at
- product
- CRUDL
- publish_at
- product images
- preview and upload
- resize and convert on backend
- order
- is_hero flag
- delete and alt update in "dialog"
- settings
- robots.txt, site_url
- home_text
- css
- based on https://open-props.style
- dark and light themes
- mobile first
- CSS modules with stylance, bundled by
Lightning CSS (
style-bundle) in cargo-leptos, see notes on css below
- components
- forms, inputs and response messages
- input datetime-local usage with chrono library
- RoutingProgress
- Favicons
https://cornucopia-rs.github.io/cornucopia/introduction/installation.html
Note the need of docker or podman.
cargo install cornucopia
cornucopia schema ./schema.sqlThe editor is a Rust dependency (leptoslate), so there is no separate
JavaScript build step.
Simplified with postgres user.
psql postgres
# create database lapa;
# .env
PG__URL="postgresql://postgres:postgres@localhost:5432/lapa"Runtime environment variables (all optional):
PG__URL— Postgres connection string (see above).PG_TLS— set to1/trueto connect to Postgres over TLS (rustls with Mozilla roots). Leave unset for a local socket or trusted-network plaintext connection.SESSION_SECURE— force the admin session cookieSecureflag (1/0). Defaults to on for release builds, off for debug.AUTH_RATELIMIT_BURST/AUTH_RATELIMIT_PERIOD_SECS— admin/auth/*rate limit (defaults: burst 10, then one token per second per client).IMAGE_UPLOAD_PATH/IMAGE_CONVERT_PATH— image storage roots.
The schema is managed with refinery migrations in
db/migrations/, embedded in the cli binary. db/migrations/V1__init.sql
mirrors schema.sql (kept for Cornucopia codegen, see above).
cargo cli migrateAdd a migration by creating the next file (e.g. db/migrations/V2__add_thing.sql)
and running cargo cli migrate again.
RUST_LOG="debug" cargo cli settings-init
RUST_LOG="debug" cargo cli user-add <username> <password>Users carry a nullable deactivated_at timestamp (added in migration
V2__user_deactivated_at.sql): NULL means active, a timestamp records when the
account was disabled. A disabled account cannot log in, and any session it
already holds is rejected on the next request (the axum-login backend returns
no user for a disabled account), so disabling takes effect immediately without
deleting the account.
cargo cli user-disable <username> # revoke access (also purges their sessions)
cargo cli user-enable <username> # restore accessBoth commands exit non-zero when no such user exists.
Users also have a single role (migration V4__user_role.sql):
| role | can |
|---|---|
admin |
everything |
editor |
product/post/category/content CRUD, but not site settings |
viewer |
read-only (dashboard and lists) |
The same permissions are enforced by the server functions; the admin UI hides nav/buttons and renders a "Forbidden" page for gated routes. Set a role with:
cargo cli user-set-role <username> admin|editor|viewercargo install stylance-cli # one-time
stylance --watch . # site CSS modules (run in a second terminal)
stylance --watch admin # admin CSS modules (run in a second terminal)
RUST_LOG="info,admin=debug" cargo leptos watch -p admin
RUST_LOG="info,site=debug" cargo leptos watch -p siteTip to enable tower_http debug
RUST_LOG="info,tower_http=debug,admin=debug" cargo leptos watch -p admin
RUST_LOG="info,tower_http=debug,site=debug" cargo leptos watch -p siteSee the tutorial/demo video. https://www.youtube.com/watch?v=KLg8Hcd3K_U
cargo leptos build --releaseProduction with compress and ratelimit
cargo leptos build --release --features="compression,ratelimit"
# or
cargo leptos build --release --features="prod"The ops/ folder contains example scripts to build, deploy and operate the
server. Copy .env.example to .env and adjust the host, paths and domains.
Requires https://github.com/ryanfowler/precompress, stylance and
cargo-leptos.
Server layout assumed by the scripts and systemd units:
/home/lapa-server—SERVER_DIR, holds.env/home/lapa-server/{web,admin}/public—LEPTOS_SITE_ROOT/home/lapa-server/image_upload,/home/lapa-server/image_convert/usr/bin/lapa_web,/usr/bin/lapa_admin— binaries- services
lapa-web(8110) andlapa-admin(8111), fronted by ferron 3
One-time setup: Postgres running, /home/lapa-server/.env with PG__URL (read
by dotenvy; the units set WorkingDirectory), service users with access to
SERVER_DIR and write access to the image dirs, then
./ops/services-deploy.sh and ./ops/ferron-deploy.sh.
./ops/build-upload.sh web # build + upload site, restart lapa-web
./ops/build-upload.sh admin # build + upload admin, restart lapa-admin
./ops/build-upload.sh cli # upload the CLI binary
./ops/cli-migrate.sh # run migrations on the server
./ops/restart.sh web|admin
./ops/services-deploy.sh # install/restart the systemd units
./ops/ferron-deploy.sh # validate + install ferron.conf and reloadBackups (Postgres + image_upload/, 30-day retention by default):
./ops/backup.sh # DB + originals into ./backups
./ops/backup.sh --with-derived # also image_convert/
./ops/backup-upload.sh backups/lapa-<timestamp>.dump # restore (destructive)Rust-native end-to-end harness (playwright-rs + testcontainers) that boots
an ephemeral Postgres, applies schema.sql, seeds fixtures, starts the site and
drives Chromium. See e2e/README.md.
cargo install cargo-leptos
cargo run -p lapa-e2e -- install # download Chromium (one-time)
cargo run -p lapa-e2e -- test # public site suite
cargo run -p lapa-e2e -- test --app admin # admin suite (auth + CRUD)
cargo run -p lapa-e2e -- test --app integration # cross-app: admin write -> site read
cargo run -p lapa-e2e -- test --filter home
cargo run -p lapa-e2e -- update # regenerate screenshot baselines
cargo run -p lapa-e2e -- serve # keep the stack up for manual checks
cargo run -p lapa-e2e -- test --features prod # build with compression + ratelimitThe suites also run in CI (.github/workflows/e2e.yml); CI sets
LAPA_E2E_SKIP_SNAPSHOTS=1 since only macOS baselines are committed.
Styling is plain CSS organised as CSS modules with
stylance plus Lightning CSS for
bundling (cargo-leptos style-bundle = true).
- Component-scoped rules live next to the components as
*.module.css(e.g.src/product_list.module.css). Add thestylance::import_crate_style!(style, "src/product_list.module.css");import and referencestyle::ProductList; the macro resolves the same deterministic hash the CLI writes into the bundle. - Shared/global element rules (e.g.
body,dialog,@container) live instyle/main.css, which also@imports the vendor stylesheets (open-props,normalize,buttons) and the generatedstyle/stylance.css. - Regenerate the bundle with
stylance .(site) /stylance admin(admin), or keepstylance --watch <crate>running while editing. The generatedstyle/stylance.cssis committed so a plaincargo leptos buildworks without the CLI;ops/build-upload.shand the e2e harness refresh it for you.
Native CSS nesting, @media, @container and @layer are all supported. The
browserquery in both crates targets browsers with native nesting support
(Chrome/Edge 120+, Firefox 117+, Safari/iOS 17.2+, Opera 106+, Samsung 25+), so
Lightning CSS keeps the nested source in the bundle instead of flattening it.
Adding an older target there re-enables downleveling for it.
@custom-media is not available: cargo-leptos always parses with
ParserOptions::default() and gives no way to set Lightning CSS'
ParserFlags::CUSTOM_MEDIA.
Earlier experiments: Sass PR #24, Lightning CSS bundle with CLI proof of concept #23.
This project is licensed under the terms of the MIT license.