cargo install rustwing-clirustwing new my_saas
cd my_saasThis generates:
my_saas/
├── Cargo.toml # workspace root
├── .env.example # environment template
├── api/ # web server
│ ├── Cargo.toml
│ ├── src/
│ │ ├── main.rs
│ │ ├── domain/user.rs # User model
│ │ ├── http/ # routes, handlers, DTOs
│ │ ├── repository/ # SQLx-native database access
│ │ └── services/ # business logic and orchestration
│ └── migrations/ # auto-run on startup
├── worker/ # DB/LLM-backed worker tick loop
└── frontend/ # your frontend (BYO)
cp .env.example .env
# Edit DATABASE_URL in .env to point to your Postgres databaserustwing runOr directly with cargo:
cargo run --bin apiThe server:
- Connects to Postgres
- Runs pending migrations (creates tables automatically)
- Starts listening on
http://0.0.0.0:3000
# Register a user (returns a JWT token + user info)
curl -s -X POST http://localhost:3000/auth/register \
-H 'Content-Type: application/json' \
-d '{"username":"demo","email":"demo@test.com","password":"password123"}' | jq .
# The token in the response can be used immediately for authenticated requests.
# If the registration token does not work (e.g., "Invalid or expired token"),
# log in instead — both return the same type of token:
# Login to get a fresh token
curl -s -X POST http://localhost:3000/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"demo@test.com","password":"password123"}' | jq .
# Extract the token using jq
TOKEN=$(curl -s -X POST http://localhost:3000/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"demo@test.com","password":"password123"}' | jq -r '.token')
# List users (authenticated)
curl http://localhost:3000/users/cursor \
-H "Authorization: Bearer $TOKEN"
# Get user by ID
curl http://localhost:3000/users/<id> \
-H "Authorization: Bearer $TOKEN"rustwing g resource post \
--fields 'title:string:required:length(1,255)' \
--fields 'body:string:required'This creates:
- Domain model (
Post) - Create/Update DTOs with validation
- Service module for validation, pagination limits, and business logic
- SQLx-native repository glue and explicit CRUD behavior
- Route handlers with offset and cursor pagination
- Router registration
- Database migration
# After generating, create a post (authenticated)
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title":"Hello World","body":"My first post!"}'For SaaS data, opt into tenant scope explicitly:
rustwing g resource ticket \
--tenant org_id \
--fields 'org_id:uuid:required' \
--fields 'subject:string:required:length(1,255)' \
--fields 'assigned_member_id:uuid:optional'This generates routes like /orgs/{org_id}/tickets. The tenant ID comes from the path, so create/update request bodies do not include org_id.
Scopes also work for parent-child resources:
rustwing g resource comment \
--scope ticket_id \
--fields 'ticket_id:uuid:required' \
--fields 'body:string:required'This generates routes like /tickets/{ticket_id}/comments. Combine scopes when a resource needs both tenant and parent boundaries:
rustwing g resource note \
--tenant org_id \
--scope ticket_id \
--fields 'org_id:uuid:required' \
--fields 'ticket_id:uuid:required' \
--fields 'body:string:required'The generated worker connects to the same database, builds the configured LLM client, and runs a tick loop:
cargo run --bin workerSet WORKER_TICK_SECONDS to change the polling interval.
- CLI reference — all
rustwingcommands - Architecture guide — how the framework works
- Configuration reference — all environment variables