Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ferriscms

A 1:1, offline-first clone of Strapi built entirely in Rust.

ferriscms reproduces Strapi's core headless-CMS workflow with a single Rust codebase that runs in two modes:

  • Offline desktop β€” an embedded SQLite database, no server, no config.
  • Online server β€” an Axum server (SQLite by default, PostgreSQL when DATABASE_URL says so) serving a Strapi-compatible REST API plus the Dioxus admin UI.
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     shared library crates                    β”‚
β”‚   core-domain, core-schema, db, dynamic-store, services,     β”‚
β”‚   api-types, client-core                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ in-process calls              β”‚ HTTP
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  desktop-bin (OFFLINE)β”‚        β”‚   server-bin (ONLINE)   β”‚
   β”‚  Dioxus UI ─ client-core     β”‚   Axum ─ services        β”‚
   β”‚  services + SQLite (embedded)β”‚   Postgres / SQLite       β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                β”‚ HTTP
                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                    β”‚  Web admin (Dioxus WASM) β”‚
                                    β”‚  client-core (HTTP)      β”‚
                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Features

The project implements the "core" of Strapi (no code generation, plugin system, or GraphQL yet):

  • Content-Type Builder β€” visually define collection types, single types, components, and dynamic zones at runtime. Field types include text, rich text (Markdown/Blocks), number, date, boolean, email, password, enum, JSON, UID, media, relation, component, and dynamic zone. All six relation kinds, New / Modified / Deleted staging badges, undo/redo/discard.
  • Content Manager β€” schema-driven entry forms, list/edit views, filters, sorting, pagination, Draft & Publish, and discard-draft controls.
  • Saved Content Manager views β€” multiple Grid, Kanban, Gallery, and Calendar presentations per content type, with persisted filters, sorts, columns, defaults, duplication, ordering, and server-side record queries.
  • Dynamic schema β†’ real tables β€” schemas are stored as JSON and applied to the database at runtime (SeaQuery-generated DDL). No manual SQL.
  • Strapi-compatible REST API β€” filters, populate, sort, pagination, fields, locale, and status query params, with Strapi's { data, meta } envelope shape.
  • Media Library β€” upload files, list/delete, served from /uploads.
  • RBAC β€” roles and a permission matrix (Super Admin, Editor, Author seeded).
  • i18n β€” locale registry and localized content variants.
  • API Tokens β€” read-only/full/custom tokens for the public API.
  • JWT admin auth β€” first-run registration + login (argon2 password hashing).

Tech stack

Layer Technology
Language Rust (edition 2021)
Web server Axum 0.8
ORM / query builder SeaORM 2.0 + SeaQuery 1.0
Migrations sea-orm-migration (system) + runtime DDL (user content-types)
Database SQLite (default, embedded) / PostgreSQL 14+ (DATABASE_URL, online)
UI Dioxus 0.7 (native desktop + WASM web)
Async runtime Tokio
Auth JWT (jsonwebtoken) + argon2 password hashing
Serialization serde / serde_json

Project layout

A Cargo workspace of library crates plus two thin binary crates:

ferriscms/
β”œβ”€ Cargo.toml                  # workspace manifest + shared dependencies
β”œβ”€ docs/                       # planning / design docs (see below)
β”œβ”€ crates/
β”‚  β”œβ”€ core-domain/             # pure domain types (no IO): kinds, fields, relations, UID
β”‚  β”œβ”€ core-schema/             # schema model, JSON (de)serialization, validation, diffing
β”‚  β”œβ”€ api-types/               # shared request/response DTOs + Strapi query parser
β”‚  β”œβ”€ db/                      # SeaORM 2.0 system entities, migrations, seeding
β”‚  β”œβ”€ dynamic-store/           # runtime DDL + CRUD for user-defined content-types
β”‚  β”œβ”€ services/                # business logic (CTB, content, media, rbac, i18n, auth)
β”‚  β”œβ”€ api-rest/                # Axum routers: /api, /admin, /content-type-builder
β”‚  β”œβ”€ client-core/             # transport-abstract client the UI calls
β”‚  β”œβ”€ ui/                      # design system: tokens, widgets, screens (framework-agnostic)
β”‚  β”œβ”€ app/                     # Dioxus admin UI (web + desktop), calls client-core
β”‚  β”œβ”€ server-bin/              # binary: online Axum server (ferriscms)
β”‚  └─ desktop-bin/             # binary: offline desktop + embedded server (ferriscms-desktop)

Installation

Prerequisites

  • Rust β€” the workspace targets Rust edition 2021; the docs assume MSRV 1.82+. Installed with rustup.
  • Dioxus CLI (dx) β€” only needed for the web/desktop UI build:
    cargo install dioxus-cli
  • PostgreSQL β€” optional. The webserver (server-bin) defaults to a local SQLite file (ferriscms.db); set DATABASE_URL to a postgres://… URL to run it on PostgreSQL instead. The desktop app always uses embedded SQLite.

Build

Build the entire workspace (backend + UI libraries):

cargo build --workspace

Build just the two runtime binaries:

cargo build -p server-bin -p desktop-bin

Build the Dioxus admin UI (the ferriscms app crate). The dx CLI needs to locate the app's Dioxus.toml, which lives in crates/app/. Either run from inside that directory, or point dx at the package with --package ferriscms:

# native desktop app
cd crates/app
dx build --desktop --features desktop

# web (WASM) bundle -> crates/app/target/dx/ferriscms/debug/web
cd crates/app
dx build --web

# ...or, from the workspace root, use the package flag:
dx build --web --package ferriscms

Test

cargo test --workspace

Tests include a full end-to-end admin workflow against the Axum router in-memory (crates/api-rest/tests/auth_workflow.rs) and dynamic-store CRUD integration tests.

Saved views

Views are scoped to a content type and never copy records. The admin API is:

GET    /admin/content-manager/content-types/:uid/views
POST   /admin/content-manager/content-types/:uid/views
GET    /admin/content-manager/content-types/:uid/views/:id
PUT    /admin/content-manager/content-types/:uid/views/:id
DELETE /admin/content-manager/content-types/:uid/views/:id
POST   /admin/content-manager/content-types/:uid/views/:id/duplicate
POST   /admin/content-manager/content-types/:uid/views/:id/default
POST   /admin/content-manager/content-types/:uid/views/reorder
GET    /admin/content-manager/content-types/:uid/views/:id/records

The records endpoint translates the saved typed filter/sort configuration through the existing dynamic-store query builder, so filtering, sorting, and pagination remain server-side and content-type RBAC still applies.

Server configuration and JSON imports

The server loads config/{RUST_ENV}.toml through axum-conf; RUST_ENV defaults to prod. BIND_ADDR still overrides the configured bind address, and database, JWT, and media settings remain available through their existing environment variables.

JSON imports are controlled under [import.json]:

[import.json]
enabled = true
max_file_bytes = 10485760
default_dataset = "data"

JSON imports are enabled by default, limited to 10 MiB, and support either a top-level array or an object containing named array datasets. The import request's dataset selects a named dataset; default_dataset is used when it is omitted or empty.


Running

Online server (ferriscms)

Starts the Axum REST API + admin API on the configured port, running migrations and seeding roles/locales on boot. With no configuration it uses a local SQLite file (ferriscms.db in the working directory, gitignored), so cargo run -p server-bin works out of the box; set DATABASE_URL to a postgres://… URL to use PostgreSQL.

cargo run -p server-bin
Env var Default Purpose
DATABASE_URL sqlite://ferriscms.db?mode=rwc Local SQLite file by default; set a postgres://… URL for PostgreSQL
BIND_ADDR 0.0.0.0:8080 HTTP listen address
JWT_SECRET change-me-in-production HS256 signing secret (set in production!)
MEDIA_STORAGE_DIR media Directory for uploaded files

On first boot, register the first admin via POST /admin/register-admin (or use the UI). Then log in through POST /admin/login to get a JWT.

Local development

Run the backend and the web UI together with cargo-make. The --no-workspace flag prevents cargo-make from trying to run the task in every workspace crate. The backend listens on port 8080; the Dioxus web server uses port 8081. In development mode, the migration creates admin / admin as a local Super Admin, and the server imports the ERP content types and sample data from examples/content-types/. The import runs in upsert mode, so restarting the development server does not duplicate the examples.

Install cargo-make if it is not already available:

cargo install cargo-make
cargo make --no-workspace dev

Without cargo-make, use two terminals from the repository root:

# Terminal 1: Axum backend
FERRISCMS_ENV=development cargo run

The development bootstrap is configured in config/dev.toml under [dev.bootstrap]. Set enabled = false there to run the development server without importing the ERP examples.

# Terminal 2: Dioxus web UI
cd crates/app
FERRISCMS_API_URL=http://127.0.0.1:8080 dx serve --web --package ferriscms --port 8081

The dx command requires the Dioxus CLI version matching the app dependency (currently 0.7.10). The web UI is available at http://127.0.0.1:8081 and the backend at http://127.0.0.1:8080.

Offline desktop (ferriscms-desktop)

Runs the full backend (SQLite + migrations + seed) and exposes the REST API on a local HTTP port β€” no external database or server required.

cargo run -p desktop-bin
Env var Default Purpose
STRAPI_DB_PATH ferriscms-desktop.db Path to the local SQLite file
STRAPI_BIND_ADDR 127.0.0.1:1338 Local HTTP listen address
JWT_SECRET desktop-local-dev Signing secret
MEDIA_STORAGE_DIR media Upload directory

Admin UI

The Dioxus admin UI (the ferriscms app crate) talks to the backend through client-core. On web it uses same-origin requests; on desktop/native it targets FERRISCMS_API_URL (default http://127.0.0.1:1337).

Because the app's Dioxus.toml lives in crates/app/, run dx from inside that directory and select the app package explicitly:

# Dev server (hot reload) for the web UI
cd crates/app
FERRISCMS_API_URL=http://127.0.0.1:8080 dx serve --web --package ferriscms --port 8081

# Native desktop app
cd crates/app
dx run --desktop --features desktop
# or, from the workspace root:
dx run --desktop --package ferriscms --features desktop

# Point a desktop/native build at a running server
FERRISCMS_API_URL=http://127.0.0.1:1337 ./target/debug/ferriscms

The web build is a separate static bundle. Run it alongside the server (cargo run -p server-bin) so the UI can reach the API, or host the crates/app/target/dx/ferriscms/debug/web output on any static server that proxies /api, /admin, and /content-type-builder to the Axum process.


Container & Helm deployment

The repo ships a Dockerfile for the server binary and a Helm chart so the CMS can be deployed to Kubernetes.

Docker image

Build the ferriscms image locally. The build produces a single self-contained binary that embeds both the Axum webserver and the Dioxus WASM admin UI (via rust-embed):

docker build -t ferriscms .

The image is the webserver and uses PostgreSQL (external). The one binary serves the admin UI at / and the REST API at /api, /admin, and /content-type-builder. Media is stored in /data/media. Set the DB connection at run time:

docker run --rm -p 1337:1337 \
  -e DATABASE_URL='postgres://user:pass@host:5432/ferriscms' \
  -e JWT_SECRET='a-strong-secret' \
  -e MEDIA_STORAGE_DIR=/data/media \
  -v ferriscms-media:/data/media \
  ferriscms

For development, you can serve the UI from a directory instead of the embedded copy by setting FERRISCMS_UI_DIR (e.g. point it at a dx serve output).

Helm chart

The chart lives in deploy/helm/ferriscms/:

helm lint deploy/helm/ferriscms
helm template ferriscms deploy/helm/ferriscms
helm install ferriscms deploy/helm/ferriscms \
  --set image.tag=0.2.0 \
  --set env.JWT_SECRET='a-strong-secret'

Useful --set overrides:

Value Default Purpose
image.tag appVersion Image tag to deploy
env.DATABASE_URL postgres://postgres:postgres@postgres:5432/ferriscms PostgreSQL connection URL
env.JWT_SECRET change-me-in-production Signing secret β€” set in production!
persistence.enabled true Mount a PVC at /data for media
persistence.size 1Gi PVC size

Docker Compose

A docker-compose.yml starts a PostgreSQL database plus the webserver, using profiles:

# Build the image from the local Dockerfile (dev)
docker compose --profile local up --build

# Pull and run the prebuilt image from GHCR (registry)
docker compose --profile registry up

Both profiles share the same Postgres database and expose ferriscms on localhost:1337. Set JWT_SECRET (and optionally POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB) via a .env file. For the registry profile, override the image with FERRISCMS_IMAGE=ghcr.io/dinosath/ferris-cms:<tag>.


License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages