| Crates.io | bloom-web |
| lib.rs | bloom-web |
| version | 0.1.6 |
| created_at | 2025-09-28 16:10:34.4874+00 |
| updated_at | 2025-10-02 21:36:45.732035+00 |
| description | A lightweight backend framework combining Actix-Web, SQLx, and declarative macros for rapid API development |
| homepage | https://github.com/matusmesko/Bloom |
| repository | https://github.com/matusmesko/Bloom |
| max_upload_size | |
| id | 1858460 |
| size | 113,877 |
A lightweight backend framework that focuses on developer ergonomics by combining:
To quickly start a new Bloom project, clone the Bloom Initializr repository.
It provides all necessary Cargo packages, a ready-to-use main function, and example projects to help you get started immediately.
The Bloom framework consists of three main components:
bloom-coreContains the core framework functionality:
bloom-macrosProcedural 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 schedulingbloom (main crate)The unified API that ties everything together:
Configure your database and server port in config.toml at the repository root:
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
use bloom::prelude::*;
#[derive(Entity, Serialize, Deserialize, Debug)]
#[table("users")]
struct User {
#[id]
id: i32,
#[column("username")]
username: String,
#[column("email")]
email: String,
}
#[repository(User)]
pub struct UserRepository;
#[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(),
}
}
use bloom::application;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
application::run().enable_swagger().await
}
The #[derive(Entity)] macro supports various attributes:
#[derive(Entity)]
#[table("my_table")]
struct MyEntity {
// ...
}
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,
}
struct User {
#[id]
id: i32,
#[one_to_many]
posts: Vec<Post>,
#[many_to_one]
#[join_column("role_id")]
role: Role,
}
Available route mapping macros:
#[get_mapping("/path")]#[post_mapping("/path")]#[put_mapping("/path")]#[delete_mapping("/path")]#[patch_mapping("/path")]#[get_mapping("/users/{id}")]
pub async fn get_user(path: web::Path<i64>) -> impl Responder {
// id is automatically extracted from URL
}
#[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
}
The #[repository(Entity)] macro generates common CRUD operations:
#[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(60000)] // Run every 60 seconds
pub async fn cleanup_job(pool: &MySqlPool) {
// Background job logic
}
// 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/
cargo build
cargo test
cargo doc --open
cargo check
#[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,
}
All components use Rust's inventory crate for automatic registration:
No manual configuration required - just add the macros and everything works!
# 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.