# Rejoice
A simple and delightful web framework for Rust with file-based routing, layouts, live reload, Tailwind CSS, and SolidJS islands for interactivity.
## Features
- **File-based routing** - Just create `.rs` files in `src/routes/` and they become pages
- **Nested layouts** - Share UI across routes with `layout.rs` files
- **Live reload** - Changes automatically refresh the browser
- **Tailwind CSS v4** - Utility-first CSS that scans your Rust and TSX files
- **SolidJS islands** - Add interactive components without a full SPA
- **Type-safe HTML** - Use Maud for compile-time HTML templating
- **SQLite database** - Optional built-in support with sqlx
- **Zero config** - Just run `rejoice dev` and start building
## Quick Start
```bash
# Install the CLI
cargo install rejoice
# Create a new project
rejoice init my-app
cd my-app
# Start the dev server
rejoice dev
```
To create a project with SQLite database support:
```bash
rejoice init my-app --with-db
```
## File-Based Routing
```
src/routes/
├── layout.rs -> Wraps all pages
├── index.rs -> GET /
├── about.rs -> GET /about
└── users/
├── layout.rs -> Wraps /users/* pages
├── index.rs -> GET /users
└── [id].rs -> GET /users/:id
```
Each route file exports a `page` function:
```rust
use rejoice::{
State,
html::{Markup, html},
};
pub async fn page(State(_): State<()>) -> Markup {
html! {
h1 { "Hello, world!" }
}
}
```
## Layouts
Layouts wrap pages and nested layouts. Create a `layout.rs` file to share UI:
```rust
use rejoice::{
Children, State,
html::{DOCTYPE, Markup, html},
};
pub async fn layout(State(_): State<()>, children: Children) -> Markup {
html! {
(DOCTYPE)
html {
head { title { "My App" } }
body {
nav { a href="/" { "Home" } }
main { (children) }
footer { "Built with Rejoice" }
}
}
}
}
```
Layouts nest automatically. A page at `/users/123` will be wrapped by:
1. `routes/layout.rs` (if exists)
2. `routes/users/layout.rs` (if exists)
3. `routes/users/[id].rs`
## Database Support
Create a project with `--with-db` to get SQLite support out of the box:
```bash
rejoice init my-app --with-db
```
This sets up:
- A SQLite database file
- `.env` with `DATABASE_URL`
- An `AppState` struct with a connection pool
- Routes configured to receive state
Access the database in your routes:
```rust
use crate::AppState;
use rejoice::{
State,
db::query_as,
html::{Markup, html},
};
pub async fn page(State(state): State<AppState>) -> Markup {
let users: Vec<(String,)> = query_as("SELECT name FROM users")
.fetch_all(&state.db)
.await
.unwrap();
html! {
h1 { "Users" }
ul {
@for user in &users {
li { (user.0) }
}
}
}
}
```
## Custom App State
You can add your own state (database, config, services, etc.) to make it available in all routes:
```rust
use rejoice::{
App,
db::{Pool, PoolConfig, Sqlite, create_pool},
};
#[derive(Clone)]
pub struct AppState {
pub db: Pool<Sqlite>,
pub config: AppConfig,
}
rejoice::routes!(AppState);
#[tokio::main]
async fn main() {
let pool = create_pool(PoolConfig { /* ... */ }).await;
let state = AppState { db: pool, config: load_config() };
let app = App::with_state(8080, create_router(), state);
app.run().await;
}
```
Then access it in routes and layouts:
```rust
pub async fn page(State(state): State<AppState>) -> Markup {
// Use state.db, state.config, etc.
}
```
## SolidJS Islands
Add interactive components to your pages:
```rust
use rejoice::{
State,
html::{Markup, html},
island,
};
pub async fn page(State(_): State<()>) -> Markup {
html! {
h1 { "My Page" }
(island!(Counter, { initial: 0 }))
}
}
```
Create the component in `client/Counter.tsx`:
```tsx
import { createSignal } from "solid-js";
export default function Counter(props: { initial: number }) {
const [count, setCount] = createSignal(props.initial);
return (
<button onClick={() => setCount((c) => c + 1)}>
Count: {count()}
</button>
);
}
```
That's it! The island is automatically registered and hydrated on the client.
## Tailwind CSS
Tailwind CSS v4 is included out of the box. Just use Tailwind classes in your Rust templates or TSX components:
```rust
use rejoice::{
State,
html::{Markup, html},
};
pub async fn page(State(_): State<()>) -> Markup {
html! {
h1 class="text-4xl font-bold text-blue-600" { "Hello!" }
p class="mt-4 text-gray-700" { "Styled with Tailwind." }
}
}
```
Tailwind automatically scans your `src/**/*.rs` and `client/**/*.tsx` files for classes.