Migration Guide

Migration Guide for Ignitia

⏱️ 12 min readπŸ“ 2275 wordsπŸ“… Updated 2025-10-16

Migration GuideπŸ”—

This guide helps you migrate to Ignitia from other Rust web frameworks and navigate version upgrades within Ignitia itself.

Migrating from Other FrameworksπŸ”—

From Actix-webπŸ”—

Ignitia shares many concepts with Actix-web, making migration relatively straightforward.

Basic Server SetupπŸ”—

Before (Actix-web):

use actix_web::{web, App, HttpServer, Result};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(hello))
            .route("/users/{id}", web::get().to(get_user))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

async fn hello() -> Result<String> {
    Ok("Hello World!".to_string())
}

async fn get_user(path: web::Path<u32>) -> Result<String> {
    Ok(format!("User ID: {}", path.into_inner()))
}

After (Ignitia v0.2.4+):

use ignitia::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let router = Router::new()
        .get("/", hello)
        .get("/users/{id}", get_user);

    let addr = "127.0.0.1:8080".parse()?;
    Server::new(router, addr).ignitia().await
}

async fn hello() -> &'static str {
    "Hello World!"
}

async fn get_user(Path(id): Path<u32>) -> String {
    format!("User ID: {}", id)
}

Middleware MigrationπŸ”—

Before (Actix-web):

use actix_web::{middleware::Logger, App};

App::new()
    .wrap(Logger::default())
    .wrap(actix_cors::Cors::default())

After (Ignitia v0.2.4+):

use ignitia::prelude::*;

Router::new()
    .middleware(LoggerMiddleware::new())
    .middleware(CorsMiddleware::new().build()?)

JSON HandlingπŸ”—

Before (Actix-web):

async fn create_user(user: web::Json<CreateUser>) -> Result<web::Json<User>> {
    let new_user = User {
        id: 1,
        name: user.name.clone(),
        email: user.email.clone(),
    };
    Ok(web::Json(new_user))
}

After (Ignitia v0.2.4+):

async fn create_user(Json(user): Json<CreateUser>) -> impl IntoResponse {
    let new_user = User {
        id: 1,
        name: user.name,
        email: user.email,
    };
    Response::json(new_user)  // Infallible in v0.2.4+
}

From AxumπŸ”—

Ignitia v0.2.4+ adopts middleware patterns inspired by Axum, making migration even smoother.

Basic Router SetupπŸ”—

Before (Axum):

use axum::{
    extract::Path,
    response::Json,
    routing::{get, post},
    Router,
};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(root))
        .route("/users/{id}", get(get_user))
        .route("/users", post(create_user));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

After (Ignitia v0.2.4+):

use ignitia::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let router = Router::new()
        .get("/", root)
        .get("/users/{id}", get_user)
        .post("/users", create_user);

    let addr = "127.0.0.1:8080".parse()?;
    Server::new(router, addr).ignitia().await
}

Middleware (Axum-compatible in v0.2.4)πŸ”—

Axum:

use axum::{
    middleware::{self, Next},
    response::Response,
    http::Request,
};

async fn my_middleware(req: Request<Body>, next: Next<Body>) -> Response {
    println!("Request: {}", req.uri());
    next.run(req).await
}

let app = Router::new()
    .route("/", get(handler))
    .layer(middleware::from_fn(my_middleware));

Ignitia v0.2.4+ (Same Pattern!):

use ignitia::prelude::*;
use ignitia::middleware::from_fn;

let my_middleware = from_fn(|req, next| async move {
    println!("Request: {}", req.uri.path());
    next.run(req).await
});

let router = Router::new()
    .get("/", handler)
    .middleware(my_middleware);

State ManagementπŸ”—

Axum:

async fn list_users(State(state): State<Arc<AppState>>) -> Json<Vec<User>> {
    // Use state
}

let app = Router::new()
    .route("/users", get(list_users))
    .with_state(shared_state);

Ignitia v0.2.4+:

async fn list_users(State(state): State<AppState>) -> impl IntoResponse {
    // Use state
    Response::json(users)
}

let router = Router::new()
    .state(app_state)
    .get("/users", list_users);

From RocketπŸ”—

Route DefinitionsπŸ”—

Before (Rocket):

#[macro_use] extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

#[get("/users/<id>")]
fn get_user(id: u32) -> String {
    format!("User ID: {}", id)
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![index, get_user])
}

After (Ignitia v0.2.4+):

use ignitia::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let router = Router::new()
        .get("/", index)
        .get("/users/{id}", get_user);

    let addr = "127.0.0.1:8080".parse()?;
    Server::new(router, addr).ignitia().await
}

async fn index() -> &'static str {
    "Hello, world!"
}

async fn get_user(Path(id): Path<u32>) -> String {
    format!("User ID: {}", id)
}

JSON GuardsπŸ”—

Before (Rocket):

#[post("/users", data = "<user>")]
fn create_user(user: Json<User>) -> Json<User> {
    Json(user.into_inner())
}

After (Ignitia v0.2.4+):

async fn create_user(Json(user): Json<User>) -> impl IntoResponse {
    Response::json(user)  // Infallible
}

From WarpπŸ”—

Filter-based to Router-basedπŸ”—

Before (Warp):

use warp::Filter;

let hello = warp::path!("hello" / String)
    .map(|name| format!("Hello, {}!", name));

let routes = warp::get().and(hello);

warp::serve(routes)
    .run((, 3030))
    .await;

After (Ignitia v0.2.4+):

use ignitia::prelude::*;

let router = Router::new()
    .get("/hello/{name}", hello);

Server::new(router, "127.0.0.1:8080".parse()?).ignitia().await

Ignitia Version MigrationsπŸ”—

v0.2.3 to v0.2.4πŸ”—

This is a major update with breaking changes. Ignitia v0.2.4 introduces a new middleware system inspired by Axum and several ergonomic improvements.

Breaking Changes SummaryπŸ”—

  1. Middleware API: Changed from before/after to handle(req, next)
  2. Handler Trait: Renamed to UniversalHandler
  3. Response API: Response::json() now returns Response directly (not Result)
  4. Removed Middleware: AuthMiddleware and ErrorHandlerMiddleware removed
  5. New Feature: IntoResponse trait for automatic error conversion

1. Middleware API MigrationπŸ”—

Old (v0.2.3):

use async_trait::async_trait;

#[async_trait]
impl Middleware for MyMiddleware {
    async fn before(&self, req: &mut Request) -> Result<()> {
        // Modify request
        req.headers.insert("X-Custom", "value".parse().unwrap());
        Ok(())
    }

    async fn after(&self, req: &Request, res: &mut Response) -> Result<()> {
        // Modify response
        res.headers.insert("X-Processed", "true".parse().unwrap());
        Ok(())
    }
}

New (v0.2.4+, Axum-inspired):

impl Middleware for MyMiddleware {
    async fn handle(&self, mut req: Request, next: Next) -> Response {
        // Modify request
        req.headers.insert("X-Custom", "value".parse().unwrap());

        // Call next middleware/handler
        let mut response = next.run(req).await;

        // Modify response
        response.headers.insert("X-Processed", "true".parse().unwrap());

        response
    }
}

Using from_fn (Recommended for simple cases):

use ignitia::middleware::from_fn;

let my_middleware = from_fn(|mut req, next| async move {
    req.headers.insert("X-Custom", "value".parse().unwrap());
    let mut response = next.run(req).await;
    response.headers.insert("X-Processed", "true".parse().unwrap());
    response
});

router.middleware(my_middleware)

2. Response API MigrationπŸ”—

Old (v0.2.3):

async fn handler() -> Result<Response> {
    let data = MyData { field: "value" };
    Ok(Response::json(data)?)  // Double Result
}

New (v0.2.4+):

async fn handler() -> Result<Response> {
    let data = MyData { field: "value" };
    Ok(Response::json(data))  // Single Result, infallible
}

// Or even simpler with IntoResponse:
async fn handler() -> impl IntoResponse {
    let data = MyData { field: "value" };
    Response::json(data)  // No Result wrapper needed!
}

3. Error Handling MigrationπŸ”—

Old (v0.2.3):

// Required ErrorHandlerMiddleware
let router = Router::new()
    .middleware(ErrorHandlerMiddleware::new())
    .get("/users/{id}", get_user);

async fn get_user(Path(id): Path<u64>) -> Result<Response> {
    let user = database::find_user(id)
        .await
        .map_err(|_| Error::not_found("User not found"))?;

    Ok(Response::json(user)?)
}

New (v0.2.4+):

// No error middleware needed - automatic via IntoResponse
let router = Router::new()
    .get("/users/{id}", get_user);

async fn get_user(Path(id): Path<u64>) -> Result<Response> {
    let user = database::find_user(id)
        .await
        .ok_or_else(|| Error::not_found("User not found"))?;

    Ok(Response::json(user))  // Errors auto-convert to responses
}

4. Auth Middleware MigrationπŸ”—

Old (v0.2.3):

let router = Router::new()
    .middleware(AuthMiddleware::new(secret_key))
    .get("/admin", admin_handler);

New (v0.2.4+):

use ignitia::middleware::from_fn;

let auth_middleware = from_fn(|req, next| async move {
    if let Some(token) = req.headers.get("Authorization") {
        if verify_token(token) {
            return next.run(req).await;
        }
    }
    Response::new().with_status(StatusCode::UNAUTHORIZED)
});

let router = Router::new()
    .middleware(auth_middleware)
    .get("/admin", admin_handler);

5. Handler Signature UpdatesπŸ”—

Old (v0.2.3):

// Implementing Handler trait
impl Handler for MyHandler {
    async fn handle(&self, req: Request) -> Result<Response> {
        Ok(Response::json(data)?)
    }
}

New (v0.2.4+):

// UniversalHandler automatically implemented for functions
async fn my_handler() -> impl IntoResponse {
    Response::json(data)
}

// Or with extractors
async fn my_handler(
    Path(id): Path<u64>,
    Json(body): Json<CreateData>
) -> Result<impl IntoResponse> {
    Ok(Response::json(data))
}

6. Complete Migration ExampleπŸ”—

Before (v0.2.3):

use ignitia::prelude::*;

#[async_trait]
struct LoggingMiddleware;

#[async_trait]
impl Middleware for LoggingMiddleware {
    async fn before(&self, req: &mut Request) -> Result<()> {
        println!("Request: {}", req.uri);
        Ok(())
    }

    async fn after(&self, _req: &Request, res: &mut Response) -> Result<()> {
        println!("Response: {}", res.status);
        Ok(())
    }
}

async fn get_user(Path(id): Path<u64>) -> Result<Response> {
    let user = fetch_user(id).await?;
    Ok(Response::json(user)?)
}

#[tokio::main]
async fn main() -> Result<()> {
    let router = Router::new()
        .middleware(LoggingMiddleware)
        .middleware(ErrorHandlerMiddleware::new())
        .middleware(AuthMiddleware::new(secret))
        .get("/users/{id}", get_user);

    Server::new(router, "127.0.0.1:8080".parse()?).ignitia().await
}

After (v0.2.4+):

use ignitia::prelude::*;
use ignitia::middleware::from_fn;

async fn get_user(Path(id): Path<u64>) -> Result<Response> {
    let user = fetch_user(id).await?;
    Ok(Response::json(user))  // Infallible now!
}

#[tokio::main]
async fn main() -> Result<()> {
    // Logging middleware using from_fn
    let logging = from_fn(|req, next| async move {
        println!("Request: {}", req.uri.path());
        let response = next.run(req).await;
        println!("Response: {}", response.status);
        response
    });

    // Auth middleware using from_fn
    let auth = from_fn(|req, next| async move {
        if let Some(token) = req.headers.get("Authorization") {
            if verify_token(token) {
                return next.run(req).await;
            }
        }
        Response::new().with_status(StatusCode::UNAUTHORIZED)
    });

    let router = Router::new()
        .middleware(logging)
        .middleware(auth)  // No ErrorHandlerMiddleware needed!
        .get("/users/{id}", get_user);

    Server::new(router, "127.0.0.1:8080".parse()?).ignitia().await
}

Migration Checklist for v0.2.4πŸ”—

  • Update Cargo.toml to version 0.2.4
  • Replace #[async_trait] middleware with handle(req, next) pattern
  • Remove ? from all Response::json() calls
  • Remove ErrorHandlerMiddleware usage
  • Replace AuthMiddleware with from_fn implementation
  • Update custom middleware to use handle method
  • Change handler returns to use impl IntoResponse where beneficial
  • Test all endpoints thoroughly
  • Update tests to match new API

v0.1.x to v0.2.xπŸ”—

Major ChangesπŸ”—

  1. Improved HTTP/2 Support: Enhanced configuration options
  2. WebSocket API Refinements: Simplified handler creation
  3. Middleware System Updates: More flexible middleware composition
  4. Performance Optimizations: Better connection handling

Handler Function SignaturesπŸ”—

v0.1.x:

async fn handler(req: Request) -> Result<Response> {
    Ok(Response::text("Hello"))
}

v0.2.x:

async fn handler() -> Result<Response> {
    Ok(Response::text("Hello"))
}

// Or with extractors
async fn handler(Json(data): Json<MyData>) -> Result<Response> {
    Ok(Response::json(data))
}

WebSocket Handler UpdatesπŸ”—

v0.1.x:

struct MyHandler;

impl WebSocketHandler for MyHandler {
    async fn handle_connection(&self, ws: WebSocketConnection) -> Result<()> {
        while let Some(msg) = ws.recv().await {
            ws.send(Message::text("Echo")).await?;
        }
        Ok(())
    }
}

v0.2.x:

let handler = websocket_handler(|ws| async move {
    while let Some(msg) = ws.recv().await {
        ws.send(Message::text("Echo")).await?;
    }
    Ok(())
});

Server ConfigurationπŸ”—

v0.1.x:

let server = Server::new(router, addr)
    .enable_http2(true)
    .run().await?;

v0.2.x:

let config = ServerConfig {
    http2: Http2Config {
        enabled: true,
        max_concurrent_streams: Some(1000),
        ..Default::default()
    },
    ..Default::default()
};

let server = Server::new(router, addr)
    .with_config(config)
    .ignitia().await?;

Common Migration PatternsπŸ”—

Error Handling (v0.2.4+)πŸ”—

Using IntoResponse:

async fn handler() -> Result<impl IntoResponse, Error> {
    let data = some_operation()
        .await
        .map_err(|e| Error::internal(format!("Operation failed: {}", e)))?;

    Ok(Response::json(data))
}

// Errors automatically convert to HTTP responses
async fn handler2() -> Result<Response> {
    Err(Error::not_found("Resource not found"))  // Auto-converts to 404
}

Middleware Conversion (v0.2.4+)πŸ”—

Simple Middleware:

use ignitia::middleware::from_fn;

let simple = from_fn(|req, next| async move {
    println!("Before handler");
    let response = next.run(req).await;
    println!("After handler");
    response
});

Complex Middleware:

struct ComplexMiddleware {
    config: Arc<Config>,
}

impl Middleware for ComplexMiddleware {
    async fn handle(&self, req: Request, next: Next) -> Response {
        // Pre-processing
        if !self.validate_request(&req) {
            return Response::new()
                .with_status(StatusCode::BAD_REQUEST);
        }

        // Execute handler
        let mut response = next.run(req).await;

        // Post-processing
        response.headers.insert("X-Custom", "value".parse().unwrap());

        response
    }
}

State ManagementπŸ”—

use std::sync::Arc;
use tokio::sync::RwLock;

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

let app_state = AppState {
    db: Arc::new(Database::new()),
    cache: Arc::new(RwLock::new(Cache::new())),
};

let router = Router::new()
    .state(app_state)
    .get("/data", get_data);

async fn get_data(State(state): State<AppState>) -> Result<Response> {
    let data = state.db.fetch_data().await?;
    Ok(Response::json(data))
}

Breaking ChangesπŸ”—

v0.2.4 Breaking ChangesπŸ”—

  1. Middleware Trait: Changed from before/after to handle(req, next)
  2. Response API: Response::json() is now infallible
  3. Handler Trait: Renamed to UniversalHandler
  4. Removed Middleware: AuthMiddleware and ErrorHandlerMiddleware
  5. Error Handling: Automatic via IntoResponse trait

v0.2.3 Breaking ChangesπŸ”—

  1. Handler Signatures: Direct request parameter removed
  2. WebSocket API: Simplified handler creation
  3. Middleware Trait: Updated method signatures
  4. Error Types: Consolidated error system

Migration ChecklistπŸ”—

Pre-MigrationπŸ”—

  • Review current framework usage patterns
  • Identify custom middleware and handlers
  • Document current API endpoints
  • Set up test environment
  • Backup current codebase
  • Read CHANGELOG for your target version

During Migration (v0.2.4)πŸ”—

  • Update Cargo.toml to ignitia = "0.2.4"
  • Replace all #[async_trait] middleware implementations
  • Update middleware from before/after to handle(req, next)
  • Remove ? from Response::json() calls
  • Remove ErrorHandlerMiddleware usage
  • Replace AuthMiddleware with from_fn
  • Update handler return types to use impl IntoResponse
  • Convert custom middleware to new pattern
  • Update tests for new API

Post-MigrationπŸ”—

  • Run comprehensive tests
  • Performance benchmarking
  • Update documentation
  • Deploy to staging environment
  • Monitor for issues
  • Update CI/CD pipelines

TroubleshootingπŸ”—

Common Issues (v0.2.4)πŸ”—

β€œMethod before not found” ErrorπŸ”—

Issue: Old middleware implementation.

Solution: Update to new handle method:

impl Middleware for MyMiddleware {
    async fn handle(&self, req: Request, next: Next) -> Response {
        // Your logic here
        next.run(req).await
    }
}

β€œExpected Result, found Response” ErrorπŸ”—

Issue: Response::json() no longer returns Result.

Solution: Remove the ? operator:

// Old
Ok(Response::json(data)?)

// New
Ok(Response::json(data))

β€œErrorHandlerMiddleware not found” ErrorπŸ”—

Issue: Middleware removed in v0.2.4.

Solution: Remove it - errors now auto-convert via IntoResponse:

// Old
router.middleware(ErrorHandlerMiddleware::new())

// New - just remove it!
router  // Errors handle automatically

β€œAuthMiddleware not found” ErrorπŸ”—

Issue: Middleware removed in v0.2.4.

Solution: Use from_fn to create custom auth:

let auth = from_fn(|req, next| async move {
    if let Some(token) = req.headers.get("Authorization") {
        if verify_token(token) {
            return next.run(req).await;
        }
    }
    Response::new().with_status(StatusCode::UNAUTHORIZED)
});

router.middleware(auth)

Middleware Not WorkingπŸ”—

Issue: Middleware not being called.

Solution: Ensure proper handle implementation:

impl Middleware for MyMiddleware {
    async fn handle(&self, req: Request, next: Next) -> Response {
        // Must call next.run() for handler to execute
        next.run(req).await
    }
}

State Not AvailableπŸ”—

Issue: State extractor failing in handlers.

Solution: Ensure state is properly registered:

let router = Router::new()
    .state(my_state)  // Must register before routes
    .get("/endpoint", handler);

Performance ConsiderationsπŸ”—

  1. Connection Pooling: Use Ignitia’s state management for database connections
  2. Middleware Order: Optimize for early rejection (auth β†’ validation β†’ handler)
  3. HTTP/2 Configuration: Tune settings for your use case
  4. from_fn Performance: Zero-cost abstraction, use freely

Getting HelpπŸ”—


This migration guide covers all major version transitions in Ignitia. For specific use cases not covered here, please refer to the framework documentation or reach out to the community for assistance.