# Bloom (Rust, Actix-Web, SQLx)
A lightweight backend framework that focuses on developer ergonomics by combining:
- Actix-Web for high‑performance HTTP
- SQLx (MySQL) for async, compile‑time checked database access
- Declarative macros to auto‑register controllers/routes, entities/migrations, and scheduled jobs
- Inventory-based discovery: drop in code, it self-registers
- Optional OpenAPI generation and Swagger UI via utoipa
- Simple, timestamped logging macro
- Config-first setup using a TOML file (config.toml)
## Features
- Convention-over-configuration controller macro: implement methods and have routes auto-wired
- Route mapping macros (get_mapping, post_mapping, put_mapping, delete_mapping, patch_mapping)
- Entity derive macro that can generate basic migration SQL and CRUD helper bindings
- Repository attribute macro to generate common CRUD helpers for an entity
- Inventory registries for:
- Controllers (auto-configured onto Actix service)
- Entities/migrations (run at boot)
- Scheduled jobs (spawned on boot)
- Built-in optional Swagger UI and OpenAPI JSON
- Flexible CORS controlled via config.toml
## 🚀 Quick Start with Bloom Initializr
To quickly start a new Bloom project, clone the [Bloom Initializr repository](https://github.com/matusmesko/Bloom-initializr).
It provides all necessary Cargo packages, a ready-to-use `main` function, and example projects to help you get started immediately.
## Architecture
The Bloom framework consists of three main components:
### 1. `bloom-core`
Contains the core framework functionality:
- Application builder and configuration
- Entity registry for database models
- Controller registry for HTTP handlers
- Swagger documentation generation
- Scheduler for background tasks
- Logging utilities
### 2. `bloom-macros`
Procedural macros for code generation:
- `#[derive(Entity)]` - Generates database entity code
- `#[get_mapping]`, `#[put_mapping]`, etc. - HTTP route mapping
- `#[repository(Entity)]` - Repository pattern implementation
- `#[scheduled(interval)]` - Background job scheduling
### 3. `bloom` (main crate)
The unified API that ties everything together:
- Re-exports all functionality from sub-crates
- Provides convenient prelude module
- Main entry point for users
## Quick Start
1. Configure your database and server port in config.toml at the repository root:
```toml
port = 8080
database_url = "mysql://user:pass@host:3306/dbname"
[cors]
enabled = true
allowed_origins = ["http://localhost:3000"]
allowed_methods = ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers = ["Content-Type", "Authorization"]
allow_credentials = true
max_age = 3600
```
### 2. Create your first entity
```rust
use bloom::prelude::*;
#[derive(Entity, Serialize, Deserialize, Debug)]
#[table("users")]
struct User {
#[id]
id: i32,
#[column("username")]
username: String,
#[column("email")]
email: String,
}
```
### 3. Create a repository
```rust
#[repository(User)]
pub struct UserRepository;
```
### 4. Add REST endpoints
```rust
#[get_mapping("/users")]
pub async fn get_all_users(pool: web::Data<MySqlPool>) -> impl Responder {
match UserRepository::find_all_raw(pool.get_ref()).await {
Ok(rows) => {
let json = serde_json::Value::Array(rows.into_iter().map(|row| {
serde_json::json!({
"id": row.try_get::<i32,_>("id").unwrap_or_default(),
"username": row.try_get::<String,_>("username").unwrap_or_default(),
"email": row.try_get::<String,_>("email").unwrap_or_default(),
})
}).collect());
HttpResponse::Ok().json(json)
}
Err(_) => HttpResponse::InternalServerError().finish()
}
}
#[put_mapping("/users/{id}")]
pub async fn update_user(
path: web::Path<i64>,
payload: web::Json<UpdateUser>,
pool: web::Data<MySqlPool>
) -> impl Responder {
let id = path.into_inner();
let data = payload.into_inner();
let user = User {
id: id as i32,
username: data.username,
email: data.email,
};
match UserRepository::update(pool.get_ref(), &user).await {
Ok(affected) if affected > 0 => HttpResponse::Ok().finish(),
Ok(_) => HttpResponse::NotFound().finish(),
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
```
### 5. Set up your application
```rust
use bloom::application;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
application::run().enable_swagger().await
}
```
## Requirements
- Rust toolchain (stable)
- MySQL compatible database
## Entity Attributes
The `#[derive(Entity)]` macro supports various attributes:
### Table Configuration
```rust
#[derive(Entity)]
#[table("my_table")]
struct MyEntity {
// ...
}
```
### Field Configuration
```rust
struct User {
#[id] // Primary key
id: i32,
#[column("user_name")] // Custom column name
name: String,
// Regular field (uses field name as column name)
email: String,
}
```
### Relationships
```rust
struct User {
#[id]
id: i32,
#[one_to_many]
posts: Vec<Post>,
#[many_to_one]
#[join_column("role_id")]
role: Role,
}
```
## HTTP Route Mapping
Available route mapping macros:
- `#[get_mapping("/path")]`
- `#[post_mapping("/path")]`
- `#[put_mapping("/path")]`
- `#[delete_mapping("/path")]`
- `#[patch_mapping("/path")]`
### Path Parameters
```rust
#[get_mapping("/users/{id}")]
pub async fn get_user(path: web::Path<i64>) -> impl Responder {
// id is automatically extracted from URL
}
```
### JSON Payloads
```rust
#[derive(Deserialize, utoipa::ToSchema, ApiSchema)]
pub struct CreateUser {
pub username: String,
pub email: String,
}
#[post_mapping("/users")]
pub async fn create_user(payload: web::Json<CreateUser>) -> impl Responder {
// Automatic JSON deserialization
}
```
## Repository Pattern
The `#[repository(Entity)]` macro generates common CRUD operations:
```rust
#[repository(User)]
pub struct UserRepository;
// Generated methods:
// - find_all_raw(pool) -> Result<Vec<MySqlRow>>
// - find_by_id_raw(pool, id) -> Result<Option<MySqlRow>>
// - exists_by_id(pool, id) -> Result<bool>
// - count(pool) -> Result<i64>
// - delete_by_id(pool, id) -> Result<u64>
// - create(pool, entity) -> Result<u64>
// - update(pool, entity) -> Result<u64>
// - insert_or_update(pool, entity) -> Result<u64>
```
## Scheduled Jobs
```rust
#[scheduled(60000)] // Run every 60 seconds
pub async fn cleanup_job(pool: &MySqlPool) {
// Background job logic
}
```
## OpenAPI/Swagger Integration
```rust
// Add to your struct for automatic API documentation
#[derive(Deserialize, utoipa::ToSchema, ApiSchema)]
pub struct UpdateUser {
pub username: String,
pub email: String,
}
```
Access Swagger UI at: `http://localhost:8080/swagger-ui/`
## Configuration
## Building and Testing
### Build the project
```bash
cargo build
```
### Run tests
```bash
cargo test
```
### Build documentation
```bash
cargo doc --open
```
### Check the project
```bash
cargo check
```
## Advanced Features
### Complex Entity Relationships
```rust
#[derive(Entity)]
#[table("users")]
pub struct User {
#[id]
id: i32,
// One-to-many relationship (virtual)
#[one_to_many]
posts: Vec<Post>,
// One-to-one relationship (virtual)
#[one_to_one]
profile: Option<Box<Profile>>,
}
#[derive(Entity)]
#[table("posts")]
pub struct Post {
#[id]
id: i32,
// Many-to-one with foreign key
#[many_to_one]
#[join_column("user_id")]
user: User,
title: String,
content: String,
}
```
### Auto-Registration System
All components use Rust's `inventory` crate for automatic registration:
- Entities automatically register their table creation functions
- Routes automatically register with the HTTP server
- Repositories are automatically available
- Scheduled jobs automatically start on server boot
No manual configuration required - just add the macros and everything works!
## Production Notes
### Performance
- Built on Actix-Web, one of the fastest Rust web frameworks
- Compile-time SQL validation with SQLx
- Zero-cost abstractions through procedural macros
### Security
- Type-safe SQL queries prevent injection attacks
- Compile-time verification of database schemas
- Proper error handling with Result types
### Deployment
```bash
# Build optimized release version
cargo build --release
# The binary will be in target/release/
```
This project is licensed under the MIT License - see the LICENSE file for details.