Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Welcome to the official documentation for the Oxidite web framework.

This documentation is designed to help you get started with Oxidite, learn its core concepts, and explore its rich feature set.

Community & Support

Have questions or want to connect with other Oxidite developers? Join our community on GitHub Discussions.

Getting Started with Oxidite

This guide will help you build your first web application with Oxidite.

Installation

Prerequisites

  • Rust 1.70 or higher
  • Cargo package manager

Create a New Project with the CLI

The easiest way to get started is by using the oxidite-cli.

cargo install --path oxidite-cli
oxidite new my-app

This will create a new Oxidite project with a default structure.

Your First Route

Navigate to your new project and open src/main.rs. It will look something like this:

use oxidite::prelude::*;

mod routes;
mod controllers;
mod models;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut router = Router::new();

    // Register routes
    routes::register(&mut router);

    let server = Server::new(router);
    println!("πŸš€ API Server running on http://127.0.0.1:8080");
    server.listen("127.0.0.1:8080".parse()?).await?;

    Ok(())
}

The routes are defined in src/routes/mod.rs:

#![allow(unused)]
fn main() {
use oxidite::prelude::*;
use serde_json::json;

pub fn register(router: &mut Router) {
    router.get("/api/health", health);
}

async fn health(_req: OxiditeRequest) -> Result<OxiditeResponse> {
    Ok(OxiditeResponse::json(json!({"status": "ok"})))
}
}

Run Your App

cargo run

Visit http://localhost:8080/api/health in your browser!

JSON API Example

Let’s create a simple JSON API for managing users.

First, generate a model and controller:

oxidite make model User
oxidite make controller UserController

Now, let’s define the User model in src/models/user.rs:

#![allow(unused)]
fn main() {
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct User {
    pub id: i64,
    pub name: String,
    pub email: String,
}
}

Next, let’s add the routes in src/routes/mod.rs:

#![allow(unused)]
fn main() {
use crate::controllers::user_controller::{list_users, get_user, create_user};

pub fn register(router: &mut Router) {
    router.get("/api/users", list_users);
    router.get("/api/users/:id", get_user);
    router.post("/api/users", create_user);
}
}

Finally, implement the controller functions in src/controllers/user_controller.rs:

#![allow(unused)]
fn main() {
use oxidite::prelude::*;
use crate::models::user::User;
use serde_json::json;

pub async fn list_users(_req: OxiditeRequest) -> Result<OxiditeResponse> {
    let users = vec![
        User { id: 1, name: "Alice".into(), email: "alice@example.com".into() },
        User { id: 2, name: "Bob".into(), email: "bob@example.com".into() },
    ];
    Ok(OxiditeResponse::json(json!(users)))
}

pub async fn get_user(req: OxiditeRequest) -> Result<OxiditeResponse> {
    let id: i64 = req.param("id")?;
    let user = User {
        id,
        name: "Alice".into(),
        email: "alice@example.com".into(),
    };
    Ok(OxiditeResponse::json(json!(user)))
}

#[derive(Deserialize)]
struct CreateUserRequest {
    name: String,
    email: String,
}

pub async fn create_user(mut req: OxiditeRequest) -> Result<OxiditeResponse> {
    let data: CreateUserRequest = req.body_json().await?;
    let user = User {
        id: 3,
        name: data.name,
        email: data.email,
    };
    Ok(OxiditeResponse::json(json!(user)))
}
}

Using Middleware

Add CORS and logging in src/main.rs:

use oxidite::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let mut app = Router::new();
    // ... register routes

    // Add middleware
    let app = ServiceBuilder::new()
        .layer(LoggerLayer)
        .layer(CorsLayer::permissive())
        .service(app);

    Server::new(app)
        .listen("127.0.0.1:3000".parse()?)
        .await
}

Next Steps

CLI Tool Guide

The Oxidite CLI helps you scaffold projects, generate code, and manage your application.

Installation

cargo install --path oxidite-cli

Commands

Create New Project

# Basic project
oxidite new myapp

# With specific type
oxidite new myapp --project-type api
oxidite new myapp --project-type fullstack
oxidite new myapp --project-type microservice

Code Generation

# Generate model
oxidite make model User

# Generate controller
oxidite make controller UserController

# Generate middleware
oxidite make middleware AuthMiddleware

Database Migrations

# Create migration
oxidite migrate create create_users_table

# Run migrations
oxidite migrate run

# Rollback last migration
oxidite migrate revert

# Check status
oxidite migrate status

Database Seeders

# Create seeder
oxidite seed create UserSeeder

# Run seeders
oxidite seed run

Queue Management

# Start workers
oxidite queue work --workers 4

# View statistics
oxidite queue list

# View dead letter queue
oxidite queue dlq

# Clear pending jobs
oxidite queue clear

Health Check

# System diagnostics
oxidite doctor

Build

# Development build
oxidite build

# Production build
oxidite build --release

Development Server

# Start with hot reload
oxidite dev

Project Structure

After oxidite new myapp, you get:

myapp/
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ main.rs
β”‚   β”œβ”€β”€ models/
β”‚   β”œβ”€β”€ controllers/
β”‚   └── middleware/
β”œβ”€β”€ migrations/
β”œβ”€β”€ seeders/
β”œβ”€β”€ templates/
└── config.toml

Configuration

Oxidite projects use config.toml:

[server]
host = "127.0.0.1"
port = 8080

[database]
url = "postgresql://localhost/myapp"

[cache]
url = "redis://127.0.0.1"

[queue]
url = "redis://127.0.0.1"

Tips

  • Use oxidite doctor to debug issues
  • Run oxidite migrate status before deploying
  • Use oxidite dev for automatic reloading during development

Fullstack Web Development with Oxidite

Build complete fullstack applications with server-side rendering, authentication, and database integration.

Project Setup

The best way to start a fullstack project is with the oxidite-cli:

oxidite new my-blog --project-type fullstack
cd my-blog

This will generate a project with a complete structure, including directories for templates, static files, and database models.

Complete Example: Blog Application

Main Application (src/main.rs)

use oxidite::prelude::*;
use oxidite_db::Database;
use oxidite_template::TemplateEngine;
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    db: Arc<Database>,
    templates: Arc<TemplateEngine>,
}

#[tokio::main]
async fn main() -> Result<()> {
    // Database
    let db = Arc::new(Database::connect(&std::env::var("DATABASE_URL")?).await?);

    // Template engine
    let templates = Arc::new(TemplateEngine::new("templates"));

    let state = AppState { db, templates };

    // Routes
    let mut app = Router::new();

    // Public routes
    app.get("/", home);
    app.get("/posts/:id", show_post);

    // ... more routes

    // Middleware and state
    let app = ServiceBuilder::new()
        .layer(LoggerLayer)
        .layer(CorsLayer::permissive())
        .state(state)
        .service(app);

    Server::new(app).listen("127.0.0.1:3000".parse()?).await
}

Models (src/models/post.rs)

#![allow(unused)]
fn main() {
use oxidite_db::Model;
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};

#[derive(Model, Serialize, Deserialize, Default)]
#[table_name = "posts"]
pub struct Post {
    pub id: i64,
    pub user_id: i64,
    pub title: String,
    pub content: String,
    pub published: bool,
    pub created_at: DateTime<Utc>,
}
}

Controllers (src/controllers/post_controller.rs)

#![allow(unused)]
fn main() {
use crate::AppState;
use oxidite::prelude::*;
use crate::models::post::Post;
use std::collections::HashMap;

pub async fn home(State(state): State<AppState>) -> Result<OxiditeResponse> {
    let posts = Post::query()
        .where_("published", "=", true)
        .order_by("created_at", "DESC")
        .get_all(&*state.db)
        .await?;

    let mut context = oxidite_template::Context::new();
    context.insert("posts", &posts);

    let html = state.templates.render("home.html", &context)?;

    Ok(OxiditeResponse::html(html))
}

pub async fn show_post(
    Path(params): Path<HashMap<String, String>>,
    State(state): State<AppState>,
) -> Result<OxiditeResponse> {
    let id: i64 = params.get("id").unwrap().parse()?;
    let post = Post::find(id, &*state.db).await?;

    let mut context = oxidite_template::Context::new();
    context.insert("post", &post);

    let html = state.templates.render("post.html", &context)?;

    Ok(OxiditeResponse::html(html))
}
}

Templates (templates/home.html)

<!DOCTYPE html>
<html>
<head>
    <title>My Blog</title>
</head>
<body>
    <h1>Latest Posts</h1>
    {% for post in posts %}
    <article>
        <h2><a href="/posts/{{ post.id }}">{{ post.title }}</a></h2>
        <p>{{ post.created_at }}</p>
    </article>
    {% endfor %}
</body>
</html>

Best Practices

  1. Separate concerns: Models, Controllers, Views
  2. Use middleware: Auth, CORS, logging
  3. Background jobs: For slow operations
  4. Caching: For frequently accessed data
  5. Validation: Validate all input
  6. Security: Use CSRF protection, sanitize HTML

Deploy

See the Deployment Guide for production deployment.

Database & ORM Guide

Complete guide to using the Oxidite ORM for database operations.

Installation

[dependencies]
oxidite = { version = "1.0", features = ["database"] }

Setup

Database Connection

Create .env file:

DATABASE_URL=postgresql://user:password@localhost/mydb

Connect in your app (src/main.rs):

use oxidite::prelude::*;
use oxidite_db::Database;
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    db: Arc<Database>,
}

#[tokio::main]
async fn main() -> Result<()> {
    // Load config
    dotenv::dotenv().ok();

    // Connect to database
    let db = Arc::new(Database::connect(&std::env::var("DATABASE_URL")?).await?);

    let state = AppState { db };

    // ... your app setup
    let app = Router::new().with_state(state);
    // ...
    Ok(())
}

Defining Models

#![allow(unused)]
fn main() {
use oxidite_db::Model;
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};

#[derive(Model, Serialize, Deserialize, Clone, Default)]
#[table_name = "users"]
pub struct User {
    pub id: i64,
    pub name: String,
    pub email: String,
    pub password_hash: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}
}

CRUD Operations

Create

#![allow(unused)]
fn main() {
let mut user = User {
    name: "Alice".to_string(),
    email: "alice@example.com".to_string(),
    password_hash: hash_password("password")?,
    ..Default::default()
};

user.save(&db).await?;
}

Read

#![allow(unused)]
fn main() {
// Find by ID
let user = User::find(1, &db).await?;

// Find all
let users = User::all(&db).await?;

// Where clause
let user = User::query()
    .where_("email", "=", "alice@example.com")
    .first(&db)
    .await?;

// Multiple conditions
let users = User::query()
    .where_("active", "=", true)
    .where_("created_at", ">", yesterday)
    .limit(10)
    .get_all(&db)
    .await?;
}

Update

#![allow(unused)]
fn main() {
let mut user = User::find(1, &db).await?;
user.name = "Alice Smith".to_string();
user.save(&db).await?;
}

Delete

#![allow(unused)]
fn main() {
let user = User::find(1, &db).await?;
user.delete(&db).await?;
}

Relationships

Has Many

#![allow(unused)]
fn main() {
#[derive(Model)]
#[table_name = "posts"]
pub struct Post {
    pub id: i64,
    pub user_id: i64,
    pub title: String,
    pub content: String,
}

// Load user's posts
let user = User::find(1, &db).await?;
let posts = user.has_many::<Post>("user_id", &db).await?;
}

Belongs To

#![allow(unused)]
fn main() {
// Load post's author
let post = Post::find(1, &db).await?;
let user = post.belongs_to::<User>("user_id", &db).await?;
}

Migrations

Create Migration

Using CLI:

oxidite migrate create create_users_table

This will create a new SQL file in the migrations directory.

Run Migrations

oxidite migrate run

Or programmatically:

#![allow(unused)]
fn main() {
use oxidite_db::Migration;

Migration::run_all(&db).await?;
}

Rollback

oxidite migrate revert

Transactions

#![allow(unused)]
fn main() {
let tx = db.begin().await?;

// Perform operations
user.save(&tx).await?;
post.save(&tx).await?;

// Commit
tx.commit().await?;

// Or rollback on error
tx.rollback().await?;
}

Authentication Guide

Complete guide to implementing authentication in Oxidite applications.

Installation

[dependencies]
oxidite = { version = "1.0", features = ["auth", "database"] }

Quick Start

This example demonstrates how to set up a simple login and a protected profile route.

Main Application (src/main.rs)

use oxidite::prelude::*;
use oxidite_auth::AuthMiddleware;

#[tokio::main]
async fn main() -> Result<()> {
    let mut app = Router::new();

    app.post("/login", login);
    app.get("/profile", profile).layer(AuthMiddleware);

    Server::new(app).listen("127.0.0.1:3000".parse()?).await
}

Login Handler (src/controllers/auth_controller.rs)

#![allow(unused)]
fn main() {
use oxidite::prelude::*;
use oxidite_auth::{Auth, create_jwt, LoginRequest, TokenResponse};
use crate::models::user::User; // Your user model
use serde_json::json;

pub async fn login(Json(creds): Json<LoginRequest>) -> Result<OxiditeResponse> {
    // Verify credentials
    let user = User::find_by_email(&creds.email).await?;

    if !oxidite_auth::verify_password(&creds.password, &user.password_hash)? {
        return Err(Error::Unauthorized("Invalid credentials".to_string()));
    }

    // Create JWT
    let token = create_jwt(user.id, "your-secret-key")?;

    Ok(OxiditeResponse::json(json!(TokenResponse { token })))
}
}

Protected Route (src/controllers/user_controller.rs)

#![allow(unused)]
fn main() {
use oxidite::prelude::*;
use oxidite_auth::Auth;
use crate::models::user::User;
use serde_json::json;

pub async fn profile(auth: Auth) -> Result<OxiditeResponse> {
    // The user is available via the Auth extractor
    let user = User::find(auth.user_id, &db).await?;
    Ok(OxiditeResponse::json(json!(user)))
}
}

Password Hashing

Oxidite uses Argon2 for password hashing.

#![allow(unused)]
fn main() {
use oxidite_auth::hash_password;

// Hash password
let hash = hash_password("password123")?;

// Verification is handled by the login logic.
}

JWT Authentication

The AuthMiddleware handles JWT verification automatically. You just need to provide a secret key in your configuration.

RBAC (Roles & Permissions)

#![allow(unused)]
fn main() {
use oxidite_auth::RequirePermission;

// Middleware for a route that requires a specific permission
app.delete("/users/:id", delete_user)
    .layer(RequirePermission::new("users.delete"));
}

To assign roles and permissions, you’ll typically interact with the database models provided by oxidite-auth.

Authorization Guide

Role-based access control (RBAC) and permission management in Oxidite.

Installation

[dependencies]
oxidite = { version = "1.0", features = ["auth", "database"] }

Quick Start

#![allow(unused)]
fn main() {
use oxidite::prelude::*;
use oxidite_auth::{RequirePermission, RequireRole};

// Protect routes with permissions
app.delete("/users/:id", delete_user)
    .layer(RequirePermission::new("users.delete"));

app.get("/admin", admin_panel)
    .layer(RequireRole::new("admin"));
}

Roles and Permissions

The oxidite-auth crate provides database models for Role and Permission. You can manage these in your application logic.

#![allow(unused)]
fn main() {
// Example: Creating a role and assigning it to a user
// (This is a simplified example, your actual implementation may vary)

// Create role
let admin_role = Role::create("admin", &db).await?;

// Create permission
let create_users_perm = Permission::create("users.create", &db).await?;

// Add permission to role
admin_role.add_permission(&create_users_perm, &db).await?;

// Assign role to user
user.assign_role(&admin_role, &db).await?;

// Check permission
if user.has_permission("users.create", &db).await? {
    // Allow action
}

// Check role
if user.has_role("admin", &db).await? {
    // Allow access
}
}

Middleware

#![allow(unused)]
fn main() {
use oxidite_auth::{RequirePermission, RequireRole, RequireAnyPermission};

// Require specific permission
app.post("/posts", create_post)
    .layer(RequirePermission::new("posts.create"));

// Require specific role
app.get("/admin/dashboard", dashboard)
    .layer(RequireRole::new("admin"));

// Require any of multiple permissions
app.put("/posts/:id", update_post)
    .layer(RequireAnyPermission::new(vec![
        "posts.update.own".to_string(),
        "posts.update.all".to_string()
    ]));
}

In Handlers

You can also perform authorization checks within your handlers.

#![allow(unused)]
fn main() {
use oxidite_auth::Auth;

async fn delete_user(
    auth: Auth,
    Path(id): Path<i64>,
    State(db): State<Arc<Database>>,
) -> Result<OxiditeResponse> {
    let user = User::find(auth.user_id, &db).await?;

    if !user.has_permission("users.delete", &db).await? {
        return Err(Error::Forbidden("You do not have permission to delete users.".to_string()));
    }

    User::delete(id, &db).await?;

    Ok(OxiditeResponse::json(json!({ "status": "ok" })))
}
}

Dynamic Permissions

For more complex scenarios, you can implement custom logic in your handlers.

#![allow(unused)]
fn main() {
// Check at runtime
async fn can_edit_post(user: &User, post: &Post, db: &Database) -> Result<bool> {
    Ok(user.has_permission("posts.update.all", db).await? ||
    (user.has_permission("posts.update.own", db).await? && post.user_id == user.id))
}
}

Real-time & WebSocket Guide

Build real-time applications with WebSockets in Oxidite.

Installation

[dependencies]
oxidite = { version = "1.0", features = ["realtime"] }

Quick Start

use oxidite::prelude::*;
use oxidite_realtime::WebSocketManager;
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    ws_manager: Arc<WebSocketManager>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let ws_manager = Arc::new(WebSocketManager::new());
    let state = AppState { ws_manager };

    let mut app = Router::new();

    // WebSocket endpoint
    app.get("/ws", |State(state): State<AppState>, req: OxiditeRequest| async {
        state.ws_manager.handle_upgrade(req).await
    });

    let app = app.with_state(state);

    Server::new(app).listen("127.0.0.1:3000".parse()?).await
}

Broadcasting

#![allow(unused)]
fn main() {
// Broadcast to all clients
state.ws_manager.broadcast("Hello everyone!").await;

// Broadcast to room
state.ws_manager.broadcast_to("room1", "Room message").await;
}

Direct Messaging

#![allow(unused)]
fn main() {
// Send to specific client
state.ws_manager.send_to(client_id, "Direct message").await;
}

Room Management

#![allow(unused)]
fn main() {
// Join room
state.ws_manager.join(client_id, "chat-room").await;

// Leave room
state.ws_manager.leave(client_id, "chat-room").await;

// List clients in room
let clients = state.ws_manager.clients_in_room("chat-room").await;
}

Complete Chat Example

use oxidite::prelude::*;
use oxidite_realtime::WebSocketManager;
use serde::{Serialize, Deserialize};
use std::sync::Arc;

#[derive(Serialize, Deserialize)]
struct ChatMessage {
    user: String,
    message: String,
    room: String,
}

#[derive(Clone)]
struct AppState {
    ws_manager: Arc<WebSocketManager>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let ws_manager = Arc::new(WebSocketManager::new());
    let state = AppState { ws_manager };

    let mut app = Router::new();

    // WebSocket connection
    app.get("/ws", |State(state): State<AppState>, req: OxiditeRequest| async {
        state.ws_manager.handle_upgrade(req).await
    });

    // Send message to room
    app.post("/chat/send", |State(state): State<AppState>, Json(msg): Json<ChatMessage>| async move {
        state.ws_manager.broadcast_to(&msg.room, &serde_json::to_string(&msg)?).await;
        Ok(Json(json!({ "status": "sent" })))
    });

    let app = app.with_state(state);

    Server::new(app).listen("127.0.0.1:3000".parse()?).await
}

Client (JavaScript)

const ws = new WebSocket('ws://localhost:3000/ws');

ws.onopen = () => {
    console.log('Connected');
    ws.send(JSON.stringify({
        type: 'join',
        room: 'general'
    }));
};

ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    console.log('Received:', data);
};

// Send message via HTTP POST to /chat/send
async function sendMessage(room, user, message) {
    await fetch('/chat/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ room, user, message })
    });
}

Templating Guide

Server-side rendering with the Oxidite template engine.

Installation

[dependencies]
oxidite = { version = "1.0", features = ["templates"] }

Quick Start

use oxidite::prelude::*;
use oxidite_template::{TemplateEngine, Context};
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    templates: Arc<TemplateEngine>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let templates = Arc::new(TemplateEngine::new("templates"));
    let state = AppState { templates };

    let mut app = Router::new();

    app.get("/", |State(state): State<AppState>| async move {
        let mut context = Context::new();
        context.insert("title", "Home");
        context.insert("message", "Welcome!");

        let html = state.templates.render("index.html", &context)?;

        Ok(OxiditeResponse::html(html))
    });

    let app = app.with_state(state);

    Server::new(app).listen("127.0.0.1:3000".parse()?).await
}

Template Syntax

templates/index.html:

<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ message }}</h1>

    {% if user %}
        <p>Hello, {{ user.name }}!</p>
    {% else %}
        <p>Please log in</p>
    {% endif %}

    <ul>
    {% for item in items %}
        <li>{{ item.name }}</li>
    {% endfor %}
    </ul>
</body>
</html>

Template Inheritance

templates/layout.html:

<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}My App{% endblock %}</title>
</head>
<body>
    <nav>Navigation</nav>

    <main>
        {% block content %}{% endblock %}
    </main>

    <footer>Footer</footer>
</body>
</html>

templates/home.html:

{% extends "layout.html" %}

{% block title %}Home - My App{% endblock %}

{% block content %}
    <h1>Welcome Home!</h1>
{% endblock %}

Rendering

#![allow(unused)]
fn main() {
use oxidite::prelude::*;
use oxidite_template::Context;
use serde::Serialize;

#[derive(Serialize)]
struct User { name: String }

#[derive(Serialize)]
struct Post { title: String }

async fn home(State(state): State<AppState>) -> Result<OxiditeResponse> {
    let mut context = Context::new();
    context.insert("user", &User { name: "Alice".to_string() });
    context.insert("posts", &vec![Post { title: "First Post".to_string() }]);

    let html = state.templates.render("home.html", &context)?;

    Ok(OxiditeResponse::html(html))
}
}

Background Jobs Guide

Learn how to process background jobs, schedule cron tasks, and handle async operations with Oxidite.

Installation

[dependencies]
oxidite = { version = "1.0", features = ["queue"] }

Quick Start

Define a Job

#![allow(unused)]
fn main() {
use oxidite_queue::{Job, JobResult};
use async_trait::async_trait;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct SendEmailJob {
    to: String,
    subject: String,
    body: String,
}

#[async_trait]
impl Job for SendEmailJob {
    async fn handle(&self) -> JobResult {
        // Your job logic
        println!("Sending email to: {}", self.to);

        // Simulate email sending
        tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

        Ok(())
    }
}
}

Enqueue Jobs

use oxidite::prelude::*;
use oxidite_queue::{Queue, Job};

#[tokio::main]
async fn main() -> Result<()> {
    // Create queue (Memory or Redis)
    let queue = Queue::new_redis("redis://127.0.0.1").await?;

    // Enqueue a job
    let job = SendEmailJob {
        to: "user@example.com".to_string(),
        subject: "Welcome!".to_string(),
        body: "Thanks for signing up!".to_string(),
    };

    queue.dispatch(job).await?;

    Ok(())
}

Start Workers

You can start workers using the CLI:

oxidite queue work

Or programmatically:

#![allow(unused)]
fn main() {
use std::sync::Arc;
use oxidite_queue::Worker;

// Start worker
let worker = Worker::new(Arc::new(queue))
    .concurrency(4);  // 4 concurrent workers

worker.run().await;
}

Cron Jobs

Scheduling cron jobs is not yet implemented in the oxidite-queue crate, but it is on the roadmap.

Retry Logic

Jobs automatically retry on failure with exponential backoff. You can configure this when dispatching a job.

#![allow(unused)]
fn main() {
use std::time::Duration;

queue.dispatch(job)
    .with_max_retries(5)
    .with_backoff(Duration::from_secs(60))
    .await?;
}

Dead Letter Queue

Jobs that fail after all retries go to the Dead Letter Queue. You can manage these using the CLI.

# List failed jobs
oxidite queue dlq

# Retry a specific job from DLQ
oxidite queue retry <job_id>

CLI Commands

Manage jobs with the CLI:

# Start worker
oxidite queue work --workers 4

# View statistics
oxidite queue list

# View dead letter queue
oxidite queue dlq

# Clear pending jobs
oxidite queue clear

API Keys Guide

Learn how to implement API key authentication in Oxidite.

Installation

[dependencies]
oxidite = { version = "1.0", features = ["auth", "database"] }

Setup

use oxidite::prelude::*;
use oxidite_auth::ApiKeyMiddleware;
use oxidite_db::Database;
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    db: Arc<Database>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let db = Arc::new(Database::connect(&std::env::var("DATABASE_URL")?).await?);
    let state = AppState { db };

    let mut app = Router::new();

    // Protected route
    app.get("/api/data", get_data)
        .layer(ApiKeyMiddleware);

    let app = app.with_state(state);

    Server::new(app).listen("127.0.0.1:3000".parse()?).await
}

Generate API Keys

#![allow(unused)]
fn main() {
use oxidite_auth::ApiKey;

async fn create_api_key(user_id: i64, db: &Database) -> Result<String> {
    let (api_key_model, plain_key) = ApiKey::generate(user_id);
    api_key_model.save(db).await?;

    // Return the plain key to user (only shown once)
    Ok(plain_key)
}
}

Validate API Keys

The ApiKeyMiddleware handles validation automatically. It expects the API key in the Authorization: Bearer <key> header.

Using API Keys

Clients send the API key in the Authorization header:

curl -H "Authorization: Bearer your-api-key" \
     http://localhost:3000/api/data

Complete Example

#![allow(unused)]
fn main() {
use oxidite::prelude::*;
use oxidite_auth::{ApiKey, ApiKeyAuth};
use serde::Serialize;

#[derive(Serialize)]
struct ApiKeyResponse {
    key: String,
}

async fn create_key(
    State(db): State<Arc<Database>>,
    Json(req): Json<CreateKeyRequest>,
) -> Result<OxiditeResponse> {
    let (api_key_model, plain_key) = ApiKey::generate(req.user_id);
    api_key_model.save(&db).await?;

    Ok(OxiditeResponse::json(json!(ApiKeyResponse { key: plain_key })))
}

async fn protected_route(auth: ApiKeyAuth) -> Result<OxiditeResponse> {
    // The user_id is available via the ApiKeyAuth extractor
    let user_id = auth.user_id;
    Ok(OxiditeResponse::json(json!({ "message": "Success!", "user_id": user_id })))
}
}

Best Practices

  1. Never log API keys
  2. Hash keys before storing
  3. Allow key rotation
  4. Set expiration dates
  5. Rate limit by API key

Revoke Keys

#![allow(unused)]
fn main() {
async fn revoke_key(key_id: i64, db: &Database) -> Result<()> {
    ApiKey::delete(key_id, db).await
}
}

Oxidite Architecture Overview

This document provides a comprehensive overview of Oxidite’s architecture, design principles, and internal workings.


🎯 Design Principles

1. Performance First

  • Zero-cost abstractions leveraging Rust’s type system
  • Async I/O with Tokio for maximum throughput
  • Efficient memory management without garbage collection

2. Security by Default

  • Secure defaults for all configurations
  • Memory safety guaranteed by Rust
  • Protection against OWASP Top 10 vulnerabilities
  • Constant-time cryptographic operations

3. Developer Ergonomics

  • Type-safe APIs that prevent runtime errors
  • Comprehensive error messages
  • Automatic documentation generation
  • Familiar patterns from popular frameworks

4. Modularity

  • Composable crates that can be used independently
  • Clean separation of concerns
  • Plugin architecture for extensibility

πŸ—οΈ System Architecture

graph TD
    A[HTTP Request] --> B[oxidite-core]
    B --> C[Middleware Stack]
    C --> D[Router]
    D --> E[Handler]
    E --> F[oxidite-db]
    E --> G[oxidite-cache]
    E --> H[oxidite-queue]
    F --> I[SQL/NoSQL]
    G --> J[Redis/Memory]
    H --> K[Job Queue]
    E --> L[Response]
    L --> C
    C --> M[HTTP Response]

πŸ“¦ Crate Dependency Graph

graph LR
    CLI[oxidite-cli] --> Core[oxidite-core]
    CLI --> Router[oxidite-router]
    CLI --> DB[oxidite-db]
    CLI --> Migrate[oxidite-migrate]

    Router --> Core
    Middleware[oxidite-middleware] --> Core
    Auth[oxidite-auth] --> Core
    Auth --> DB
    DB --> Core
    Queue[oxidite-queue] --> Core
    Cache[oxidite-cache] --> Core
    Realtime[oxidite-realtime] --> Core
    Admin[oxidite-admin] --> Core
    Admin --> Auth
    Admin --> DB

πŸ”„ Request Lifecycle

1. Connection Accepted

#![allow(unused)]
fn main() {
TcpListener::bind(addr).await
    -> Accept connection
    -> Spawn task
}

2. HTTP Parsing

#![allow(unused)]
fn main() {
Hyper parses HTTP request
    -> Creates Request<Body>
    -> Passes to service
}

3. Middleware Processing (Pre)

#![allow(unused)]
fn main() {
ServiceBuilder::new()
    .layer(LoggerLayer)      // Log request
    .layer(CorsLayer)        // Check CORS
    .layer(AuthLayer)        // Authenticate
    .layer(RateLimitLayer)   // Rate limit
    .service(router)
}

4. Routing

#![allow(unused)]
fn main() {
Router matches path & method
    -> Extracts path params
    -> Extracts query params
    -> Extracts body
    -> Calls handler
}

5. Handler Execution

#![allow(unused)]
fn main() {
async fn handler(Path(id): Path<i64>) -> Result<Json<User>> {
    let user = User::find(id).await?;
    Ok(Json(user))
}
}

6. Middleware Processing (Post)

#![allow(unused)]
fn main() {
Response flows back through middleware
    -> Compression
    -> Security headers
    -> Logging
}

7. Response Sent

#![allow(unused)]
fn main() {
Hyper serializes response
    -> Sends over TCP
    -> Connection closed or kept alive
}

🎨 Core Components

oxidite-core (Implemented)

Purpose: HTTP server foundation, request/response handling, and routing.

Key Types:

  • Server: The main HTTP server.
  • Router: The routing engine.
  • OxiditeRequest: A type alias for http::Request.
  • OxiditeResponse: A type alias for http::Response.
  • Path<T>, Query<T>, Json<T>: Extractors for typed parameters and bodies.
  • Error: The framework’s primary error type.
  • Result<T>: A convenient Result type alias.

Responsibilities:

  • TCP connection management and HTTP protocol handling (via Hyper).
  • Service integration (via Tower).
  • Path matching, HTTP method routing, and parameter extraction.
  • Error propagation.

oxidite-middleware (Implemented)

Purpose: Cross-cutting concerns via Tower layers

Key Middleware:

  • Logger: Request/response logging
  • Cors: CORS policy enforcement
  • Compression: gzip/brotli response compression
  • RateLimit: Token bucket rate limiting
  • Timeout: Request timeout handling
  • SecurityHeaders: CSP, HSTS, X-Frame-Options, etc.

Architecture:

#![allow(unused)]
fn main() {
impl<S> Service<Request> for Middleware<S> {
    type Response = Response;
    type Error = Error;
    type Future = Future;

    fn call(&mut self, req: Request) -> Self::Future {
        // Pre-processing
        let fut = self.inner.call(req);
        // Post-processing
    }
}
}

oxidite-db (In Progress)

Purpose: Database abstraction and ORM

Key Types:

  • Database: A connection pool to the database.
  • Model: A trait for database models.
  • QueryBuilder: For constructing type-safe SQL queries.

Supported Databases:

  • PostgreSQL (via tokio-postgres)
  • MySQL (via mysql_async)
  • SQLite (via rusqlite + async wrapper)

Architecture:

#![allow(unused)]
fn main() {
pub trait Database: Send + Sync {
    async fn execute(&self, query: &str) -> Result<u64>;
    async fn query<T>(&self, query: &str) -> Result<Vec<T>>;
}

pub trait Model: Sized {
    fn table_name() -> &'static str;
    async fn find(id: i64, db: &Database) -> Result<Self>;
    async fn save(&mut self, db: &Database) -> Result<()>;
    async fn delete(&self, db: &Database) -> Result<()>;
}
}

oxidite-auth (In Progress)

Purpose: Authentication and authorization

Strategies:

  1. JWT: Stateless token authentication
  2. API Key: Simple API authentication

RBAC/PBAC:

#![allow(unused)]
fn main() {
pub trait Authorizable {
    async fn has_role(&self, role: &str, db: &Database) -> Result<bool>;
    async fn has_permission(&self, permission: &str, db: &Database) -> Result<bool>;
}

// Middleware usage
.layer(AuthMiddleware)
.layer(RequireRole::new("admin"))
.layer(RequirePermission::new("users:delete"))
}

oxidite-queue (In Progress)

Purpose: Background job processing

Architecture:

#![allow(unused)]
fn main() {
#[async_trait]
pub trait Job: Serialize + DeserializeOwned + Send + Sync {
    async fn handle(&self) -> Result<()>;
}

// Enqueue
queue.dispatch(SendEmailJob { to: "user@example.com" }).await?;

// Worker
Worker::new(queue)
    .concurrency(4)
    .run()
    .await;
}

Backends:

  • In-memory (development)
  • Redis (production)

oxidite-cli (In Progress)

Purpose: Command-line interface

Commands:

  • new: Project scaffolding
  • dev: Development server with hot reload
  • build: Production build
  • migrate: Database migrations
  • make:*: Code generation
  • queue:work: Start queue workers

πŸ” Security Architecture

Memory Safety

  • No buffer overflows (Rust prevents)
  • No use-after-free (ownership system)
  • No data races (borrow checker)

Cryptography

  • Argon2id for password hashing
  • Constant-time comparisons
  • Secure random number generation
  • TLS 1.3 for transport security

Input Validation

  • Type-safe parameter extraction
  • Automatic deserialization validation
  • SQL injection prevention (prepared statements)
  • XSS prevention (auto-escaping templates)