From 4d865beb34e54ef7ef169890c725f1cb3db8ed7f Mon Sep 17 00:00:00 2001
From: Mason Stallmo <masonstallmo@hey.com>
Date: Sat, 21 Sep 2024 00:32:01 -0700
Subject: [PATCH 1/4] Implement shared application state as a trait

Transition from using the `AppContext` struct internally to using
`AppContextTrait` to represent shared global state.

This allows end users to implement and extend their own shared
application state beyond what loco provides out of the box.

The `AppContextTrait` is implemented for `AppContext` so `AppContext`
can be used directly by users that don't have a need to extend what is
already provided by the context.
---
 loco-extras/src/initializers/extra_db.rs      |   6 +-
 loco-extras/src/initializers/mongodb/mod.rs   |   6 +-
 loco-extras/src/initializers/multi_db.rs      |   6 +-
 .../src/initializers/normalize_path.rs        |   4 +-
 .../src/initializers/opentelemetry/mod.rs     |   6 +-
 loco-extras/src/initializers/prometheus.rs    |   4 +-
 src/app.rs                                    | 158 ++++++++++++++++--
 src/banner.rs                                 |  36 ++--
 src/boot.rs                                   | 110 ++++++------
 src/cli.rs                                    |  58 +++----
 src/controller/app_routes.rs                  | 101 +++++------
 src/controller/describe.rs                    |   4 +-
 src/controller/health.rs                      |  12 +-
 src/controller/mod.rs                         |  12 +-
 src/controller/ping.rs                        |   4 +-
 src/controller/routes.rs                      |  24 +--
 src/db.rs                                     |  15 +-
 src/gen/controller.rs                         |   7 +-
 src/gen/mod.rs                                |  17 +-
 src/gen/model.rs                              |   8 +-
 src/gen/scaffold.rs                           |   9 +-
 src/logger.rs                                 |  14 +-
 src/mailer/mod.rs                             |  14 +-
 src/scheduler.rs                              |  27 ++-
 src/task.rs                                   |  15 +-
 src/testing.rs                                |  16 +-
 src/tests_cfg/db.rs                           |   8 +-
 src/tests_cfg/task.rs                         |   6 +-
 src/worker.rs                                 |  13 +-
 29 files changed, 447 insertions(+), 273 deletions(-)

diff --git a/loco-extras/src/initializers/extra_db.rs b/loco-extras/src/initializers/extra_db.rs
index 95c67cf7c..c982844b9 100644
--- a/loco-extras/src/initializers/extra_db.rs
+++ b/loco-extras/src/initializers/extra_db.rs
@@ -1,6 +1,6 @@
 use async_trait::async_trait;
 use axum::{Extension, Router as AxumRouter};
-use loco_rs::{db, prelude::*};
+use loco_rs::{app::Context, db, prelude::*};
 
 #[allow(clippy::module_name_repetitions)]
 pub struct ExtraDbInitializer;
@@ -11,9 +11,9 @@ impl Initializer for ExtraDbInitializer {
         "extra-db".to_string()
     }
 
-    async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(&self, router: AxumRouter, ctx: &dyn Context) -> Result<AxumRouter> {
         let extra_db_config = ctx
-            .config
+            .config()
             .initializers
             .clone()
             .ok_or_else(|| Error::Message("initializers config not configured".to_string()))?;
diff --git a/loco-extras/src/initializers/mongodb/mod.rs b/loco-extras/src/initializers/mongodb/mod.rs
index 87564ae4e..f7cae508e 100644
--- a/loco-extras/src/initializers/mongodb/mod.rs
+++ b/loco-extras/src/initializers/mongodb/mod.rs
@@ -1,6 +1,6 @@
 use async_trait::async_trait;
 use axum::{Extension, Router as AxumRouter};
-use loco_rs::prelude::*;
+use loco_rs::{app::Context, prelude::*};
 use mongodb::{bson::doc, options::ClientOptions, Client, Database};
 
 #[allow(clippy::module_name_repetitions)]
@@ -12,9 +12,9 @@ impl Initializer for MongoDbInitializer {
         "mongodb".to_string()
     }
 
-    async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(&self, router: AxumRouter, ctx: &dyn Context) -> Result<AxumRouter> {
         let mongo_db_config = ctx
-            .config
+            .config()
             .initializers
             .clone()
             .ok_or_else(|| Error::Message("initializers config not configured".to_string()))?;
diff --git a/loco-extras/src/initializers/multi_db.rs b/loco-extras/src/initializers/multi_db.rs
index 6c4cffbe3..b050544d4 100644
--- a/loco-extras/src/initializers/multi_db.rs
+++ b/loco-extras/src/initializers/multi_db.rs
@@ -1,6 +1,6 @@
 use async_trait::async_trait;
 use axum::{Extension, Router as AxumRouter};
-use loco_rs::{db, errors::Error, prelude::*};
+use loco_rs::{app::Context, db, errors::Error, prelude::*};
 
 #[allow(clippy::module_name_repetitions)]
 pub struct MultiDbInitializer;
@@ -11,9 +11,9 @@ impl Initializer for MultiDbInitializer {
         "multi-db".to_string()
     }
 
-    async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(&self, router: AxumRouter, ctx: &dyn Context) -> Result<AxumRouter> {
         let settings = ctx
-            .config
+            .config()
             .initializers
             .clone()
             .ok_or_else(|| Error::Message("settings config not configured".to_string()))?;
diff --git a/loco-extras/src/initializers/normalize_path.rs b/loco-extras/src/initializers/normalize_path.rs
index 2c2ebd012..3c275c1c4 100644
--- a/loco-extras/src/initializers/normalize_path.rs
+++ b/loco-extras/src/initializers/normalize_path.rs
@@ -15,7 +15,7 @@
 //! [axum-docs]: https://docs.rs/axum/latest/axum/middleware/index.html#rewriting-request-uri-in-middleware
 use async_trait::async_trait;
 use axum::Router;
-use loco_rs::prelude::*;
+use loco_rs::{app::Context, prelude::*};
 use tower::Layer;
 use tower_http::normalize_path::NormalizePathLayer;
 
@@ -28,7 +28,7 @@ impl Initializer for NormalizePathInitializer {
         "normalize-path".to_string()
     }
 
-    async fn after_routes(&self, router: Router, _ctx: &AppContext) -> Result<Router> {
+    async fn after_routes(&self, router: Router, _ctx: &dyn Context) -> Result<Router> {
         let router = NormalizePathLayer::trim_trailing_slash().layer(router);
         let router = Router::new().nest_service("", router);
         Ok(router)
diff --git a/loco-extras/src/initializers/opentelemetry/mod.rs b/loco-extras/src/initializers/opentelemetry/mod.rs
index dc7330fa7..7cd420403 100644
--- a/loco-extras/src/initializers/opentelemetry/mod.rs
+++ b/loco-extras/src/initializers/opentelemetry/mod.rs
@@ -1,7 +1,7 @@
 use axum::{async_trait, Router as AxumRouter};
 use axum_tracing_opentelemetry::middleware::{OtelAxumLayer, OtelInResponseLayer};
 use loco_rs::{
-    app::{AppContext, Initializer},
+    app::{AppContext, Context, Initializer},
     Error, Result,
 };
 
@@ -13,7 +13,7 @@ impl Initializer for OpenTelemetryInitializer {
         "opentelemetry".to_string()
     }
 
-    async fn before_run(&self, _app_context: &AppContext) -> Result<()> {
+    async fn before_run(&self, _app_context: &dyn Context) -> Result<()> {
         match init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers() {
             Ok(_) => Ok(()),
             Err(e) => {
@@ -23,7 +23,7 @@ impl Initializer for OpenTelemetryInitializer {
         }
     }
 
-    async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(&self, router: AxumRouter, _ctx: &dyn Context) -> Result<AxumRouter> {
         let router = router
             .layer(OtelInResponseLayer::default())
             .layer(OtelAxumLayer::default());
diff --git a/loco-extras/src/initializers/prometheus.rs b/loco-extras/src/initializers/prometheus.rs
index c071752a4..c1e664f11 100644
--- a/loco-extras/src/initializers/prometheus.rs
+++ b/loco-extras/src/initializers/prometheus.rs
@@ -1,7 +1,7 @@
 use async_trait::async_trait;
 use axum::Router as AxumRouter;
 use axum_prometheus::PrometheusMetricLayer;
-use loco_rs::prelude::*;
+use loco_rs::{app::Context, prelude::*};
 
 pub struct AxumPrometheusInitializer;
 
@@ -11,7 +11,7 @@ impl Initializer for AxumPrometheusInitializer {
         "axum-prometheus".to_string()
     }
 
-    async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(&self, router: AxumRouter, _ctx: &dyn Context) -> Result<AxumRouter> {
         let (prometheus_layer, metric_handle) = PrometheusMetricLayer::pair();
         let router = router
             .route("/metrics", get(|| async move { metric_handle.render() }))
diff --git a/src/app.rs b/src/app.rs
index 5c0b9c9ba..6f9c28cf5 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -15,18 +15,45 @@ use axum::Router as AxumRouter;
 #[cfg(feature = "channels")]
 use crate::controller::channels::AppChannels;
 use crate::{
-    boot::{BootResult, ServeParams, StartMode},
-    cache::{self},
+    boot::{create_mailer, BootResult, ServeParams, StartMode},
+    cache::{self, Cache},
     config::{self, Config},
     controller::AppRoutes,
     environment::Environment,
     mailer::EmailSender,
-    storage::Storage,
+    storage::{self, Storage},
     task::Tasks,
     worker::{Pool, Processor, RedisConnectionManager},
     Result,
 };
 
+pub trait Context: Send + Sync + 'static {
+    fn environment(&self) -> &Environment;
+    #[cfg(feature = "with-db")]
+    fn db(&self) -> &DatabaseConnection;
+    fn queue(&self) -> &Option<Pool<RedisConnectionManager>>;
+    fn config(&self) -> &Config;
+    fn mailer(&self) -> &Option<EmailSender>;
+    fn storage(&self) -> Arc<Storage>;
+    fn cache(&self) -> Arc<cache::Cache>;
+}
+
+pub trait AppContextTrait: Clone + Default + Context {
+    #[cfg(feature = "with-db")]
+    fn create(
+        environment: Environment,
+        config: Config,
+        db: DatabaseConnection,
+        queue: Option<Pool<RedisConnectionManager>>,
+    ) -> Result<Self>;
+    #[cfg(not(feature = "with-db"))]
+    fn create(
+        environment: Environment,
+        config: Config,
+        queue: Option<Pool<RedisConnectionManager>>,
+    ) -> Result<Self>;
+}
+
 /// Represents the application context for a web server.
 ///
 /// This struct encapsulates various components and configurations required by
@@ -53,6 +80,107 @@ pub struct AppContext {
     pub cache: Arc<cache::Cache>,
 }
 
+impl Default for AppContext {
+    fn default() -> Self {
+        let environment = Environment::Test;
+        #[cfg(feature = "with-db")]
+        let db = DatabaseConnection::default();
+        let config = environment
+            .load()
+            .expect("Failed to load config for test environment");
+
+        AppContext {
+            environment,
+            #[cfg(feature = "with-db")]
+            db,
+            queue: None,
+            storage: Storage::single(storage::drivers::null::new()).into(),
+            cache: Cache::new(cache::drivers::null::new()).into(),
+            config,
+            mailer: None,
+        }
+    }
+}
+
+impl Context for AppContext {
+    fn environment(&self) -> &Environment {
+        &self.environment
+    }
+
+    #[cfg(feature = "with-db")]
+    fn db(&self) -> &DatabaseConnection {
+        &self.db
+    }
+
+    fn queue(&self) -> &Option<Pool<RedisConnectionManager>> {
+        &self.queue
+    }
+
+    fn config(&self) -> &Config {
+        &self.config
+    }
+
+    fn mailer(&self) -> &Option<EmailSender> {
+        &self.mailer
+    }
+
+    fn storage(&self) -> Arc<Storage> {
+        self.storage.clone()
+    }
+
+    fn cache(&self) -> Arc<cache::Cache> {
+        self.cache.clone()
+    }
+}
+
+impl AppContextTrait for AppContext {
+    #[cfg(feature = "with-db")]
+    fn create(
+        environment: Environment,
+        config: Config,
+        db: DatabaseConnection,
+        queue: Option<Pool<RedisConnectionManager>>,
+    ) -> Result<Self> {
+        let mailer = if let Some(cfg) = config.mailer.as_ref() {
+            create_mailer(cfg)?
+        } else {
+            None
+        };
+
+        Ok(AppContext {
+            environment,
+            db,
+            queue,
+            storage: Storage::single(storage::drivers::null::new()).into(),
+            cache: Cache::new(cache::drivers::null::new()).into(),
+            config,
+            mailer,
+        })
+    }
+
+    #[cfg(not(feature = "with-db"))]
+    fn create(
+        environment: Environment,
+        config: Config,
+        queue: Option<Pool<RedisConnectionManager>>,
+    ) -> Result<Self> {
+        let mailer = if let Some(cfg) = config.mailer.as_ref() {
+            create_mailer(cfg)?
+        } else {
+            None
+        };
+
+        Ok(AppContext {
+            environment,
+            queue,
+            storage: Storage::single(storage::drivers::null::new()).into(),
+            cache: Cache::new(cache::drivers::null::new()).into(),
+            config,
+            mailer,
+        })
+    }
+}
+
 /// A trait that defines hooks for customizing and extending the behavior of a
 /// web server application.
 ///
@@ -60,7 +188,7 @@ pub struct AppContext {
 /// the application's routing, worker connections, task registration, and
 /// database actions according to their specific requirements and use cases.
 #[async_trait]
-pub trait Hooks {
+pub trait Hooks<AC: AppContextTrait> {
     /// Defines the composite app version
     #[must_use]
     fn app_version() -> String {
@@ -101,7 +229,7 @@ pub trait Hooks {
     ///
     /// # Errors
     /// Could not boot the application
-    async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult>;
+    async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult<AC>>;
 
     /// Start serving the Axum web application on the specified address and
     /// port.
@@ -141,7 +269,7 @@ pub trait Hooks {
     ///
     /// # Errors
     /// Return an [`Result`] when the router could not be created
-    async fn before_routes(_ctx: &AppContext) -> Result<AxumRouter<AppContext>> {
+    async fn before_routes(_ctx: &AC) -> Result<AxumRouter<AC>> {
         Ok(AxumRouter::new())
     }
 
@@ -151,39 +279,39 @@ pub trait Hooks {
     ///
     /// # Errors
     /// Axum router error
-    async fn after_routes(router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(router: AxumRouter, _ctx: &AC) -> Result<AxumRouter> {
         Ok(router)
     }
 
     /// Provide a list of initializers
     /// An initializer can be used to seamlessly add functionality to your app
     /// or to initialize some aspects of it.
-    async fn initializers(_ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> {
+    async fn initializers(_ctx: &AC) -> Result<Vec<Box<dyn Initializer>>> {
         Ok(vec![])
     }
 
     /// Calling the function before run the app
     /// You can now code some custom loading of resources or other things before
     /// the app runs
-    async fn before_run(_app_context: &AppContext) -> Result<()> {
+    async fn before_run(_app_context: &AC) -> Result<()> {
         Ok(())
     }
 
     /// Defines the application's routing configuration.
-    fn routes(_ctx: &AppContext) -> AppRoutes;
+    fn routes(_ctx: &AC) -> AppRoutes<AC>;
 
     // Provides the options to change Loco [`AppContext`] after initialization.
-    async fn after_context(ctx: AppContext) -> Result<AppContext> {
+    async fn after_context(ctx: AC) -> Result<AC> {
         Ok(ctx)
     }
 
     #[cfg(feature = "channels")]
     /// Register channels endpoints to the application routers
-    fn register_channels(_ctx: &AppContext) -> AppChannels;
+    fn register_channels(_ctx: &AC) -> AppChannels;
 
     /// Connects custom workers to the application using the provided
     /// [`Processor`] and [`AppContext`].
-    fn connect_workers<'a>(p: &'a mut Processor, ctx: &'a AppContext);
+    fn connect_workers<'a>(p: &'a mut Processor, ctx: &'a AC);
 
     /// Registers custom tasks with the provided [`Tasks`] object.
     fn register_tasks(tasks: &mut Tasks);
@@ -212,14 +340,14 @@ pub trait Initializer: Sync + Send {
     /// Occurs after the app's `before_run`.
     /// Use this to for one-time initializations, load caches, perform web
     /// hooks, etc.
-    async fn before_run(&self, _app_context: &AppContext) -> Result<()> {
+    async fn before_run(&self, _app_context: &dyn Context) -> Result<()> {
         Ok(())
     }
 
     /// Occurs after the app's `after_routes`.
     /// Use this to compose additional functionality and wire it into an Axum
     /// Router
-    async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(&self, router: AxumRouter, _ctx: &dyn Context) -> Result<AxumRouter> {
         Ok(router)
     }
 }
diff --git a/src/banner.rs b/src/banner.rs
index a7af381cd..33042a006 100644
--- a/src/banner.rs
+++ b/src/banner.rs
@@ -1,31 +1,37 @@
 use colored::Colorize;
 
-use crate::boot::{BootResult, ServeParams};
+use crate::{
+    app::AppContextTrait,
+    boot::{BootResult, ServeParams},
+};
 
 pub const BANNER: &str = r"
-                      ▄     ▀                     
-                                 ▀  ▄             
-                  ▄       ▀     ▄  ▄ ▄▀           
-                                    ▄ ▀▄▄         
-                        ▄     ▀    ▀  ▀▄▀█▄       
-                                          ▀█▄     
-▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄   ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█    
- ██████  █████   ███ █████   ███ █████   ███ ▀█   
- ██████  █████   ███ █████   ▀▀▀ █████   ███ ▄█▄  
+                      ▄     ▀
+                                 ▀  ▄
+                  ▄       ▀     ▄  ▄ ▄▀
+                                    ▄ ▀▄▄
+                        ▄     ▀    ▀  ▀▄▀█▄
+                                          ▀█▄
+▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄   ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█
+ ██████  █████   ███ █████   ███ █████   ███ ▀█
+ ██████  █████   ███ █████   ▀▀▀ █████   ███ ▄█▄
  ██████  █████   ███ █████       █████   ███ ████▄
  ██████  █████   ███ █████   ▄▄▄ █████   ███ █████
  ██████  █████   ███  ████   ███ █████   ███ ████▀
-   ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀ ██▀  
-       ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀    
+   ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀ ██▀
+       ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
                 https://loco.rs
 ";
 
-pub fn print_banner(boot_result: &BootResult, server_config: &ServeParams) {
+pub fn print_banner<AC: AppContextTrait>(
+    boot_result: &BootResult<AC>,
+    server_config: &ServeParams,
+) {
     let ctx = &boot_result.app_context;
     println!("{BANNER}");
-    let config = &ctx.config;
+    let config = ctx.config();
 
-    println!("environment: {}", ctx.environment.to_string().green());
+    println!("environment: {}", ctx.environment().to_string().green());
 
     #[cfg(feature = "with-db")]
     {
diff --git a/src/boot.rs b/src/boot.rs
index fcf95da75..c20b34279 100644
--- a/src/boot.rs
+++ b/src/boot.rs
@@ -11,9 +11,8 @@ use tracing::{info, trace, warn};
 #[cfg(feature = "with-db")]
 use crate::db;
 use crate::{
-    app::{AppContext, Hooks},
+    app::{AppContextTrait, Hooks},
     banner::print_banner,
-    cache,
     config::{self, Config},
     controller::ListRoutes,
     environment::Environment,
@@ -21,7 +20,6 @@ use crate::{
     mailer::{EmailSender, MailerWorker},
     redis,
     scheduler::{self, Scheduler},
-    storage::{self, Storage},
     task::{self, Tasks},
     worker::{self, AppWorker, Pool, Processor, RedisConnectionManager},
     Result,
@@ -37,9 +35,9 @@ pub enum StartMode {
     /// Pulling job worker and execute them
     WorkerOnly,
 }
-pub struct BootResult {
+pub struct BootResult<AC: AppContextTrait> {
     /// Application Context
-    pub app_context: AppContext,
+    pub app_context: AC,
     /// Web server routes
     pub router: Option<Router>,
     /// worker processor
@@ -64,7 +62,10 @@ pub struct ServeParams {
 /// # Errors
 ///
 /// When could not initialize the application.
-pub async fn start<H: Hooks>(boot: BootResult, server_config: ServeParams) -> Result<()> {
+pub async fn start<AC: AppContextTrait, H: Hooks<AC>>(
+    boot: BootResult<AC>,
+    server_config: ServeParams,
+) -> Result<()> {
     print_banner(&boot, &server_config);
 
     let BootResult {
@@ -103,8 +104,8 @@ async fn process(processor: Processor) -> Result<()> {
 /// # Errors
 ///
 /// When running could not run the task.
-pub async fn run_task<H: Hooks>(
-    app_context: &AppContext,
+pub async fn run_task<AC: AppContextTrait, H: Hooks<AC>>(
+    app_context: &AC,
     task: Option<&String>,
     vars: &task::Vars,
 ) -> Result<()> {
@@ -135,8 +136,8 @@ pub async fn run_task<H: Hooks>(
 /// # Errors
 ///
 /// When running could not run the scheduler.
-pub async fn run_scheduler<H: Hooks>(
-    app_context: &AppContext,
+pub async fn run_scheduler<AC: AppContextTrait, H: Hooks<AC>>(
+    app_context: &AC,
     config: Option<&PathBuf>,
     name: Option<String>,
     tag: Option<String>,
@@ -149,10 +150,10 @@ pub async fn run_scheduler<H: Hooks>(
     let _guard = task_span.enter();
 
     let scheduler = match config {
-        Some(path) => Scheduler::from_config::<H>(path, &app_context.environment)?,
+        Some(path) => Scheduler::from_config::<AC, H>(path, app_context.environment())?,
         None => {
-            if let Some(config) = &app_context.config.scheduler {
-                Scheduler::new::<H>(config, &app_context.environment)?
+            if let Some(config) = &app_context.config().scheduler {
+                Scheduler::new::<AC, H>(config, app_context.environment())?
             } else {
                 return Err(Error::Scheduler(scheduler::Error::Empty));
             }
@@ -193,35 +194,35 @@ pub enum RunDbCommand {
 ///
 /// Return an error when the given command fails. mostly return
 /// [`sea_orm::DbErr`]
-pub async fn run_db<H: Hooks, M: MigratorTrait>(
-    app_context: &AppContext,
+pub async fn run_db<AC: AppContextTrait, H: Hooks<AC>, M: MigratorTrait>(
+    app_context: &AC,
     cmd: RunDbCommand,
 ) -> Result<()> {
     match cmd {
         RunDbCommand::Migrate => {
             tracing::warn!("migrate:");
-            db::migrate::<M>(&app_context.db).await?;
+            db::migrate::<M>(app_context.db()).await?;
         }
         RunDbCommand::Down(steps) => {
             tracing::warn!("down:");
-            db::down::<M>(&app_context.db, steps).await?;
+            db::down::<M>(app_context.db(), steps).await?;
         }
         RunDbCommand::Reset => {
             tracing::warn!("reset:");
-            db::reset::<M>(&app_context.db).await?;
+            db::reset::<M>(app_context.db()).await?;
         }
         RunDbCommand::Status => {
             tracing::warn!("status:");
-            db::status::<M>(&app_context.db).await?;
+            db::status::<M>(app_context.db()).await?;
         }
         RunDbCommand::Entities => {
             tracing::warn!("entities:");
 
-            tracing::warn!("{}", db::entities::<M>(app_context).await?);
+            tracing::warn!("{}", db::entities::<AC, M>(app_context).await?);
         }
         RunDbCommand::Truncate => {
             tracing::warn!("truncate:");
-            H::truncate(&app_context.db).await?;
+            H::truncate(app_context.db()).await?;
         }
     }
     Ok(())
@@ -232,7 +233,9 @@ pub async fn run_db<H: Hooks, M: MigratorTrait>(
 ///
 /// # Errors
 /// When has an error to create DB connection.
-pub async fn create_context<H: Hooks>(environment: &Environment) -> Result<AppContext> {
+pub async fn create_context<AC: AppContextTrait, H: Hooks<AC>>(
+    environment: &Environment,
+) -> Result<AC> {
     let config = environment.load()?;
 
     if config.logger.pretty_backtrace {
@@ -242,26 +245,18 @@ pub async fn create_context<H: Hooks>(environment: &Environment) -> Result<AppCo
              for production. disable with `logger.pretty_backtrace` in your config yaml)"
         );
     }
-    #[cfg(feature = "with-db")]
-    let db = db::connect(&config.database).await?;
 
-    let mailer = if let Some(cfg) = config.mailer.as_ref() {
-        create_mailer(cfg)?
-    } else {
-        None
-    };
+    let queue = connect_redis(&config).await;
 
-    let ctx = AppContext {
-        environment: environment.clone(),
-        #[cfg(feature = "with-db")]
-        db,
-        queue: connect_redis(&config).await,
-        storage: Storage::single(storage::drivers::null::new()).into(),
-        cache: cache::Cache::new(cache::drivers::null::new()).into(),
-        config,
-        mailer,
+    #[cfg(feature = "with-db")]
+    let ctx = {
+        let db = db::connect(&config.database).await?;
+        AC::create(environment.clone(), config, db, queue)?
     };
 
+    #[cfg(not(feature = "with-db"))]
+    let ctx = AC::create(environment.clone(), config, queue)?;
+
     H::after_context(ctx).await
 }
 
@@ -271,39 +266,42 @@ pub async fn create_context<H: Hooks>(environment: &Environment) -> Result<AppCo
 /// # Errors
 ///
 /// When could not create the application
-pub async fn create_app<H: Hooks, M: MigratorTrait>(
+pub async fn create_app<AC: AppContextTrait, H: Hooks<AC>, M: MigratorTrait>(
     mode: StartMode,
     environment: &Environment,
-) -> Result<BootResult> {
-    let app_context = create_context::<H>(environment).await?;
-    db::converge::<H, M>(&app_context.db, &app_context.config.database).await?;
+) -> Result<BootResult<AC>> {
+    let app_context = create_context::<AC, H>(environment).await?;
+    db::converge::<AC, H, M>(app_context.db(), &app_context.config().database).await?;
 
-    if let Some(pool) = &app_context.queue {
-        redis::converge(pool, &app_context.config.queue).await?;
+    if let Some(pool) = app_context.queue() {
+        redis::converge(pool, &app_context.config().queue).await?;
     }
 
-    run_app::<H>(&mode, app_context).await
+    run_app::<AC, H>(&mode, app_context).await
 }
 
 #[cfg(not(feature = "with-db"))]
-pub async fn create_app<H: Hooks>(
+pub async fn create_app<AC: AppContextTrait, H: Hooks<AC>>(
     mode: StartMode,
     environment: &Environment,
 ) -> Result<BootResult> {
-    let app_context = create_context::<H>(environment).await?;
+    let app_context = create_context::<AC, H>(environment).await?;
 
     if let Some(pool) = &app_context.queue {
         redis::converge(pool, &app_context.config.queue).await?;
     }
 
-    run_app::<H>(&mode, app_context).await
+    run_app::<AC, H>(&mode, app_context).await
 }
 
 /// Run the application with the  given mode
 /// # Errors
 ///
 /// When could not create the application
-pub async fn run_app<H: Hooks>(mode: &StartMode, app_context: AppContext) -> Result<BootResult> {
+pub async fn run_app<AC: AppContextTrait, H: Hooks<AC>>(
+    mode: &StartMode,
+    app_context: AC,
+) -> Result<BootResult<AC>> {
     H::before_run(&app_context).await?;
     let initializers = H::initializers(&app_context).await?;
     info!(initializers = ?initializers.iter().map(|init| init.name()).collect::<Vec<_>>().join(","), "initializers loaded");
@@ -326,7 +324,7 @@ pub async fn run_app<H: Hooks>(mode: &StartMode, app_context: AppContext) -> Res
             })
         }
         StartMode::ServerAndWorker => {
-            let processor = create_processor::<H>(&app_context)?;
+            let processor = create_processor::<AC, H>(&app_context)?;
             let app = H::before_routes(&app_context).await?;
             let app = H::routes(&app_context).to_router(app_context.clone(), app)?;
             let mut router = H::after_routes(app, &app_context).await?;
@@ -340,7 +338,7 @@ pub async fn run_app<H: Hooks>(mode: &StartMode, app_context: AppContext) -> Res
             })
         }
         StartMode::WorkerOnly => {
-            let processor = create_processor::<H>(&app_context)?;
+            let processor = create_processor::<AC, H>(&app_context)?;
             Ok(BootResult {
                 app_context,
                 router: None,
@@ -350,13 +348,13 @@ pub async fn run_app<H: Hooks>(mode: &StartMode, app_context: AppContext) -> Res
     }
 }
 /// Creates and configures a [`Processor`] for handling worker tasks.
-fn create_processor<H: Hooks>(app_context: &AppContext) -> Result<Processor> {
-    let queues = worker::get_queues(&app_context.config.workers.queues);
+fn create_processor<AC: AppContextTrait, H: Hooks<AC>>(app_context: &AC) -> Result<Processor> {
+    let queues = worker::get_queues(&app_context.config().workers.queues);
     trace!(
         queues = ?queues,
         "registering queues (merged config and default)"
     );
-    let mut p = if let Some(queue) = &app_context.queue {
+    let mut p = if let Some(queue) = app_context.queue() {
         Processor::new(queue.clone(), queues)
     } else {
         return Err(Error::Message(
@@ -372,13 +370,13 @@ fn create_processor<H: Hooks>(app_context: &AppContext) -> Result<Processor> {
 }
 
 #[must_use]
-pub fn list_endpoints<H: Hooks>(ctx: &AppContext) -> Vec<ListRoutes> {
+pub fn list_endpoints<AC: AppContextTrait, H: Hooks<AC>>(ctx: &AC) -> Vec<ListRoutes<AC>> {
     H::routes(ctx).collect()
 }
 
 /// Initializes an [`EmailSender`] based on the mailer configuration settings
 /// ([`config::Mailer`]).
-fn create_mailer(config: &config::Mailer) -> Result<Option<EmailSender>> {
+pub(crate) fn create_mailer(config: &config::Mailer) -> Result<Option<EmailSender>> {
     if config.stub {
         return Ok(Some(EmailSender::stub()));
     }
diff --git a/src/cli.rs b/src/cli.rs
index 7f20e737e..2732a836c 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -29,7 +29,7 @@ use std::path::PathBuf;
 use clap::{Parser, Subcommand};
 
 use crate::{
-    app::{AppContext, Hooks},
+    app::{AppContextTrait, Hooks},
     boot::{
         create_app, create_context, list_endpoints, run_scheduler, run_task, start, RunDbCommand,
         ServeParams, StartMode,
@@ -366,11 +366,11 @@ where
 /// # Errors
 ///
 /// When could not create app context
-pub async fn playground<H: Hooks>() -> crate::Result<AppContext> {
+pub async fn playground<AC: AppContextTrait, H: Hooks<AC>>() -> crate::Result<AC> {
     let cli = Playground::parse();
     let environment: Environment = cli.environment.unwrap_or_else(resolve_from_env).into();
 
-    let app_context = create_context::<H>(&environment).await?;
+    let app_context = create_context::<AC, H>(&environment).await?;
     Ok(app_context)
 }
 
@@ -399,14 +399,14 @@ pub async fn playground<H: Hooks>() -> crate::Result<AppContext> {
 /// }
 /// ```
 #[cfg(feature = "with-db")]
-pub async fn main<H: Hooks, M: MigratorTrait>() -> crate::Result<()> {
+pub async fn main<AC: AppContextTrait, H: Hooks<AC>, M: MigratorTrait>() -> crate::Result<()> {
     let cli: Cli = Cli::parse();
     let environment: Environment = cli.environment.unwrap_or_else(resolve_from_env).into();
 
     let config = environment.load()?;
 
     if !H::init_logger(&config, &environment)? {
-        logger::init::<H>(&config.logger);
+        logger::init::<AC, H>(&config.logger);
     }
 
     let task_span = create_root_span(&environment);
@@ -427,31 +427,31 @@ pub async fn main<H: Hooks, M: MigratorTrait>() -> crate::Result<()> {
                 StartMode::ServerOnly
             };
 
-            let boot_result = create_app::<H, M>(start_mode, &environment).await?;
+            let boot_result = create_app::<AC, H, M>(start_mode, &environment).await?;
             let serve_params = ServeParams {
-                port: port.map_or(boot_result.app_context.config.server.port, |p| p),
+                port: port.map_or(boot_result.app_context.config().server.port, |p| p),
                 binding: binding
-                    .unwrap_or_else(|| boot_result.app_context.config.server.binding.to_string()),
+                    .unwrap_or_else(|| boot_result.app_context.config().server.binding.to_string()),
             };
-            start::<H>(boot_result, serve_params).await?;
+            start::<AC, H>(boot_result, serve_params).await?;
         }
         #[cfg(feature = "with-db")]
         Commands::Db { command } => {
             if matches!(command, DbCommands::Create) {
                 db::create(&environment.load()?.database.uri).await?;
             } else {
-                let app_context = create_context::<H>(&environment).await?;
-                run_db::<H, M>(&app_context, command.into()).await?;
+                let app_context = create_context::<AC, H>(&environment).await?;
+                run_db::<AC, H, M>(&app_context, command.into()).await?;
             }
         }
         Commands::Routes {} => {
-            let app_context = create_context::<H>(&environment).await?;
-            show_list_endpoints::<H>(&app_context);
+            let app_context = create_context::<AC, H>(&environment).await?;
+            show_list_endpoints::<AC, H>(&app_context);
         }
         Commands::Task { name, params } => {
             let vars = task::Vars::from_cli_args(params);
-            let app_context = create_context::<H>(&environment).await?;
-            run_task::<H>(&app_context, name.as_ref(), &vars).await?;
+            let app_context = create_context::<AC, H>(&environment).await?;
+            run_task::<AC, H>(&app_context, name.as_ref(), &vars).await?;
         }
         Commands::Scheduler {
             name,
@@ -459,11 +459,11 @@ pub async fn main<H: Hooks, M: MigratorTrait>() -> crate::Result<()> {
             tag,
             list,
         } => {
-            let app_context = create_context::<H>(&environment).await?;
-            run_scheduler::<H>(&app_context, config.as_ref(), name, tag, list).await?;
+            let app_context = create_context::<AC, H>(&environment).await?;
+            run_scheduler::<AC, H>(&app_context, config.as_ref(), name, tag, list).await?;
         }
         Commands::Generate { component } => {
-            gen::generate::<H>(component.try_into()?, &config)?;
+            gen::generate::<AC, H>(component.try_into()?, &config)?;
         }
         Commands::Doctor { config: config_arg } => {
             if config_arg {
@@ -490,14 +490,14 @@ pub async fn main<H: Hooks, M: MigratorTrait>() -> crate::Result<()> {
 }
 
 #[cfg(not(feature = "with-db"))]
-pub async fn main<H: Hooks>() -> crate::Result<()> {
+pub async fn main<AC: AppContextTrait, H: Hooks<AC>>() -> crate::Result<()> {
     let cli = Cli::parse();
     let environment: Environment = cli.environment.unwrap_or_else(resolve_from_env).into();
 
     let config = environment.load()?;
 
     if !H::init_logger(&config, &environment)? {
-        logger::init::<H>(&config.logger);
+        logger::init::<AC, H>(&config.logger);
     }
 
     let task_span = create_root_span(&environment);
@@ -518,7 +518,7 @@ pub async fn main<H: Hooks>() -> crate::Result<()> {
                 StartMode::ServerOnly
             };
 
-            let boot_result = create_app::<H>(start_mode, &environment).await?;
+            let boot_result = create_app::<AC, H>(start_mode, &environment).await?;
             let serve_params = ServeParams {
                 port: port.map_or(boot_result.app_context.config.server.port, |p| p),
                 binding: binding.map_or(
@@ -526,15 +526,15 @@ pub async fn main<H: Hooks>() -> crate::Result<()> {
                     |b| b,
                 ),
             };
-            start::<H>(boot_result, serve_params).await?;
+            start::<AC, H>(boot_result, serve_params).await?;
         }
         Commands::Routes {} => {
-            let app_context = create_context::<H>(&environment).await?;
+            let app_context = create_context::<AC, H>(&environment).await?;
             show_list_endpoints::<H>(&app_context)
         }
         Commands::Task { name, params } => {
             let vars = task::Vars::from_cli_args(params);
-            let app_context = create_context::<H>(&environment).await?;
+            let app_context = create_context::<AC, H>(&environment).await?;
             run_task::<H>(&app_context, name.as_ref(), &vars).await?;
         }
         Commands::Scheduler {
@@ -543,11 +543,11 @@ pub async fn main<H: Hooks>() -> crate::Result<()> {
             tag,
             list,
         } => {
-            let app_context = create_context::<H>(&environment).await?;
-            run_scheduler::<H>(&app_context, config.as_ref(), name, tag, list).await?;
+            let app_context = create_context::<AC, H>(&environment).await?;
+            run_scheduler::<AC, H>(&app_context, config.as_ref(), name, tag, list).await?;
         }
         Commands::Generate { component } => {
-            gen::generate::<H>(component.try_into()?, &config)?;
+            gen::generate::<AC, H>(component.try_into()?, &config)?;
         }
         Commands::Version {} => {
             println!("{}", H::app_version(),);
@@ -556,8 +556,8 @@ pub async fn main<H: Hooks>() -> crate::Result<()> {
     Ok(())
 }
 
-fn show_list_endpoints<H: Hooks>(ctx: &AppContext) {
-    let mut routes = list_endpoints::<H>(ctx);
+fn show_list_endpoints<AC: AppContextTrait, H: Hooks<AC>>(ctx: &AC) {
+    let mut routes = list_endpoints::<AC, H>(ctx);
     routes.sort_by(|a, b| a.uri.cmp(&b.uri));
     for router in routes {
         println!("{router}");
diff --git a/src/controller/app_routes.rs b/src/controller/app_routes.rs
index 6d75d64e0..063688e62 100644
--- a/src/controller/app_routes.rs
+++ b/src/controller/app_routes.rs
@@ -29,7 +29,7 @@ use super::{
     routes::Routes,
 };
 use crate::{
-    app::AppContext,
+    app::AppContextTrait,
     config::{self, FallbackConfig},
     controller::middleware::{
         etag::EtagLayer,
@@ -50,20 +50,20 @@ lazy_static! {
 
 /// Represents the routes of the application.
 #[derive(Clone)]
-pub struct AppRoutes {
+pub struct AppRoutes<AC: AppContextTrait> {
     prefix: Option<String>,
-    routes: Vec<Routes>,
+    routes: Vec<Routes<AC>>,
     #[cfg(feature = "channels")]
     channels: Option<AppChannels>,
 }
 
-pub struct ListRoutes {
+pub struct ListRoutes<AC: AppContextTrait> {
     pub uri: String,
     pub actions: Vec<axum::http::Method>,
-    pub method: axum::routing::MethodRouter<AppContext>,
+    pub method: axum::routing::MethodRouter<AC>,
 }
 
-impl fmt::Display for ListRoutes {
+impl<AC: AppContextTrait> fmt::Display for ListRoutes<AC> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         let actions_str = self
             .actions
@@ -76,7 +76,7 @@ impl fmt::Display for ListRoutes {
     }
 }
 
-impl AppRoutes {
+impl<AC: AppContextTrait> AppRoutes<AC> {
     /// Create a new instance with the default routes.
     #[must_use]
     pub fn with_default_routes() -> Self {
@@ -99,7 +99,7 @@ impl AppRoutes {
     }
 
     #[must_use]
-    pub fn collect(&self) -> Vec<ListRoutes> {
+    pub fn collect(&self) -> Vec<ListRoutes<AC>> {
         let base_url_prefix = self
             .get_prefix()
             // add a leading slash forcefully. Axum routes must start with a leading slash.
@@ -146,7 +146,7 @@ impl AppRoutes {
 
     /// Get the routes.
     #[must_use]
-    pub fn get_routes(&self) -> &[Routes] {
+    pub fn get_routes(&self) -> &[Routes<AC>] {
         self.routes.as_ref()
     }
 
@@ -158,9 +158,9 @@ impl AppRoutes {
     /// In the following example you are adding api as a prefix for all routes
     ///
     /// ```rust
-    /// use loco_rs::controller::AppRoutes;
+    /// use loco_rs::{app::AppContext, controller::AppRoutes};
     ///
-    /// AppRoutes::with_default_routes().prefix("api");
+    /// AppRoutes::<AppContext>::with_default_routes().prefix("api");
     /// ```
     #[must_use]
     pub fn prefix(mut self, prefix: &str) -> Self {
@@ -170,14 +170,14 @@ impl AppRoutes {
 
     /// Add a single route.
     #[must_use]
-    pub fn add_route(mut self, route: Routes) -> Self {
+    pub fn add_route(mut self, route: Routes<AC>) -> Self {
         self.routes.push(route);
         self
     }
 
     /// Add multiple routes.
     #[must_use]
-    pub fn add_routes(mut self, mounts: Vec<Routes>) -> Self {
+    pub fn add_routes(mut self, mounts: Vec<Routes<AC>>) -> Self {
         for mount in mounts {
             self.routes.push(mount);
         }
@@ -198,7 +198,7 @@ impl AppRoutes {
     /// Return an [`Result`] when could not convert the router setup to
     /// [`axum::Router`].
     #[allow(clippy::cognitive_complexity)]
-    pub fn to_router(&self, ctx: AppContext, mut app: AXRouter<AppContext>) -> Result<AXRouter> {
+    pub fn to_router(&self, ctx: AC, mut app: AXRouter<AC>) -> Result<AXRouter> {
         //
         // IMPORTANT: middleware ordering in this function is opposite to what you
         // intuitively may think. when using `app.layer` to add individual middleware,
@@ -249,72 +249,72 @@ impl AppRoutes {
             }
         }
 
-        if let Some(catch_panic) = &ctx.config.server.middlewares.catch_panic {
+        if let Some(catch_panic) = &ctx.config().server.middlewares.catch_panic {
             if catch_panic.enable {
                 app = Self::add_catch_panic(app);
             }
         }
 
-        if let Some(etag) = &ctx.config.server.middlewares.etag {
+        if let Some(etag) = &ctx.config().server.middlewares.etag {
             if etag.enable {
                 app = Self::add_etag_middleware(app);
             }
         }
 
-        if let Some(remote_ip) = &ctx.config.server.middlewares.remote_ip {
+        if let Some(remote_ip) = &ctx.config().server.middlewares.remote_ip {
             if remote_ip.enable {
                 app = Self::add_remote_ip_middleware(app, remote_ip)?;
             }
         }
 
-        if let Some(compression) = &ctx.config.server.middlewares.compression {
+        if let Some(compression) = &ctx.config().server.middlewares.compression {
             if compression.enable {
                 app = Self::add_compression_middleware(app);
             }
         }
 
-        if let Some(timeout_request) = &ctx.config.server.middlewares.timeout_request {
+        if let Some(timeout_request) = &ctx.config().server.middlewares.timeout_request {
             if timeout_request.enable {
                 app = Self::add_timeout_middleware(app, timeout_request);
             }
         }
 
-        if let Some(cors) = &ctx.config.server.middlewares.cors {
+        if let Some(cors) = &ctx.config().server.middlewares.cors {
             if cors.enable {
                 app = app.layer(cors_middleware(cors)?);
             }
         }
 
-        if let Some(limit) = &ctx.config.server.middlewares.limit_payload {
+        if let Some(limit) = &ctx.config().server.middlewares.limit_payload {
             if limit.enable {
                 app = Self::add_limit_payload_middleware(app, limit)?;
             }
         }
 
-        if let Some(logger) = &ctx.config.server.middlewares.logger {
+        if let Some(logger) = &ctx.config().server.middlewares.logger {
             if logger.enable {
-                app = Self::add_logger_middleware(app, &ctx.environment);
+                app = Self::add_logger_middleware(app, ctx.environment());
             }
         }
 
-        if let Some(static_assets) = &ctx.config.server.middlewares.static_assets {
+        if let Some(static_assets) = &ctx.config().server.middlewares.static_assets {
             if static_assets.enable {
                 app = Self::add_static_asset_middleware(app, static_assets)?;
             }
         }
 
-        if let Some(secure_headers) = &ctx.config.server.middlewares.secure_headers {
+        if let Some(secure_headers) = &ctx.config().server.middlewares.secure_headers {
             app = app.layer(SecureHeaders::new(secure_headers)?);
             tracing::info!("[Middleware] +secure headers");
         }
 
-        if let Some(fallback) = &ctx.config.server.middlewares.fallback {
+        if let Some(fallback) = &ctx.config().server.middlewares.fallback {
             if fallback.enable {
                 app = Self::add_fallback(app, fallback)?;
             }
         }
 
-        app = Self::add_powered_by_header(app, &ctx.config.server);
+        app = Self::add_powered_by_header(app, &ctx.config().server);
 
         app = Self::add_request_id_middleware(app);
 
@@ -322,10 +322,7 @@ impl AppRoutes {
         Ok(router)
     }
 
-    fn add_fallback(
-        app: AXRouter<AppContext>,
-        fallback: &FallbackConfig,
-    ) -> Result<AXRouter<AppContext>> {
+    fn add_fallback(app: AXRouter<AC>, fallback: &FallbackConfig) -> Result<AXRouter<AC>> {
         let app = if let Some(path) = &fallback.file {
             app.fallback_service(ServeFile::new(path))
         } else if let Some(not_found) = &fallback.not_found {
@@ -352,16 +349,16 @@ impl AppRoutes {
         Ok(app)
     }
 
-    fn add_request_id_middleware(app: AXRouter<AppContext>) -> AXRouter<AppContext> {
+    fn add_request_id_middleware(app: AXRouter<AC>) -> AXRouter<AC> {
         let app = app.layer(axum::middleware::from_fn(request_id_middleware));
         tracing::info!("[Middleware] +request id");
         app
     }
 
     fn add_static_asset_middleware(
-        app: AXRouter<AppContext>,
+        app: AXRouter<AC>,
         config: &config::StaticAssetsMiddleware,
-    ) -> Result<AXRouter<AppContext>> {
+    ) -> Result<AXRouter<AC>> {
         if config.must_exist
             && (!PathBuf::from(&config.folder.path).exists()
                 || !PathBuf::from(&config.fallback).exists())
@@ -386,35 +383,35 @@ impl AppRoutes {
         ))
     }
 
-    fn add_compression_middleware(app: AXRouter<AppContext>) -> AXRouter<AppContext> {
+    fn add_compression_middleware(app: AXRouter<AC>) -> AXRouter<AC> {
         let app = app.layer(CompressionLayer::new());
         tracing::info!("[Middleware] +compression");
         app
     }
 
-    fn add_etag_middleware(app: AXRouter<AppContext>) -> AXRouter<AppContext> {
+    fn add_etag_middleware(app: AXRouter<AC>) -> AXRouter<AC> {
         let app = app.layer(EtagLayer::new());
         tracing::info!("[Middleware] +etag");
         app
     }
 
     fn add_remote_ip_middleware(
-        app: AXRouter<AppContext>,
+        app: AXRouter<AC>,
         config: &RemoteIPConfig,
-    ) -> Result<AXRouter<AppContext>> {
+    ) -> Result<AXRouter<AC>> {
         let app = app.layer(RemoteIPLayer::new(config)?);
         tracing::info!("[Middleware] +remote IP");
         Ok(app)
     }
 
-    fn add_catch_panic(app: AXRouter<AppContext>) -> AXRouter<AppContext> {
+    fn add_catch_panic(app: AXRouter<AC>) -> AXRouter<AC> {
         app.layer(CatchPanicLayer::custom(handle_panic))
     }
 
     fn add_limit_payload_middleware(
-        app: AXRouter<AppContext>,
+        app: AXRouter<AC>,
         limit: &config::LimitPayloadMiddleware,
-    ) -> Result<AXRouter<AppContext>> {
+    ) -> Result<AXRouter<AC>> {
         let app = app.layer(axum::extract::DefaultBodyLimit::max(
             byte_unit::Byte::from_str(&limit.body_limit)
                 .map_err(Box::from)?
@@ -424,10 +421,7 @@ impl AppRoutes {
 
         Ok(app)
     }
-    fn add_logger_middleware(
-        app: AXRouter<AppContext>,
-        environment: &Environment,
-    ) -> AXRouter<AppContext> {
+    fn add_logger_middleware(app: AXRouter<AC>, environment: &Environment) -> AXRouter<AC> {
         let app = app
             .layer(
                 TraceLayer::new_for_http().make_span_with(|request: &http::Request<_>| {
@@ -464,19 +458,16 @@ impl AppRoutes {
     }
 
     fn add_timeout_middleware(
-        app: AXRouter<AppContext>,
+        app: AXRouter<AC>,
         config: &config::TimeoutRequestMiddleware,
-    ) -> AXRouter<AppContext> {
+    ) -> AXRouter<AC> {
         let app = app.layer(TimeoutLayer::new(Duration::from_millis(config.timeout)));
 
         tracing::info!("[Middleware] +timeout");
         app
     }
 
-    fn add_powered_by_header(
-        app: AXRouter<AppContext>,
-        config: &config::Server,
-    ) -> AXRouter<AppContext> {
+    fn add_powered_by_header(app: AXRouter<AC>, config: &config::Server) -> AXRouter<AC> {
         let ident_value = config.ident.as_ref().map_or_else(
             || Some(DEFAULT_IDENT_HEADER_VALUE.clone()),
             |ident| {
@@ -538,7 +529,7 @@ mod tests {
 
     #[test]
     fn can_load_app_route_from_default() {
-        for route in AppRoutes::with_default_routes().collect() {
+        for route in AppRoutes::<AppContext>::with_default_routes().collect() {
             assert_debug_snapshot!(
                 format!("[{}]", route.uri.replace('/', "[slash]")),
                 format!("{:?} {}", route.actions, route.uri)
@@ -548,12 +539,12 @@ mod tests {
 
     #[test]
     fn can_load_empty_app_routes() {
-        assert_eq!(AppRoutes::empty().collect().len(), 0);
+        assert_eq!(AppRoutes::<AppContext>::empty().collect().len(), 0);
     }
 
     #[test]
     fn can_load_routes() {
-        let router_without_prefix = Routes::new().add("/", get(action));
+        let router_without_prefix = Routes::<AppContext>::new().add("/", get(action));
         let normalizer = Routes::new()
             .prefix("/normalizer")
             .add("no-slash", get(action))
@@ -581,7 +572,7 @@ mod tests {
 
     #[test]
     fn can_load_routes_with_root_prefix() {
-        let router_without_prefix = Routes::new()
+        let router_without_prefix = Routes::<AppContext>::new()
             .add("/loco", get(action))
             .add("loco-rs", get(action));
 
diff --git a/src/controller/describe.rs b/src/controller/describe.rs
index 43aec5b29..25927ee4b 100644
--- a/src/controller/describe.rs
+++ b/src/controller/describe.rs
@@ -2,7 +2,7 @@ use axum::{http, routing::MethodRouter};
 use lazy_static::lazy_static;
 use regex::Regex;
 
-use crate::app::AppContext;
+use crate::app::AppContextTrait;
 
 lazy_static! {
     static ref DESCRIBE_METHOD_ACTION: Regex = Regex::new(r"\b(\w+):\s*BoxedHandler\b").unwrap();
@@ -13,7 +13,7 @@ lazy_static! {
 /// Currently axum not exposed the action type of the router. for hold extra
 /// information about routers we need to convert the `method` to string and
 /// capture the details
-pub fn method_action(method: &MethodRouter<AppContext>) -> Vec<http::Method> {
+pub fn method_action<AC: AppContextTrait>(method: &MethodRouter<AC>) -> Vec<http::Method> {
     let method_str = format!("{method:?}");
 
     DESCRIBE_METHOD_ACTION
diff --git a/src/controller/health.rs b/src/controller/health.rs
index 3829a3d4c..b044bf2e1 100644
--- a/src/controller/health.rs
+++ b/src/controller/health.rs
@@ -6,7 +6,7 @@ use axum::{extract::State, response::Response, routing::get};
 use serde::Serialize;
 
 use super::{format, routes::Routes};
-use crate::{app::AppContext, redis, Result};
+use crate::{app::AppContextTrait, redis, Result};
 
 /// Represents the health status of the application.
 #[derive(Serialize)]
@@ -16,15 +16,15 @@ struct Health {
 
 /// Check the healthiness of the application bt ping to the redis and the DB to
 /// insure that connection
-async fn health(State(ctx): State<AppContext>) -> Result<Response> {
-    let mut is_ok = match ctx.db.ping().await {
+async fn health<AC: AppContextTrait>(State(ctx): State<AC>) -> Result<Response> {
+    let mut is_ok = match ctx.db().ping().await {
         Ok(()) => true,
         Err(error) => {
             tracing::error!(err.msg = %error, err.detail = ?error, "health_db_ping_error");
             false
         }
     };
-    if let Some(pool) = ctx.queue {
+    if let Some(pool) = ctx.queue() {
         if let Err(error) = redis::ping(&pool).await {
             tracing::error!(err.msg = %error, err.detail = ?error, "health_redis_ping_error");
             is_ok = false;
@@ -34,6 +34,6 @@ async fn health(State(ctx): State<AppContext>) -> Result<Response> {
 }
 
 /// Defines and returns the health-related routes.
-pub fn routes() -> Routes {
-    Routes::new().add("/_health", get(health))
+pub fn routes<AC: AppContextTrait>() -> Routes<AC> {
+    Routes::new().add("/_health", get(health::<AC>))
 }
diff --git a/src/controller/mod.rs b/src/controller/mod.rs
index be8e60cac..9e7713dee 100644
--- a/src/controller/mod.rs
+++ b/src/controller/mod.rs
@@ -34,21 +34,21 @@
 //! }
 //!
 //! #[async_trait]
-//! impl Hooks for App {
+//! impl Hooks<AppContext> for App {
 //!
 //!    fn app_name() -> &'static str {
 //!        env!("CARGO_CRATE_NAME")
 //!    }
 //!
-//!     fn routes(ctx: &AppContext) -> AppRoutes {
+//!     fn routes(ctx: &AppContext) -> AppRoutes<AppContext> {
 //!         AppRoutes::with_default_routes()
 //!             // .add_route(controllers::notes::routes())
 //!     }
-//!     
-//!     async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult>{
-//!          create_app::<Self, Migrator>(mode, environment).await
+//!
+//!     async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult<AppContext>>{
+//!          create_app::<AppContext, Self, Migrator>(mode, environment).await
 //!     }
-//!     
+//!
 //!     #[cfg(feature = "channels")]
 //!    /// Only when `channels` feature is enabled
 //!    fn register_channels(_ctx: &AppContext) -> AppChannels {
diff --git a/src/controller/ping.rs b/src/controller/ping.rs
index 078e32cb0..bff2889c5 100644
--- a/src/controller/ping.rs
+++ b/src/controller/ping.rs
@@ -6,7 +6,7 @@ use axum::{response::Response, routing::get};
 use serde::Serialize;
 
 use super::{format, routes::Routes};
-use crate::Result;
+use crate::{app::AppContextTrait, Result};
 
 /// Represents the health status of the application.
 #[derive(Serialize)]
@@ -20,6 +20,6 @@ async fn ping() -> Result<Response> {
 }
 
 /// Defines and returns the health-related routes.
-pub fn routes() -> Routes {
+pub fn routes<AC: AppContextTrait>() -> Routes<AC> {
     Routes::new().add("/_ping", get(ping))
 }
diff --git a/src/controller/routes.rs b/src/controller/routes.rs
index 32da70207..d58e02144 100644
--- a/src/controller/routes.rs
+++ b/src/controller/routes.rs
@@ -4,22 +4,22 @@ use axum::{extract::Request, response::IntoResponse, routing::Route};
 use tower::{Layer, Service};
 
 use super::describe;
-use crate::app::AppContext;
+use crate::app::AppContextTrait;
 #[derive(Clone, Default)]
-pub struct Routes {
+pub struct Routes<AC: AppContextTrait> {
     pub prefix: Option<String>,
-    pub handlers: Vec<Handler>,
+    pub handlers: Vec<Handler<AC>>,
     // pub version: Option<String>,
 }
 
 #[derive(Clone, Default)]
-pub struct Handler {
+pub struct Handler<AC: AppContextTrait> {
     pub uri: String,
-    pub method: axum::routing::MethodRouter<AppContext>,
+    pub method: axum::routing::MethodRouter<AC>,
     pub actions: Vec<axum::http::Method>,
 }
 
-impl Routes {
+impl<AC: AppContextTrait> Routes<AC> {
     /// Creates a new [`Routes`] instance with default settings.
     #[must_use]
     pub fn new() -> Self {
@@ -46,8 +46,8 @@ impl Routes {
     /// async fn ping() -> Result<Response> {
     ///     format::json(Health { ok: true })
     /// }
-    /// Routes::at("status").add("/_ping", get(ping));
-    ///    
+    /// Routes::<AppContext>::at("status").add("/_ping", get(ping));
+    ///
     /// ````
     #[must_use]
     pub fn at(prefix: &str) -> Self {
@@ -75,10 +75,10 @@ impl Routes {
     /// async fn ping() -> Result<Response> {
     ///     format::json(Health { ok: true })
     /// }
-    /// Routes::new().add("/_ping", get(ping));
+    /// Routes::<AppContext>::new().add("/_ping", get(ping));
     /// ````
     #[must_use]
-    pub fn add(mut self, uri: &str, method: axum::routing::MethodRouter<AppContext>) -> Self {
+    pub fn add(mut self, uri: &str, method: axum::routing::MethodRouter<AC>) -> Self {
         describe::method_action(&method);
         self.handlers.push(Handler {
             uri: uri.to_owned(),
@@ -108,7 +108,7 @@ impl Routes {
     /// async fn ping() -> Result<Response> {
     ///     format::json(Health { ok: true })
     /// }
-    /// Routes::new().prefix("status").add("/_ping", get(ping));
+    /// Routes::<AppContext>::new().prefix("status").add("/_ping", get(ping));
     /// ````
     #[must_use]
     pub fn prefix(mut self, uri: &str) -> Self {
@@ -130,7 +130,7 @@ impl Routes {
     /// async fn ping() -> Result<Response> {
     ///     format::json("Ok")
     /// }
-    /// Routes::new().prefix("status").add("/_ping", get(ping)).layer(TimeoutLayer::new(std::time::Duration::from_secs(5)));
+    /// Routes::<AppContext>::new().prefix("status").add("/_ping", get(ping)).layer(TimeoutLayer::new(std::time::Duration::from_secs(5)));
     /// ```
     #[allow(clippy::needless_pass_by_value)]
     #[must_use]
diff --git a/src/db.rs b/src/db.rs
index c204b16fb..fde75c2dc 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -18,7 +18,7 @@ use tracing::info;
 
 use super::Result as AppResult;
 use crate::{
-    app::{AppContext, Hooks},
+    app::{AppContextTrait, Hooks},
     config, doctor,
     errors::Error,
 };
@@ -102,7 +102,7 @@ pub async fn verify_access(db: &DatabaseConnection) -> AppResult<()> {
 ///  an `AppResult`, which is an alias for `Result<(), AppError>`. It may
 /// return an `AppError` variant representing different database operation
 /// failures.
-pub async fn converge<H: Hooks, M: MigratorTrait>(
+pub async fn converge<AC: AppContextTrait, H: Hooks<AC>, M: MigratorTrait>(
     db: &DatabaseConnection,
     config: &config::Database,
 ) -> AppResult<()> {
@@ -251,9 +251,9 @@ where
 /// # Errors
 ///
 /// Returns a [`AppResult`] if an error occurs during generate model entity.
-pub async fn entities<M: MigratorTrait>(ctx: &AppContext) -> AppResult<String> {
+pub async fn entities<AC: AppContextTrait, M: MigratorTrait>(ctx: &AC) -> AppResult<String> {
     doctor::check_seaorm_cli().to_result()?;
-    doctor::check_db(&ctx.config.database).await.to_result()?;
+    doctor::check_db(&ctx.config().database).await.to_result()?;
 
     let out = cmd!(
         "sea-orm-cli",
@@ -264,7 +264,7 @@ pub async fn entities<M: MigratorTrait>(ctx: &AppContext) -> AppResult<String> {
         "--output-dir",
         "src/models/_entities",
         "--database-url",
-        &ctx.config.database.uri
+        &ctx.config().database.uri
     )
     .stderr_to_stdout()
     .run()
@@ -361,7 +361,10 @@ where
 /// # Errors
 ///
 /// when seed process is fails
-pub async fn run_app_seed<H: Hooks>(db: &DatabaseConnection, path: &Path) -> AppResult<()> {
+pub async fn run_app_seed<AC: AppContextTrait, H: Hooks<AC>>(
+    db: &DatabaseConnection,
+    path: &Path,
+) -> AppResult<()> {
     H::seed(db, path).await
 }
 
diff --git a/src/gen/controller.rs b/src/gen/controller.rs
index b2947be4f..acd4e8ccb 100644
--- a/src/gen/controller.rs
+++ b/src/gen/controller.rs
@@ -1,7 +1,10 @@
 use rrgen::RRgen;
 use serde_json::json;
 
-use crate::{app::Hooks, gen};
+use crate::{
+    app::{AppContextTrait, Hooks},
+    gen,
+};
 
 const API_CONTROLLER_CONTROLLER_T: &str = include_str!("templates/controller/api/controller.t");
 const API_CONTROLLER_TEST_T: &str = include_str!("templates/controller/api/test.t");
@@ -15,7 +18,7 @@ const HTML_VIEW_T: &str = include_str!("templates/controller/html/view.t");
 use super::collect_messages;
 use crate::Result;
 
-pub fn generate<H: Hooks>(
+pub fn generate<AC: AppContextTrait, H: Hooks<AC>>(
     rrgen: &RRgen,
     name: &str,
     actions: &[String],
diff --git a/src/gen/mod.rs b/src/gen/mod.rs
index e5a68ab44..805b7a0fc 100644
--- a/src/gen/mod.rs
+++ b/src/gen/mod.rs
@@ -14,7 +14,11 @@ mod model;
 mod scaffold;
 use std::str::FromStr;
 
-use crate::{app::Hooks, config::Config, errors, Result};
+use crate::{
+    app::{AppContextTrait, Hooks},
+    config::Config,
+    errors, Result,
+};
 
 const CONTROLLER_T: &str = include_str!("templates/controller.t");
 const CONTROLLER_TEST_T: &str = include_str!("templates/request_test.t");
@@ -178,7 +182,10 @@ pub enum Component {
     Deployment {},
 }
 #[allow(clippy::too_many_lines)]
-pub fn generate<H: Hooks>(component: Component, config: &Config) -> Result<()> {
+pub fn generate<AC: AppContextTrait, H: Hooks<AC>>(
+    component: Component,
+    config: &Config,
+) -> Result<()> {
     let rrgen = RRgen::default();
     match component {
         #[cfg(feature = "with-db")]
@@ -190,14 +197,14 @@ pub fn generate<H: Hooks>(component: Component, config: &Config) -> Result<()> {
         } => {
             println!(
                 "{}",
-                model::generate::<H>(&rrgen, &name, link, migration_only, &fields)?
+                model::generate::<AC, H>(&rrgen, &name, link, migration_only, &fields)?
             );
         }
         #[cfg(feature = "with-db")]
         Component::Scaffold { name, fields, kind } => {
             println!(
                 "{}",
-                scaffold::generate::<H>(&rrgen, &name, &fields, &kind)?
+                scaffold::generate::<AC, H>(&rrgen, &name, &fields, &kind)?
             );
         }
         #[cfg(feature = "with-db")]
@@ -212,7 +219,7 @@ pub fn generate<H: Hooks>(component: Component, config: &Config) -> Result<()> {
         } => {
             println!(
                 "{}",
-                controller::generate::<H>(&rrgen, &name, &actions, &kind)?
+                controller::generate::<AC, H>(&rrgen, &name, &actions, &kind)?
             );
         }
         Component::Task { name } => {
diff --git a/src/gen/model.rs b/src/gen/model.rs
index e7d6fa08f..25c485444 100644
--- a/src/gen/model.rs
+++ b/src/gen/model.rs
@@ -5,7 +5,11 @@ use duct::cmd;
 use rrgen::RRgen;
 use serde_json::json;
 
-use crate::{app::Hooks, errors::Error, Result};
+use crate::{
+    app::{AppContextTrait, Hooks},
+    errors::Error,
+    Result,
+};
 
 const MODEL_T: &str = include_str!("templates/model.t");
 const MODEL_TEST_T: &str = include_str!("templates/model_test.t");
@@ -17,7 +21,7 @@ use super::{collect_messages, MAPPINGS};
 /// generated by the Loco app and should be given
 pub const IGNORE_FIELDS: &[&str] = &["created_at", "updated_at", "create_at", "update_at"];
 
-pub fn generate<H: Hooks>(
+pub fn generate<AC: AppContextTrait, H: Hooks<AC>>(
     rrgen: &RRgen,
     name: &str,
     is_link: bool,
diff --git a/src/gen/scaffold.rs b/src/gen/scaffold.rs
index e1e5b5093..a034d13d5 100644
--- a/src/gen/scaffold.rs
+++ b/src/gen/scaffold.rs
@@ -1,7 +1,10 @@
 use rrgen::RRgen;
 use serde_json::json;
 
-use crate::{app::Hooks, gen};
+use crate::{
+    app::{AppContextTrait, Hooks},
+    gen,
+};
 
 const API_CONTROLLER_SCAFFOLD_T: &str = include_str!("templates/scaffold/api/controller.t");
 const API_CONTROLLER_TEST_T: &str = include_str!("templates/scaffold/api/test.t");
@@ -25,7 +28,7 @@ const HTML_VIEW_LIST_SCAFFOLD_T: &str = include_str!("templates/scaffold/html/vi
 use super::{collect_messages, model, MAPPINGS};
 use crate::{errors::Error, Result};
 
-pub fn generate<H: Hooks>(
+pub fn generate<AC: AppContextTrait, H: Hooks<AC>>(
     rrgen: &RRgen,
     name: &str,
     fields: &[(String, String)],
@@ -34,7 +37,7 @@ pub fn generate<H: Hooks>(
     // - scaffold is never a link table
     // - never run with migration_only, because the controllers will refer to the
     //   models. the models only arrive after migration and entities sync.
-    let model_messages = model::generate::<H>(rrgen, name, false, false, fields)?;
+    let model_messages = model::generate::<AC, H>(rrgen, name, false, false, fields)?;
 
     let mut columns = Vec::new();
     for (fname, ftype) in fields {
diff --git a/src/logger.rs b/src/logger.rs
index 44f2d05b5..ca5b74af6 100644
--- a/src/logger.rs
+++ b/src/logger.rs
@@ -10,7 +10,10 @@ use tracing_subscriber::layer::SubscriberExt;
 use tracing_subscriber::util::SubscriberInitExt;
 use tracing_subscriber::{fmt, EnvFilter, Layer, Registry};
 
-use crate::{app::Hooks, config};
+use crate::{
+    app::{AppContextTrait, Hooks},
+    config,
+};
 
 // Define an enumeration for log levels
 #[derive(Debug, Default, Clone, Deserialize, Serialize)]
@@ -91,7 +94,7 @@ static NONBLOCKING_WORK_GUARD_KEEP: OnceLock<WorkerGuard> = OnceLock::new();
 ///    use via PR)
 /// 3. regardless of (1) and (2) operators in production, or elsewhere can
 ///    always use `RUST_LOG` to quickly diagnose a service
-pub fn init<H: Hooks>(config: &config::Logger) {
+pub fn init<AC: AppContextTrait, H: Hooks<AC>>(config: &config::Logger) {
     let mut layers: Vec<Box<dyn Layer<Registry> + Sync + Send>> = Vec::new();
 
     if let Some(file_appender_config) = config.file_appender.as_ref() {
@@ -153,7 +156,7 @@ pub fn init<H: Hooks>(config: &config::Logger) {
     }
 
     if !layers.is_empty() {
-        let env_filter = init_env_filter::<H>(config.override_filter.as_ref(), &config.level);
+        let env_filter = init_env_filter::<AC, H>(config.override_filter.as_ref(), &config.level);
         tracing_subscriber::registry()
             .with(layers)
             .with(env_filter)
@@ -161,7 +164,10 @@ pub fn init<H: Hooks>(config: &config::Logger) {
     }
 }
 
-fn init_env_filter<H: Hooks>(override_filter: Option<&String>, level: &LogLevel) -> EnvFilter {
+fn init_env_filter<AC: AppContextTrait, H: Hooks<AC>>(
+    override_filter: Option<&String>,
+    level: &LogLevel,
+) -> EnvFilter {
     EnvFilter::try_from_default_env()
         .or_else(|_| {
             // user wanted a specific filter, don't care about our internal whitelist
diff --git a/src/mailer/mod.rs b/src/mailer/mod.rs
index ab2244652..83e5a8314 100644
--- a/src/mailer/mod.rs
+++ b/src/mailer/mod.rs
@@ -11,6 +11,8 @@ use include_dir::Dir;
 use serde::{Deserialize, Serialize};
 use sidekiq::Worker;
 
+use crate::app::AppContextTrait;
+
 use self::template::Template;
 use super::{app::AppContext, worker::AppWorker, Result};
 
@@ -108,20 +110,20 @@ pub trait Mailer {
 /// The [`MailerWorker`] struct represents a worker responsible for asynchronous
 /// email processing.
 #[allow(clippy::module_name_repetitions)]
-pub struct MailerWorker {
-    pub ctx: AppContext,
+pub struct MailerWorker<AC: AppContextTrait> {
+    pub ctx: AC,
 }
 
 /// Implementation of the `AppWorker` trait for the [`MailerWorker`].
-impl AppWorker<Email> for MailerWorker {
-    fn build(ctx: &AppContext) -> Self {
+impl<AC: AppContextTrait> AppWorker<AC, Email> for MailerWorker<AC> {
+    fn build(ctx: &AC) -> Self {
         Self { ctx: ctx.clone() }
     }
 }
 
 /// Implementation of the [`Worker`] trait for the [`MailerWorker`].
 #[async_trait]
-impl Worker<Email> for MailerWorker {
+impl<AC: AppContextTrait> Worker<Email> for MailerWorker<AC> {
     /// Returns options for the mailer worker, specifying the queue to process.
     fn opts() -> sidekiq::WorkerOpts<Email, Self> {
         sidekiq::WorkerOpts::new().queue("mailer")
@@ -130,7 +132,7 @@ impl Worker<Email> for MailerWorker {
     /// Performs the email sending operation using the provided [`AppContext`]
     /// and email details.
     async fn perform(&self, email: Email) -> sidekiq::Result<()> {
-        if let Some(mailer) = &self.ctx.mailer {
+        if let Some(mailer) = self.ctx.mailer() {
             Ok(mailer.mail(&email).await.map_err(Box::from)?)
         } else {
             Err(sidekiq::Error::Message(
diff --git a/src/scheduler.rs b/src/scheduler.rs
index f98909f3c..7db31a3e0 100644
--- a/src/scheduler.rs
+++ b/src/scheduler.rs
@@ -10,7 +10,11 @@ use std::{
     time::Instant,
 };
 
-use crate::{app::Hooks, environment::Environment, task::Tasks};
+use crate::{
+    app::{AppContextTrait, Hooks},
+    environment::Environment,
+    task::Tasks,
+};
 
 use tokio_cron_scheduler::{JobScheduler, JobSchedulerError};
 
@@ -203,7 +207,10 @@ impl Scheduler {
     /// # Errors
     ///
     /// When could not parse the given file content into a [`Config`] struct.
-    pub fn from_config<H: Hooks>(config: &Path, environment: &Environment) -> Result<Self> {
+    pub fn from_config<AC: AppContextTrait, H: Hooks<AC>>(
+        config: &Path,
+        environment: &Environment,
+    ) -> Result<Self> {
         let config_str =
             std::fs::read_to_string(config).map_err(|error| Error::ConfigNotFound {
                 path: config.to_path_buf(),
@@ -213,7 +220,7 @@ impl Scheduler {
         let config: Config = serde_yaml::from_str(&config_str)
             .map_err(|error| Error::InvalidConfigSchema { error })?;
 
-        Self::new::<H>(&config, environment)
+        Self::new::<AC, H>(&config, environment)
     }
 
     /// Creates a new scheduler instance from the provided configuration data.
@@ -224,7 +231,10 @@ impl Scheduler {
     /// # Errors
     ///
     /// When there is not job in the given config
-    pub fn new<H: Hooks>(data: &Config, environment: &Environment) -> Result<Self> {
+    pub fn new<AC: AppContextTrait, H: Hooks<AC>>(
+        data: &Config,
+        environment: &Environment,
+    ) -> Result<Self> {
         let mut tasks = Tasks::default();
         H::register_tasks(&mut tasks);
 
@@ -352,7 +362,7 @@ impl Scheduler {
 mod tests {
 
     use super::*;
-    use crate::tests_cfg;
+    use crate::{app::AppContext, tests_cfg};
     use insta::assert_debug_snapshot;
 
     use rstest::rstest;
@@ -365,7 +375,10 @@ mod tests {
             .join("scheduler")
             .join("scheduler.yaml");
 
-        Scheduler::from_config::<AppHook>(&scheduler_config_path, &Environment::Development)
+        Scheduler::from_config::<AppContext, AppHook>(
+            &scheduler_config_path,
+            &Environment::Development,
+        )
     }
 
     #[test]
@@ -382,7 +395,7 @@ mod tests {
     #[tokio::test]
     pub async fn can_load_from_env_config() {
         let app_context = tests_cfg::app::get_app_context().await;
-        let scheduler = Scheduler::new::<AppHook>(
+        let scheduler = Scheduler::new::<AppContext, AppHook>(
             &app_context.config.scheduler.unwrap(),
             &Environment::Development,
         );
diff --git a/src/task.rs b/src/task.rs
index 3da449ee3..faecb736d 100644
--- a/src/task.rs
+++ b/src/task.rs
@@ -6,7 +6,11 @@ use std::collections::BTreeMap;
 
 use async_trait::async_trait;
 
-use crate::{app::AppContext, errors::Error, Result};
+use crate::{
+    app::{AppContextTrait, Context},
+    errors::Error,
+    Result,
+};
 
 /// Struct representing a collection of task arguments.
 #[derive(Default, Debug)]
@@ -75,7 +79,7 @@ pub trait Task: Send + Sync {
     /// Get information about the task.
     fn task(&self) -> TaskInfo;
     /// Execute the task with the provided application context and variables.
-    async fn run(&self, app_context: &AppContext, vars: &Vars) -> Result<()>;
+    async fn run(&self, app_context: &dyn Context, vars: &Vars) -> Result<()>;
 }
 
 /// Managing and running tasks.
@@ -106,7 +110,12 @@ impl Tasks {
     ///
     /// Returns a [`Result`] if an task finished with error. mostly if the given
     /// task is not found or an error to run the task.s
-    pub async fn run(&self, app_context: &AppContext, task: &str, vars: &Vars) -> Result<()> {
+    pub async fn run<AC: AppContextTrait>(
+        &self,
+        app_context: &AC,
+        task: &str,
+        vars: &Vars,
+    ) -> Result<()> {
         let task = self
             .registry
             .get(task)
diff --git a/src/testing.rs b/src/testing.rs
index b2940012c..0a28aeb19 100644
--- a/src/testing.rs
+++ b/src/testing.rs
@@ -10,7 +10,7 @@ use lazy_static::lazy_static;
 use sea_orm::DatabaseConnection;
 
 use crate::{
-    app::{AppContext, Hooks},
+    app::{AppContext, AppContextTrait, Hooks},
     boot::{self, BootResult},
     environment::Environment,
     Result,
@@ -108,19 +108,19 @@ pub fn cleanup_email() -> Vec<(&'static str, &'static str)> {
 /// application context.
 ///
 /// ```rust,ignore
-/// use myapp::app::App;
+/// use myapp::app::{App, AppContext};
 /// use loco_rs::testing;
 /// use migration::Migrator;
 ///
 /// #[tokio::test]
 /// async fn test_create_user() {
-///     let boot = testing::boot_test::<App, Migrator>().await;
+///     let boot = testing::boot_test::<AppContext, App, Migrator>().await;
 ///
 ///     /// .....
 ///     assert!(false)
 /// }
 /// ```
-pub async fn boot_test<H: Hooks>() -> Result<BootResult> {
+pub async fn boot_test<AC: AppContextTrait, H: Hooks<AC>>() -> Result<BootResult<AC>> {
     H::boot(boot::StartMode::ServerOnly, &Environment::Test).await
 }
 
@@ -150,7 +150,7 @@ pub async fn boot_test<H: Hooks>() -> Result<BootResult> {
 ///     assert!(false)
 /// }
 /// ```
-pub async fn seed<H: Hooks>(db: &DatabaseConnection) -> Result<()> {
+pub async fn seed<AC: AppContextTrait, H: Hooks<AC>>(db: &DatabaseConnection) -> Result<()> {
     let path = std::path::Path::new("src/fixtures");
     H::seed(db, path).await
 }
@@ -188,12 +188,12 @@ pub async fn seed<H: Hooks>(db: &DatabaseConnection) -> Result<()> {
 /// }
 /// ```
 #[allow(clippy::future_not_send)]
-pub async fn request<H: Hooks, F, Fut>(callback: F)
+pub async fn request<AC: AppContextTrait, H: Hooks<AC>, F, Fut>(callback: F)
 where
-    F: FnOnce(TestServer, AppContext) -> Fut,
+    F: FnOnce(TestServer, AC) -> Fut,
     Fut: std::future::Future<Output = ()>,
 {
-    let boot = boot_test::<H>().await.unwrap();
+    let boot = boot_test::<AC, H>().await.unwrap();
 
     let config = TestServerConfig::builder()
         .default_content_type("application/json")
diff --git a/src/tests_cfg/db.rs b/src/tests_cfg/db.rs
index 8be43641a..77e66be42 100644
--- a/src/tests_cfg/db.rs
+++ b/src/tests_cfg/db.rs
@@ -81,7 +81,7 @@ impl MigratorTrait for Migrator {
 
 pub struct AppHook;
 #[async_trait]
-impl Hooks for AppHook {
+impl Hooks<AppContext> for AppHook {
     fn app_version() -> String {
         "test".to_string()
     }
@@ -94,12 +94,12 @@ impl Hooks for AppHook {
         Ok(vec![])
     }
 
-    fn routes(_ctx: &AppContext) -> AppRoutes {
+    fn routes(_ctx: &AppContext) -> AppRoutes<AppContext> {
         AppRoutes::with_default_routes()
     }
 
-    async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult> {
-        create_app::<Self, Migrator>(mode, environment).await
+    async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult<AppContext>> {
+        create_app::<AppContext, Self, Migrator>(mode, environment).await
     }
 
     fn connect_workers<'a>(_p: &'a mut Processor, _ctx: &'a AppContext) {}
diff --git a/src/tests_cfg/task.rs b/src/tests_cfg/task.rs
index 03d05aa61..667a46416 100644
--- a/src/tests_cfg/task.rs
+++ b/src/tests_cfg/task.rs
@@ -1,4 +1,4 @@
-use crate::prelude::*;
+use crate::{app::Context, prelude::*};
 
 pub struct Foo;
 #[async_trait]
@@ -9,7 +9,7 @@ impl Task for Foo {
             detail: "run foo task".to_string(),
         }
     }
-    async fn run(&self, _app_context: &AppContext, _vars: &task::Vars) -> Result<()> {
+    async fn run(&self, _app_context: &dyn Context, _vars: &task::Vars) -> Result<()> {
         println!("Foo task executed!!!");
         Ok(())
     }
@@ -24,7 +24,7 @@ impl Task for ParseArgs {
             detail: "Validate the paring args".to_string(),
         }
     }
-    async fn run(&self, _app_context: &AppContext, vars: &task::Vars) -> Result<()> {
+    async fn run(&self, _app_context: &dyn Context, vars: &task::Vars) -> Result<()> {
         let refresh = vars.cli_arg("test").is_ok_and(|test| test == "true");
 
         let app = vars
diff --git a/src/worker.rs b/src/worker.rs
index 14629a963..1c191acdf 100644
--- a/src/worker.rs
+++ b/src/worker.rs
@@ -3,7 +3,7 @@ pub use bb8::Pool;
 pub use sidekiq::{Processor, RedisConnectionManager, Result, Worker};
 use tracing::error;
 
-use super::{app::AppContext, config::WorkerMode};
+use super::{app::AppContextTrait, config::WorkerMode};
 pub const DEFAULT_QUEUES: &[&str] = &["default", "mailer"];
 
 pub fn get_queues(config_queues: &Option<Vec<String>>) -> Vec<String> {
@@ -26,16 +26,17 @@ pub fn get_queues(config_queues: &Option<Vec<String>>) -> Vec<String> {
 
 #[async_trait]
 #[allow(clippy::module_name_repetitions)]
-pub trait AppWorker<T>: Worker<T>
+pub trait AppWorker<AC, T>: Worker<T>
 where
     Self: Sized,
     T: Send + Sync + serde::Serialize + 'static,
+    AC: AppContextTrait,
 {
-    fn build(ctx: &AppContext) -> Self;
-    async fn perform_later(ctx: &AppContext, args: T) -> Result<()> {
-        match &ctx.config.workers.mode {
+    fn build(ctx: &AC) -> Self;
+    async fn perform_later(ctx: &AC, args: T) -> Result<()> {
+        match &ctx.config().workers.mode {
             WorkerMode::BackgroundQueue => {
-                if let Some(queue) = &ctx.queue {
+                if let Some(queue) = ctx.queue() {
                     Self::perform_async(queue, args).await.unwrap();
                 } else {
                     error!(

From 53711eee1fb540044da34478b6d391790be0803e Mon Sep 17 00:00:00 2001
From: Mason Stallmo <masonstallmo@hey.com>
Date: Sat, 21 Sep 2024 00:33:03 -0700
Subject: [PATCH 2/4] Update demo application to work with `AppContextTrait`

---
 examples/demo/Cargo.lock                      |  2 +-
 examples/demo/examples/playground.rs          |  4 +-
 examples/demo/examples/start.rs               |  6 ++-
 examples/demo/examples/task.rs                |  5 +-
 examples/demo/examples/workers.rs             |  6 ++-
 examples/demo/src/app.rs                      |  8 ++--
 examples/demo/src/bin/main.rs                 |  4 +-
 examples/demo/src/bin/tool.rs                 |  4 +-
 examples/demo/src/controllers/auth.rs         |  2 +-
 examples/demo/src/controllers/cache.rs        |  2 +-
 examples/demo/src/controllers/mylayer.rs      |  2 +-
 examples/demo/src/controllers/mysession.rs    |  2 +-
 examples/demo/src/controllers/notes.rs        |  2 +-
 examples/demo/src/controllers/responses.rs    |  2 +-
 examples/demo/src/controllers/upload.rs       |  2 +-
 examples/demo/src/controllers/user.rs         |  2 +-
 examples/demo/src/controllers/view_engine.rs  |  2 +-
 .../demo/src/initializers/axum_session.rs     |  4 +-
 .../src/initializers/hello_view_engine.rs     |  4 +-
 examples/demo/src/initializers/view_engine.rs |  4 +-
 examples/demo/src/tasks/foo.rs                |  4 +-
 examples/demo/src/tasks/seed.rs               |  8 ++--
 examples/demo/src/tasks/user_report.rs        |  6 +--
 examples/demo/src/workers/downloader.rs       |  2 +-
 examples/demo/tests/models/roles.rs           | 12 ++---
 examples/demo/tests/models/users.rs           | 48 ++++++++++++-------
 examples/demo/tests/models/users_roles.rs     |  6 +--
 examples/demo/tests/requests/auth.rs          | 10 ++--
 examples/demo/tests/requests/cache.rs         |  8 ++--
 examples/demo/tests/requests/mylayer.rs       | 18 +++----
 examples/demo/tests/requests/notes.rs         | 16 +++----
 examples/demo/tests/requests/ping.rs          |  4 +-
 examples/demo/tests/requests/responses.rs     |  4 +-
 examples/demo/tests/requests/upload.rs        |  4 +-
 examples/demo/tests/requests/user.rs          | 10 ++--
 examples/demo/tests/requests/view_engine.rs   |  4 +-
 examples/demo/tests/tasks/foo.rs              |  6 +--
 examples/demo/tests/tasks/seed.rs             |  6 +--
 38 files changed, 132 insertions(+), 113 deletions(-)

diff --git a/examples/demo/Cargo.lock b/examples/demo/Cargo.lock
index 847488ded..83ececcad 100644
--- a/examples/demo/Cargo.lock
+++ b/examples/demo/Cargo.lock
@@ -2998,7 +2998,7 @@ dependencies = [
 
 [[package]]
 name = "loco-rs"
-version = "0.8.1"
+version = "0.9.0"
 dependencies = [
  "argon2",
  "async-trait",
diff --git a/examples/demo/examples/playground.rs b/examples/demo/examples/playground.rs
index c9501a325..7defb1f5e 100644
--- a/examples/demo/examples/playground.rs
+++ b/examples/demo/examples/playground.rs
@@ -1,10 +1,10 @@
 use demo_app::app::App;
 #[allow(unused_imports)]
-use loco_rs::{cli::playground, prelude::*};
+use loco_rs::{app::AppContext, cli::playground, prelude::*};
 
 #[tokio::main]
 async fn main() -> loco_rs::Result<()> {
-    let _ctx = playground::<App>().await?;
+    let _ctx = playground::<AppContext, App>().await?;
 
     // let active_model: articles::ActiveModel = ActiveModel {
     //     title: Set(Some("how to build apps in 3 steps".to_string())),
diff --git a/examples/demo/examples/start.rs b/examples/demo/examples/start.rs
index fc5a0f250..b1dc0d0e2 100644
--- a/examples/demo/examples/start.rs
+++ b/examples/demo/examples/start.rs
@@ -1,5 +1,6 @@
 use demo_app::app::App;
 use loco_rs::{
+    app::AppContext,
     boot::{create_app, start, ServeParams, StartMode},
     environment::{resolve_from_env, Environment},
 };
@@ -9,11 +10,12 @@ use migration::Migrator;
 async fn main() -> loco_rs::Result<()> {
     let environment: Environment = resolve_from_env().into();
 
-    let boot_result = create_app::<App, Migrator>(StartMode::ServerAndWorker, &environment).await?;
+    let boot_result =
+        create_app::<AppContext, App, Migrator>(StartMode::ServerAndWorker, &environment).await?;
     let serve_params = ServeParams {
         port: boot_result.app_context.config.server.port,
         binding: boot_result.app_context.config.server.binding.to_string(),
     };
-    start::<App>(boot_result, serve_params).await?;
+    start::<AppContext, App>(boot_result, serve_params).await?;
     Ok(())
 }
diff --git a/examples/demo/examples/task.rs b/examples/demo/examples/task.rs
index eb2f271f5..4704d5628 100644
--- a/examples/demo/examples/task.rs
+++ b/examples/demo/examples/task.rs
@@ -2,6 +2,7 @@ use std::env;
 
 use demo_app::app::App;
 use loco_rs::{
+    app::AppContext,
     boot::{create_context, run_task},
     environment::{resolve_from_env, Environment},
     task,
@@ -13,8 +14,8 @@ async fn main() -> loco_rs::Result<()> {
 
     let args = env::args().collect::<Vec<_>>();
     let cmd = args.get(1);
-    let app_context = create_context::<App>(&environment).await?;
-    run_task::<App>(&app_context, cmd, &task::Vars::default()).await?;
+    let app_context = create_context::<AppContext, App>(&environment).await?;
+    run_task::<AppContext, App>(&app_context, cmd, &task::Vars::default()).await?;
 
     Ok(())
 }
diff --git a/examples/demo/examples/workers.rs b/examples/demo/examples/workers.rs
index 23ccdb78c..2aa2ebdc3 100644
--- a/examples/demo/examples/workers.rs
+++ b/examples/demo/examples/workers.rs
@@ -1,5 +1,6 @@
 use demo_app::app::App;
 use loco_rs::{
+    app::AppContext,
     boot::{create_app, start, ServeParams, StartMode},
     environment::{resolve_from_env, Environment},
 };
@@ -9,11 +10,12 @@ use migration::Migrator;
 async fn main() -> loco_rs::Result<()> {
     let environment: Environment = resolve_from_env().into();
 
-    let boot_result = create_app::<App, Migrator>(StartMode::WorkerOnly, &environment).await?;
+    let boot_result =
+        create_app::<AppContext, App, Migrator>(StartMode::WorkerOnly, &environment).await?;
     let serve_params = ServeParams {
         port: boot_result.app_context.config.server.port,
         binding: boot_result.app_context.config.server.binding.to_string(),
     };
-    start::<App>(boot_result, serve_params).await?;
+    start::<AppContext, App>(boot_result, serve_params).await?;
     Ok(())
 }
diff --git a/examples/demo/src/app.rs b/examples/demo/src/app.rs
index 414e6310d..ca68bf769 100644
--- a/examples/demo/src/app.rs
+++ b/examples/demo/src/app.rs
@@ -27,7 +27,7 @@ use crate::{
 
 pub struct App;
 #[async_trait]
-impl Hooks for App {
+impl Hooks<AppContext> for App {
     fn app_version() -> String {
         format!(
             "{} ({})",
@@ -61,7 +61,7 @@ impl Hooks for App {
     }
     // </snip>
 
-    fn routes(ctx: &AppContext) -> AppRoutes {
+    fn routes(ctx: &AppContext) -> AppRoutes<AppContext> {
         AppRoutes::with_default_routes()
             .add_route(
                 controllers::mylayer::routes(ctx.clone())
@@ -77,8 +77,8 @@ impl Hooks for App {
             .add_route(controllers::cache::routes())
     }
 
-    async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult> {
-        create_app::<Self, Migrator>(mode, environment).await
+    async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult<AppContext>> {
+        create_app::<AppContext, Self, Migrator>(mode, environment).await
     }
 
     async fn after_context(ctx: AppContext) -> Result<AppContext> {
diff --git a/examples/demo/src/bin/main.rs b/examples/demo/src/bin/main.rs
index 8eb2a0306..41fb02ea2 100644
--- a/examples/demo/src/bin/main.rs
+++ b/examples/demo/src/bin/main.rs
@@ -1,8 +1,8 @@
 use demo_app::app::App;
-use loco_rs::cli;
+use loco_rs::{app::AppContext, cli};
 use migration::Migrator;
 
 #[tokio::main]
 async fn main() -> loco_rs::Result<()> {
-    cli::main::<App, Migrator>().await
+    cli::main::<AppContext, App, Migrator>().await
 }
diff --git a/examples/demo/src/bin/tool.rs b/examples/demo/src/bin/tool.rs
index 8eb2a0306..41fb02ea2 100644
--- a/examples/demo/src/bin/tool.rs
+++ b/examples/demo/src/bin/tool.rs
@@ -1,8 +1,8 @@
 use demo_app::app::App;
-use loco_rs::cli;
+use loco_rs::{app::AppContext, cli};
 use migration::Migrator;
 
 #[tokio::main]
 async fn main() -> loco_rs::Result<()> {
-    cli::main::<App, Migrator>().await
+    cli::main::<AppContext, App, Migrator>().await
 }
diff --git a/examples/demo/src/controllers/auth.rs b/examples/demo/src/controllers/auth.rs
index 3c2cbb0ec..6abdc0c13 100644
--- a/examples/demo/src/controllers/auth.rs
+++ b/examples/demo/src/controllers/auth.rs
@@ -136,7 +136,7 @@ async fn login(State(ctx): State<AppContext>, Json(params): Json<LoginParams>) -
     format::json(UserSession::new(&user, &token))
 }
 
-pub fn routes() -> Routes {
+pub fn routes() -> Routes<AppContext> {
     Routes::new()
         .prefix("auth")
         .add("/register", post(register))
diff --git a/examples/demo/src/controllers/cache.rs b/examples/demo/src/controllers/cache.rs
index 5edee1b8b..dd43ff339 100644
--- a/examples/demo/src/controllers/cache.rs
+++ b/examples/demo/src/controllers/cache.rs
@@ -32,7 +32,7 @@ async fn get_or_insert(State(ctx): State<AppContext>) -> Result<Response> {
     }
 }
 
-pub fn routes() -> Routes {
+pub fn routes() -> Routes<AppContext> {
     Routes::new()
         .prefix("cache")
         .add("/", get(get_cache))
diff --git a/examples/demo/src/controllers/mylayer.rs b/examples/demo/src/controllers/mylayer.rs
index b9428a4dc..c93a472a6 100644
--- a/examples/demo/src/controllers/mylayer.rs
+++ b/examples/demo/src/controllers/mylayer.rs
@@ -15,7 +15,7 @@ async fn echo() -> Result<Response> {
     format::json("Hello, World!")
 }
 
-pub fn routes(ctx: AppContext) -> Routes {
+pub fn routes(ctx: AppContext) -> Routes<AppContext> {
     Routes::new()
         .prefix("mylayer")
         // Only users with the RoleName::Admin can access this route
diff --git a/examples/demo/src/controllers/mysession.rs b/examples/demo/src/controllers/mysession.rs
index f8b948ebb..aa334ee86 100644
--- a/examples/demo/src/controllers/mysession.rs
+++ b/examples/demo/src/controllers/mysession.rs
@@ -11,6 +11,6 @@ pub async fn get_session(_session: Session<SessionNullPool>) -> Result<Response>
     format::empty()
 }
 
-pub fn routes() -> Routes {
+pub fn routes() -> Routes<AppContext> {
     Routes::new().prefix("mysession").add("/", get(get_session))
 }
diff --git a/examples/demo/src/controllers/notes.rs b/examples/demo/src/controllers/notes.rs
index 6c5200bcc..91245353a 100644
--- a/examples/demo/src/controllers/notes.rs
+++ b/examples/demo/src/controllers/notes.rs
@@ -150,7 +150,7 @@ impl ListQueryParams {
     }
 }
 
-pub fn routes() -> Routes {
+pub fn routes() -> Routes<AppContext> {
     Routes::new()
         .prefix("notes")
         .add("/", get(list))
diff --git a/examples/demo/src/controllers/responses.rs b/examples/demo/src/controllers/responses.rs
index bb558309b..54d487ef1 100644
--- a/examples/demo/src/controllers/responses.rs
+++ b/examples/demo/src/controllers/responses.rs
@@ -97,7 +97,7 @@ pub async fn set_cookie() -> Result<Response> {
     format::render().cookies(&[cookie])?.json(())
 }
 
-pub fn routes() -> Routes {
+pub fn routes() -> Routes<AppContext> {
     Routes::new()
         .prefix("response")
         .add("/empty", get(empty))
diff --git a/examples/demo/src/controllers/upload.rs b/examples/demo/src/controllers/upload.rs
index b04d0cd89..7fd2414bb 100644
--- a/examples/demo/src/controllers/upload.rs
+++ b/examples/demo/src/controllers/upload.rs
@@ -41,7 +41,7 @@ async fn upload_file(State(ctx): State<AppContext>, mut multipart: Multipart) ->
     })
 }
 
-pub fn routes() -> Routes {
+pub fn routes() -> Routes<AppContext> {
     Routes::new()
         .prefix("upload")
         .add("/file", post(upload_file))
diff --git a/examples/demo/src/controllers/user.rs b/examples/demo/src/controllers/user.rs
index 48365d48c..afa1604fc 100644
--- a/examples/demo/src/controllers/user.rs
+++ b/examples/demo/src/controllers/user.rs
@@ -35,7 +35,7 @@ async fn convert_to_user(
     format::json(UserResponse::new(&auth.user, &roles))
 }
 
-pub fn routes() -> Routes {
+pub fn routes() -> Routes<AppContext> {
     Routes::new()
         .prefix("user")
         .add("/current", get(current))
diff --git a/examples/demo/src/controllers/view_engine.rs b/examples/demo/src/controllers/view_engine.rs
index fc89d2b75..d73d70b2f 100644
--- a/examples/demo/src/controllers/view_engine.rs
+++ b/examples/demo/src/controllers/view_engine.rs
@@ -29,7 +29,7 @@ pub async fn render_simple() -> Result<Response> {
     format::render().template("{{name}} website", json!({"name": "Loco"}))
 }
 
-pub fn routes() -> Routes {
+pub fn routes() -> Routes<AppContext> {
     Routes::new()
         .prefix("view-engine")
         .add("/home", get(render_home))
diff --git a/examples/demo/src/initializers/axum_session.rs b/examples/demo/src/initializers/axum_session.rs
index fb01bbee5..766b84972 100644
--- a/examples/demo/src/initializers/axum_session.rs
+++ b/examples/demo/src/initializers/axum_session.rs
@@ -1,6 +1,6 @@
 use async_trait::async_trait;
 use axum::Router as AxumRouter;
-use loco_rs::prelude::*;
+use loco_rs::{app::Context, prelude::*};
 
 pub struct AxumSessionInitializer;
 
@@ -10,7 +10,7 @@ impl Initializer for AxumSessionInitializer {
         "axum-session".to_string()
     }
 
-    async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(&self, router: AxumRouter, _ctx: &dyn Context) -> Result<AxumRouter> {
         let session_config =
             axum_session::SessionConfig::default().with_table_name("sessions_table");
 
diff --git a/examples/demo/src/initializers/hello_view_engine.rs b/examples/demo/src/initializers/hello_view_engine.rs
index e160b7520..e9e01ad71 100644
--- a/examples/demo/src/initializers/hello_view_engine.rs
+++ b/examples/demo/src/initializers/hello_view_engine.rs
@@ -1,6 +1,6 @@
 use axum::{async_trait, Extension, Router as AxumRouter};
 use loco_rs::{
-    app::{AppContext, Initializer},
+    app::{AppContext, Context, Initializer},
     controller::views::{ViewEngine, ViewRenderer},
     Result,
 };
@@ -21,7 +21,7 @@ impl Initializer for HelloViewEngineInitializer {
         "custom-view-engine".to_string()
     }
 
-    async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(&self, router: AxumRouter, _ctx: &dyn Context) -> Result<AxumRouter> {
         Ok(router.layer(Extension(ViewEngine::from(HelloView))))
     }
 }
diff --git a/examples/demo/src/initializers/view_engine.rs b/examples/demo/src/initializers/view_engine.rs
index f0a5ffba9..2a324ca22 100644
--- a/examples/demo/src/initializers/view_engine.rs
+++ b/examples/demo/src/initializers/view_engine.rs
@@ -1,7 +1,7 @@
 use axum::{async_trait, Extension, Router as AxumRouter};
 use fluent_templates::{ArcLoader, FluentLoader};
 use loco_rs::{
-    app::{AppContext, Initializer},
+    app::{AppContext, Context, Initializer},
     controller::views::{engines, ViewEngine},
     Error, Result,
 };
@@ -17,7 +17,7 @@ impl Initializer for ViewEngineInitializer {
         "view-engine".to_string()
     }
 
-    async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
+    async fn after_routes(&self, router: AxumRouter, _ctx: &dyn Context) -> Result<AxumRouter> {
         let mut tera_engine = engines::TeraView::build()?;
         if std::path::Path::new(I18N_DIR).exists() {
             let arc = ArcLoader::builder(&I18N_DIR, unic_langid::langid!("en-US"))
diff --git a/examples/demo/src/tasks/foo.rs b/examples/demo/src/tasks/foo.rs
index 43193b49d..3d885a6df 100644
--- a/examples/demo/src/tasks/foo.rs
+++ b/examples/demo/src/tasks/foo.rs
@@ -1,5 +1,5 @@
 // <snip id="task-code-example" />
-use loco_rs::prelude::*;
+use loco_rs::{app::Context, prelude::*};
 
 pub struct Foo;
 #[async_trait]
@@ -10,7 +10,7 @@ impl Task for Foo {
             detail: "run foo task".to_string(),
         }
     }
-    async fn run(&self, _app_context: &AppContext, _vars: &task::Vars) -> Result<()> {
+    async fn run(&self, _app_context: &dyn Context, _vars: &task::Vars) -> Result<()> {
         Ok(())
     }
 }
diff --git a/examples/demo/src/tasks/seed.rs b/examples/demo/src/tasks/seed.rs
index 34227a283..64824c877 100644
--- a/examples/demo/src/tasks/seed.rs
+++ b/examples/demo/src/tasks/seed.rs
@@ -12,7 +12,7 @@
 //! command with the `refresh:true` argument: ```sh
 //! cargo run task seed_data refresh:true
 //! ```
-use loco_rs::{db, prelude::*};
+use loco_rs::{app::Context, db, prelude::*};
 use migration::Migrator;
 
 use crate::app::App;
@@ -27,16 +27,16 @@ impl Task for SeedData {
             detail: "Task for seeding data".to_string(),
         }
     }
-    async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> {
+    async fn run(&self, app_context: &dyn Context, vars: &task::Vars) -> Result<()> {
         let refresh = vars
             .cli_arg("refresh")
             .is_ok_and(|refresh| refresh == "true");
 
         if refresh {
-            db::reset::<Migrator>(&app_context.db).await?;
+            db::reset::<Migrator>(app_context.db()).await?;
         }
         let path = std::path::Path::new("src/fixtures");
-        db::run_app_seed::<App>(&app_context.db, path).await?;
+        db::run_app_seed::<AppContext, App>(app_context.db(), path).await?;
         Ok(())
     }
 }
diff --git a/examples/demo/src/tasks/user_report.rs b/examples/demo/src/tasks/user_report.rs
index 4440fe404..7d1cf26d3 100644
--- a/examples/demo/src/tasks/user_report.rs
+++ b/examples/demo/src/tasks/user_report.rs
@@ -1,4 +1,4 @@
-use loco_rs::prelude::*;
+use loco_rs::{app::Context, prelude::*};
 
 use crate::models::_entities::users;
 
@@ -11,8 +11,8 @@ impl Task for UserReport {
             detail: "output a user report".to_string(),
         }
     }
-    async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> {
-        let users = users::Entity::find().all(&app_context.db).await?;
+    async fn run(&self, app_context: &dyn Context, vars: &task::Vars) -> Result<()> {
+        let users = users::Entity::find().all(app_context.db()).await?;
         println!("args: {vars:?}");
         println!("!!! user_report: listing users !!!");
         println!("------------------------");
diff --git a/examples/demo/src/workers/downloader.rs b/examples/demo/src/workers/downloader.rs
index 42c0bd7a4..2462859b6 100644
--- a/examples/demo/src/workers/downloader.rs
+++ b/examples/demo/src/workers/downloader.rs
@@ -15,7 +15,7 @@ pub struct DownloadWorkerArgs {
     pub user_guid: String,
 }
 
-impl worker::AppWorker<DownloadWorkerArgs> for DownloadWorker {
+impl worker::AppWorker<AppContext, DownloadWorkerArgs> for DownloadWorker {
     fn build(ctx: &AppContext) -> Self {
         Self { ctx: ctx.clone() }
     }
diff --git a/examples/demo/tests/models/roles.rs b/examples/demo/tests/models/roles.rs
index 52f4d8af6..bc94b3a78 100644
--- a/examples/demo/tests/models/roles.rs
+++ b/examples/demo/tests/models/roles.rs
@@ -2,7 +2,7 @@ use demo_app::{
     app::App,
     models::{roles, sea_orm_active_enums, users, users::RegisterParams, users_roles},
 };
-use loco_rs::{prelude::*, testing};
+use loco_rs::{app::AppContext, prelude::*, testing};
 use sea_orm::DatabaseConnection;
 use serial_test::serial;
 
@@ -20,7 +20,7 @@ macro_rules! configure_insta {
 async fn can_add_user_to_admin() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
     let new_user: Result<users::Model, ModelError> = users::Model::create_with_password(
         &boot.app_context.db,
         &RegisterParams {
@@ -42,7 +42,7 @@ async fn can_add_user_to_admin() {
 async fn can_add_user_to_user() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
     let new_user: Result<users::Model, ModelError> = users::Model::create_with_password(
         &boot.app_context.db,
         &RegisterParams {
@@ -64,7 +64,7 @@ async fn can_add_user_to_user() {
 async fn can_convert_between_user_and_admin() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
     let new_user: Result<users::Model, ModelError> = users::Model::create_with_password(
         &boot.app_context.db,
         &RegisterParams {
@@ -94,7 +94,7 @@ async fn can_convert_between_user_and_admin() {
 async fn can_find_user_roles() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
     let new_user: Result<users::Model, ModelError> = users::Model::create_with_password(
         &boot.app_context.db,
         &RegisterParams {
@@ -131,7 +131,7 @@ async fn can_find_user_roles() {
 async fn cannot_find_user_before_conversation() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
     let new_user: Result<users::Model, ModelError> = users::Model::create_with_password(
         &boot.app_context.db,
         &RegisterParams {
diff --git a/examples/demo/tests/models/users.rs b/examples/demo/tests/models/users.rs
index d4d935bd2..b90d9c615 100644
--- a/examples/demo/tests/models/users.rs
+++ b/examples/demo/tests/models/users.rs
@@ -3,7 +3,7 @@ use demo_app::{
     models::users::{self, Model, RegisterParams},
 };
 use insta::assert_debug_snapshot;
-use loco_rs::{model::ModelError, testing};
+use loco_rs::{app::AppContext, model::ModelError, testing};
 use sea_orm::{ActiveModelTrait, ActiveValue, IntoActiveModel};
 use serial_test::serial;
 
@@ -21,7 +21,7 @@ macro_rules! configure_insta {
 async fn test_can_validate_model() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
 
     let res = users::ActiveModel {
         name: ActiveValue::set("1".to_string()),
@@ -39,7 +39,7 @@ async fn test_can_validate_model() {
 async fn can_create_with_password() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
 
     let params = RegisterParams {
         email: "test@framework.com".to_string(),
@@ -60,8 +60,10 @@ async fn can_create_with_password() {
 async fn handle_create_with_password_with_duplicate() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
-    testing::seed::<App>(&boot.app_context.db).await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
+    testing::seed::<AppContext, App>(&boot.app_context.db)
+        .await
+        .unwrap();
 
     let new_user: Result<Model, ModelError> = Model::create_with_password(
         &boot.app_context.db,
@@ -80,8 +82,10 @@ async fn handle_create_with_password_with_duplicate() {
 async fn can_find_by_email() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
-    testing::seed::<App>(&boot.app_context.db).await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
+    testing::seed::<AppContext, App>(&boot.app_context.db)
+        .await
+        .unwrap();
 
     let existing_user = Model::find_by_email(&boot.app_context.db, "user1@example.com").await;
     let non_existing_user_results =
@@ -96,8 +100,10 @@ async fn can_find_by_email() {
 async fn can_find_by_pid() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
-    testing::seed::<App>(&boot.app_context.db).await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
+    testing::seed::<AppContext, App>(&boot.app_context.db)
+        .await
+        .unwrap();
 
     let existing_user =
         Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111").await;
@@ -113,8 +119,10 @@ async fn can_find_by_pid() {
 async fn can_verification_token() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
-    testing::seed::<App>(&boot.app_context.db).await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
+    testing::seed::<AppContext, App>(&boot.app_context.db)
+        .await
+        .unwrap();
 
     let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
         .await
@@ -142,8 +150,10 @@ async fn can_verification_token() {
 async fn can_set_forgot_password_sent() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
-    testing::seed::<App>(&boot.app_context.db).await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
+    testing::seed::<AppContext, App>(&boot.app_context.db)
+        .await
+        .unwrap();
 
     let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
         .await
@@ -171,8 +181,10 @@ async fn can_set_forgot_password_sent() {
 async fn can_verified() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
-    testing::seed::<App>(&boot.app_context.db).await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
+    testing::seed::<AppContext, App>(&boot.app_context.db)
+        .await
+        .unwrap();
 
     let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
         .await
@@ -198,8 +210,10 @@ async fn can_verified() {
 async fn can_reset_password() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
-    testing::seed::<App>(&boot.app_context.db).await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
+    testing::seed::<AppContext, App>(&boot.app_context.db)
+        .await
+        .unwrap();
 
     let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
         .await
diff --git a/examples/demo/tests/models/users_roles.rs b/examples/demo/tests/models/users_roles.rs
index debacd2a8..90aff0c40 100644
--- a/examples/demo/tests/models/users_roles.rs
+++ b/examples/demo/tests/models/users_roles.rs
@@ -2,7 +2,7 @@ use demo_app::{
     app::App,
     models::{roles, sea_orm_active_enums, users, users::RegisterParams, users_roles},
 };
-use loco_rs::{prelude::*, testing};
+use loco_rs::{app::AppContext, prelude::*, testing};
 use sea_orm::{ColumnTrait, DatabaseConnection};
 use serial_test::serial;
 macro_rules! configure_insta {
@@ -19,7 +19,7 @@ macro_rules! configure_insta {
 async fn can_connect_user_to_user_role() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
     let new_user: Result<users::Model, ModelError> = users::Model::create_with_password(
         &boot.app_context.db,
         &RegisterParams {
@@ -61,7 +61,7 @@ async fn can_connect_user_to_user_role() {
 async fn can_connect_user_to_admin_role() {
     configure_insta!();
 
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
     let new_user: Result<users::Model, ModelError> = users::Model::create_with_password(
         &boot.app_context.db,
         &RegisterParams {
diff --git a/examples/demo/tests/requests/auth.rs b/examples/demo/tests/requests/auth.rs
index 957fb7632..b9b5f7b47 100644
--- a/examples/demo/tests/requests/auth.rs
+++ b/examples/demo/tests/requests/auth.rs
@@ -1,6 +1,6 @@
 use demo_app::{app::App, models::users};
 use insta::{assert_debug_snapshot, with_settings};
-use loco_rs::testing;
+use loco_rs::{app::AppContext, testing};
 use rstest::rstest;
 use serial_test::serial;
 
@@ -22,7 +22,7 @@ macro_rules! configure_insta {
 async fn can_register() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let email = "test@loco.com";
         let payload = serde_json::json!({
             "name": "loco",
@@ -56,7 +56,7 @@ async fn can_register() {
 async fn can_login_with_verify(#[case] test_name: &str, #[case] password: &str) {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let email = "test@loco.com";
         let register_payload = serde_json::json!({
             "name": "loco",
@@ -103,7 +103,7 @@ async fn can_login_with_verify(#[case] test_name: &str, #[case] password: &str)
 async fn can_login_without_verify() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, _ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, _ctx| async move {
         let email = "test@loco.com";
         let password = "12341234";
         let register_payload = serde_json::json!({
@@ -138,7 +138,7 @@ async fn can_login_without_verify() {
 async fn can_reset_password() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let login_data = prepare_data::init_user_login(&request, &ctx).await;
 
         let forgot_payload = serde_json::json!({
diff --git a/examples/demo/tests/requests/cache.rs b/examples/demo/tests/requests/cache.rs
index 2321645a1..817e2fe1b 100644
--- a/examples/demo/tests/requests/cache.rs
+++ b/examples/demo/tests/requests/cache.rs
@@ -1,6 +1,6 @@
 use demo_app::{app::App, models::users};
 use insta::assert_debug_snapshot;
-use loco_rs::testing;
+use loco_rs::{app::AppContext, testing};
 use sea_orm::ModelTrait;
 use serial_test::serial;
 
@@ -20,7 +20,7 @@ macro_rules! configure_insta {
 async fn ping() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, _ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, _ctx| async move {
         let response = request.get("cache").await;
         assert_debug_snapshot!("key_not_exists", (response.text(), response.status_code()));
         let response = request.post("cache/insert").await;
@@ -36,8 +36,8 @@ async fn ping() {
 async fn can_get_or_insert() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
-        testing::seed::<App>(&ctx.db).await.unwrap();
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
+        testing::seed::<AppContext, App>(&ctx.db).await.unwrap();
         let response = request.get("/cache/get_or_insert").await;
         assert_eq!(response.text(), "user1");
 
diff --git a/examples/demo/tests/requests/mylayer.rs b/examples/demo/tests/requests/mylayer.rs
index b78b45326..c67ca20bd 100644
--- a/examples/demo/tests/requests/mylayer.rs
+++ b/examples/demo/tests/requests/mylayer.rs
@@ -1,5 +1,5 @@
 use demo_app::{app::App, views::user::UserResponse};
-use loco_rs::testing;
+use loco_rs::{app::AppContext, testing};
 use serial_test::serial;
 
 use crate::requests::prepare_data;
@@ -15,7 +15,7 @@ macro_rules! configure_insta {
 #[serial]
 async fn cannot_get_echo_when_no_role_assigned() {
     configure_insta!();
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
         let response = request
@@ -31,7 +31,7 @@ async fn cannot_get_echo_when_no_role_assigned() {
 #[serial]
 async fn can_get_echo_when_admin_role_assigned() {
     configure_insta!();
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
         let response = request
@@ -54,7 +54,7 @@ async fn can_get_echo_when_admin_role_assigned() {
 #[serial]
 async fn can_get_echo_when_user_role_assigned() {
     configure_insta!();
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
         let response = request
@@ -78,7 +78,7 @@ async fn can_get_echo_when_user_role_assigned() {
 #[serial]
 async fn cannot_get_admin_when_no_role() {
     configure_insta!();
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
         let response = request
@@ -94,7 +94,7 @@ async fn cannot_get_admin_when_no_role() {
 #[serial]
 async fn cannot_get_admin_when_user_role_assigned() {
     configure_insta!();
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
         let response = request
@@ -118,7 +118,7 @@ async fn cannot_get_admin_when_user_role_assigned() {
 #[serial]
 async fn can_get_admin_when_admin_role_assigned() {
     configure_insta!();
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
         let response = request
@@ -142,7 +142,7 @@ async fn can_get_admin_when_admin_role_assigned() {
 #[serial]
 async fn cannot_get_user_when_no_role() {
     configure_insta!();
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
         let response = request
@@ -158,7 +158,7 @@ async fn cannot_get_user_when_no_role() {
 #[serial]
 async fn can_get_user_when_user_role_assigned() {
     configure_insta!();
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
         let response = request
diff --git a/examples/demo/tests/requests/notes.rs b/examples/demo/tests/requests/notes.rs
index 7377771bc..8663e0cd7 100644
--- a/examples/demo/tests/requests/notes.rs
+++ b/examples/demo/tests/requests/notes.rs
@@ -1,6 +1,6 @@
 use demo_app::{app::App, models::_entities::notes::Entity};
 use insta::{assert_debug_snapshot, with_settings};
-use loco_rs::testing;
+use loco_rs::{app::AppContext, testing};
 use rstest::rstest;
 use sea_orm::entity::prelude::*;
 use serde_json;
@@ -27,8 +27,8 @@ macro_rules! configure_insta {
 async fn can_get_notes(#[case] test_name: &str, #[case] params: serde_json::Value) {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
-        testing::seed::<App>(&ctx.db).await.unwrap();
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
+        testing::seed::<AppContext, App>(&ctx.db).await.unwrap();
 
         let notes = request.get("notes").add_query_params(params).await;
 
@@ -52,7 +52,7 @@ async fn can_get_notes(#[case] test_name: &str, #[case] params: serde_json::Valu
 async fn can_add_note() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, _ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, _ctx| async move {
         let payload = serde_json::json!({
             "title": "loco",
             "content": "loco note test",
@@ -80,8 +80,8 @@ async fn can_add_note() {
 async fn can_get_note() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
-        testing::seed::<App>(&ctx.db).await.unwrap();
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
+        testing::seed::<AppContext, App>(&ctx.db).await.unwrap();
 
         let add_note_request = request.get("notes/1").await;
 
@@ -105,8 +105,8 @@ async fn can_get_note() {
 async fn can_delete_note() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
-        testing::seed::<App>(&ctx.db).await.unwrap();
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
+        testing::seed::<AppContext, App>(&ctx.db).await.unwrap();
 
         let count_before_delete = Entity::find().all(&ctx.db).await.unwrap().len();
         let delete_note_request = request.delete("notes/1").await;
diff --git a/examples/demo/tests/requests/ping.rs b/examples/demo/tests/requests/ping.rs
index 1be7243f0..562313f1c 100644
--- a/examples/demo/tests/requests/ping.rs
+++ b/examples/demo/tests/requests/ping.rs
@@ -1,6 +1,6 @@
 use demo_app::app::App;
 use insta::assert_debug_snapshot;
-use loco_rs::testing;
+use loco_rs::{app::AppContext, testing};
 use rstest::rstest;
 
 // TODO: see how to dedup / extract this to app-local test utils
@@ -24,7 +24,7 @@ macro_rules! configure_insta {
 async fn ping(#[case] test_name: &str, #[case] path: &str) {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, _ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, _ctx| async move {
         let response = request.get(path).await;
 
         assert_debug_snapshot!(test_name, (response.text(), response.status_code()));
diff --git a/examples/demo/tests/requests/responses.rs b/examples/demo/tests/requests/responses.rs
index e68b84c60..fb60982dd 100644
--- a/examples/demo/tests/requests/responses.rs
+++ b/examples/demo/tests/requests/responses.rs
@@ -1,7 +1,7 @@
 use axum::http::HeaderMap;
 use demo_app::app::App;
 use insta::assert_debug_snapshot;
-use loco_rs::testing;
+use loco_rs::{app::AppContext, testing};
 use rstest::rstest;
 use serial_test::serial;
 // TODO: see how to dedup / extract this to app-local test utils
@@ -29,7 +29,7 @@ macro_rules! configure_insta {
 #[serial]
 async fn can_return_different_responses(#[case] uri: &str) {
     configure_insta!();
-    testing::request::<App, _, _>(|request, _ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, _ctx| async move {
         let response = request.get(uri).await;
 
         let mut headers = HeaderMap::new();
diff --git a/examples/demo/tests/requests/upload.rs b/examples/demo/tests/requests/upload.rs
index 0c2a46667..cb80e18cc 100644
--- a/examples/demo/tests/requests/upload.rs
+++ b/examples/demo/tests/requests/upload.rs
@@ -1,12 +1,12 @@
 use axum_test::multipart::{MultipartForm, Part};
 use demo_app::{app::App, views};
-use loco_rs::testing;
+use loco_rs::{app::AppContext, testing};
 use serial_test::serial;
 
 #[tokio::test]
 #[serial]
 async fn can_upload_file() {
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let file_content = "loco file upload";
         let file_part = Part::bytes(file_content.as_bytes()).file_name("loco.txt");
 
diff --git a/examples/demo/tests/requests/user.rs b/examples/demo/tests/requests/user.rs
index a6112a718..88eae3258 100644
--- a/examples/demo/tests/requests/user.rs
+++ b/examples/demo/tests/requests/user.rs
@@ -1,6 +1,6 @@
 use demo_app::app::App;
 use insta::{assert_debug_snapshot, with_settings};
-use loco_rs::testing;
+use loco_rs::{app::AppContext, testing};
 use serial_test::serial;
 
 use super::prepare_data;
@@ -21,7 +21,7 @@ macro_rules! configure_insta {
 async fn can_get_current_user() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
 
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
@@ -44,7 +44,7 @@ async fn can_get_current_user() {
 async fn can_get_current_user_with_api_key() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user_data = prepare_data::init_user_login(&request, &ctx).await;
 
         let (auth_key, auth_value) = prepare_data::auth_header(&user_data.user.api_key);
@@ -67,7 +67,7 @@ async fn can_get_current_user_with_api_key() {
 async fn can_convert_user_to_user_role() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
 
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
@@ -90,7 +90,7 @@ async fn can_convert_user_to_user_role() {
 async fn can_convert_user_to_admin_role() {
     configure_insta!();
 
-    testing::request::<App, _, _>(|request, ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, ctx| async move {
         let user = prepare_data::init_user_login(&request, &ctx).await;
 
         let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
diff --git a/examples/demo/tests/requests/view_engine.rs b/examples/demo/tests/requests/view_engine.rs
index 4f7422556..315169fa1 100644
--- a/examples/demo/tests/requests/view_engine.rs
+++ b/examples/demo/tests/requests/view_engine.rs
@@ -1,6 +1,6 @@
 use demo_app::app::App;
 use insta::assert_debug_snapshot;
-use loco_rs::testing;
+use loco_rs::{app::AppContext, testing};
 use rstest::rstest;
 use serial_test::serial;
 // TODO: see how to dedup / extract this to app-local test utils
@@ -22,7 +22,7 @@ macro_rules! configure_insta {
 #[serial]
 async fn can_get_view_engine(#[case] uri: &str) {
     configure_insta!();
-    testing::request::<App, _, _>(|request, _ctx| async move {
+    testing::request::<AppContext, App, _, _>(|request, _ctx| async move {
         let response = request.get(&format!("/view-engine/{uri}")).await;
 
         assert_debug_snapshot!(
diff --git a/examples/demo/tests/tasks/foo.rs b/examples/demo/tests/tasks/foo.rs
index e45bbc085..c7b4c4e56 100644
--- a/examples/demo/tests/tasks/foo.rs
+++ b/examples/demo/tests/tasks/foo.rs
@@ -1,13 +1,13 @@
 use demo_app::app::App;
-use loco_rs::{boot::run_task, task, testing};
+use loco_rs::{app::AppContext, boot::run_task, task, testing};
 use serial_test::serial;
 
 #[tokio::test]
 #[serial]
 async fn test_can_run_foo_task() {
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
 
-    assert!(run_task::<App>(
+    assert!(run_task::<AppContext, App>(
         &boot.app_context,
         Some(&"foo".to_string()),
         &task::Vars::default()
diff --git a/examples/demo/tests/tasks/seed.rs b/examples/demo/tests/tasks/seed.rs
index 2657bf2a4..6f854c3d1 100644
--- a/examples/demo/tests/tasks/seed.rs
+++ b/examples/demo/tests/tasks/seed.rs
@@ -1,13 +1,13 @@
 use demo_app::app::App;
-use loco_rs::{boot::run_task, task, testing};
+use loco_rs::{app::AppContext, boot::run_task, task, testing};
 use serial_test::serial;
 
 #[tokio::test]
 #[serial]
 async fn test_can_seed_data() {
-    let boot = testing::boot_test::<App>().await.unwrap();
+    let boot = testing::boot_test::<AppContext, App>().await.unwrap();
 
-    assert!(run_task::<App>(
+    assert!(run_task::<AppContext, App>(
         &boot.app_context,
         Some(&"seed_data".to_string()),
         &task::Vars::default()

From f35443aa4e60bbf236bf25d053edae406fc0005f Mon Sep 17 00:00:00 2001
From: Mason Stallmo <masonstallmo@hey.com>
Date: Sun, 22 Sep 2024 01:01:19 -0700
Subject: [PATCH 3/4] Add documentation for new traits

---
 src/app.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 113 insertions(+), 1 deletion(-)

diff --git a/src/app.rs b/src/app.rs
index 6f9c28cf5..3ec1363c4 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -27,6 +27,11 @@ use crate::{
     Result,
 };
 
+/// Object-safe trait for representing application context needed
+/// by the web server to operate.
+///
+/// See [AppContextTrait] for more complete documentation on
+/// application context.
 pub trait Context: Send + Sync + 'static {
     fn environment(&self) -> &Environment;
     #[cfg(feature = "with-db")]
@@ -38,7 +43,114 @@ pub trait Context: Send + Sync + 'static {
     fn cache(&self) -> Arc<cache::Cache>;
 }
 
-pub trait AppContextTrait: Clone + Default + Context {
+/// This trait defines the configuration required by the
+/// web server to operate.
+///
+/// This trait along with [Context] should be implemented for any
+/// struct used to represent the application context. A default implementation
+/// is provided by the [AppContext] struct that can be used in your server.
+///
+/// ```rust,ignore
+/// use loco_rs::{app::{AppContext, AppContextTrait, Context}};
+///
+/// #[derive(Default)]
+/// struct LocalContext {
+///     app_context: AppContext,
+/// }
+///
+/// impl Context for LocalContext {
+///     fn environment(&self) -> &Environment {
+///         &self.app_context.environment
+///     }
+///
+///     #[cfg(feature = "with-db")]
+///     fn db(&self) -> &DatabaseConnection {
+///         &self.app_context.db
+///     }
+///
+///     fn queue(&self) -> &Option<Pool<RedisConnectionManager>> {
+///         &self.app_context.queue
+///     }
+///
+///     fn config(&self) -> &Config {
+///         &self.app_context.config
+///     }
+///
+///     fn mailer(&self) -> &Option<EmailSender> {
+///         &self.app_context.mailer
+///     }
+///
+///     fn storage(&self) -> Arc<Storage> {
+///         self.app_context.storage.clone()
+///     }
+///
+///     fn cache(&self) -> Arc<cache::Cache> {
+///         self.app_context.cache.clone()
+///     }
+/// }
+///
+/// impl AppContextTrait for LocalContext {
+///
+///     #[cfg(feature = "with-db")]
+///     fn create(
+///         environment: Environment,
+///         config: Config,
+///         db: DatabaseConnection,
+///         queue: Option<Pool<RedisConnectionManager>>,
+///     ) -> Result<Self> {
+///         let mailer = if let Some(cfg) = config.mailer.as_ref() {
+///             create_mailer(cfg)?
+///         } else {
+///             None
+///         };
+///
+///         Ok(LocalContext {
+///             app_context: AppContext {
+///                 environment,
+///                 db,
+///                 queue,
+///                 storage: Storage::single(storage::drivers::null::new()).into(),
+///                 cache: Cache::new(cache::drivers::null::new()).into(),
+///                 config,
+///                 mailer,
+///             }
+///         })
+///     }
+///
+///
+///
+///     #[cfg(not(feature = "with-db"))]
+///     fn create(
+///         environment: Environment,
+///         config: Config,
+///         queue: Option<Pool<RedisConnectionManager>>,
+///     ) -> Result<Self> {
+///         let mailer = if let Some(cfg) = config.mailer.as_ref() {
+///             create_mailer(cfg)?
+///         } else {
+///             None
+///         };
+///
+///         Ok(LocalContext {
+///             app_context: AppContext {
+///                 environment,
+///                 queue,
+///                 storage: Storage::single(storage::drivers::null::new()).into(),
+///                 cache: Cache::new(cache::drivers::null::new()).into(),
+///                 config,
+///                 mailer,
+///             }
+///         })
+///     }
+/// }
+///
+/// impl Hooks<LocalContext> for App {
+///     .
+///     .
+///     .
+/// }
+/// ```
+pub trait AppContextTrait: Context + Clone + Default {
     #[cfg(feature = "with-db")]
     fn create(
         environment: Environment,

From a4e1b0bf16354704242c7c66d2d9c752d0c80f2e Mon Sep 17 00:00:00 2001
From: Mason Stallmo <masonstallmo@hey.com>
Date: Sun, 22 Sep 2024 01:05:36 -0700
Subject: [PATCH 4/4] Format with cargo +nightly fmt

---
 src/app.rs                   |  6 +++---
 src/boot.rs                  | 14 ++++++++------
 src/controller/app_routes.rs |  6 +++---
 src/controller/format.rs     |  5 +++--
 src/controller/routes.rs     |  1 -
 src/controller/views/mod.rs  |  4 ++--
 src/logger.rs                |  7 +++----
 src/mailer/mod.rs            |  3 +--
 src/scheduler.rs             | 20 ++++++++++----------
 src/tests_cfg/config.rs      |  3 ++-
 src/tests_cfg/db.rs          | 12 ++++++------
 11 files changed, 41 insertions(+), 40 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index 3ec1363c4..a0917468f 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -30,7 +30,7 @@ use crate::{
 /// Object-safe trait for representing application context needed
 /// by the web server to operate.
 ///
-/// See [AppContextTrait] for more complete documentation on
+/// See [`AppContextTrait`] for more complete documentation on
 /// application context.
 pub trait Context: Send + Sync + 'static {
     fn environment(&self) -> &Environment;
@@ -46,9 +46,9 @@ pub trait Context: Send + Sync + 'static {
 /// This trait defines the configuration required by the
 /// web server to operate.
 ///
-/// This trait along with [Context] should be implemented for any
+/// This trait along with [`Context`] should be implemented for any
 /// struct used to represent the application context. A default implementation
-/// is provided by the [AppContext] struct that can be used in your server.
+/// is provided by the [`AppContext`] struct that can be used in your server.
 ///
 /// ```rust,ignore
 /// use loco_rs::{app::{AppContext, AppContextTrait, Context}};
diff --git a/src/boot.rs b/src/boot.rs
index c20b34279..1ced9f73f 100644
--- a/src/boot.rs
+++ b/src/boot.rs
@@ -125,13 +125,15 @@ pub async fn run_task<AC: AppContextTrait, H: Hooks<AC>>(
     Ok(())
 }
 
-/// Runs the scheduler with the given configuration and context. in case if list args is true
-/// prints scheduler job configuration
+/// Runs the scheduler with the given configuration and context. in case if list
+/// args is true prints scheduler job configuration
 ///
-/// This function initializes the scheduler, registers tasks through the provided [`Hooks`],
-/// and executes the scheduler based on the specified configuration or context. The scheduler
-/// continuously runs, managing and executing scheduled tasks until a signal is received to shut down.
-/// Upon receiving this signal, the function gracefully shuts down all running tasks and exits safely.
+/// This function initializes the scheduler, registers tasks through the
+/// provided [`Hooks`], and executes the scheduler based on the specified
+/// configuration or context. The scheduler continuously runs, managing and
+/// executing scheduled tasks until a signal is received to shut down.
+/// Upon receiving this signal, the function gracefully shuts down all running
+/// tasks and exits safely.
 ///
 /// # Errors
 ///
diff --git a/src/controller/app_routes.rs b/src/controller/app_routes.rs
index 063688e62..715aee7fb 100644
--- a/src/controller/app_routes.rs
+++ b/src/controller/app_routes.rs
@@ -516,13 +516,13 @@ fn handle_panic(err: Box<dyn std::any::Any + Send + 'static>) -> axum::response:
 #[cfg(test)]
 mod tests {
 
-    use super::*;
-    use crate::prelude::*;
-    use crate::tests_cfg;
     use insta::assert_debug_snapshot;
     use rstest::rstest;
     use tower::ServiceExt;
 
+    use super::*;
+    use crate::{prelude::*, tests_cfg};
+
     async fn action() -> Result<Response> {
         format::json("loco")
     }
diff --git a/src/controller/format.rs b/src/controller/format.rs
index 8acb70382..1f8aa7a4a 100644
--- a/src/controller/format.rs
+++ b/src/controller/format.rs
@@ -371,11 +371,12 @@ pub fn render() -> RenderBuilder {
 #[cfg(test)]
 mod tests {
 
-    use super::*;
-    use crate::{controller::views::engines::TeraView, prelude::*};
     use insta::assert_debug_snapshot;
     use tree_fs;
 
+    use super::*;
+    use crate::{controller::views::engines::TeraView, prelude::*};
+
     async fn response_body_to_string(response: hyper::Response<Body>) -> String {
         let bytes = axum::body::to_bytes(response.into_body(), 200)
             .await
diff --git a/src/controller/routes.rs b/src/controller/routes.rs
index d58e02144..a49317ce5 100644
--- a/src/controller/routes.rs
+++ b/src/controller/routes.rs
@@ -47,7 +47,6 @@ impl<AC: AppContextTrait> Routes<AC> {
     ///     format::json(Health { ok: true })
     /// }
     /// Routes::<AppContext>::at("status").add("/_ping", get(ping));
-    ///
     /// ````
     #[must_use]
     pub fn at(prefix: &str) -> Self {
diff --git a/src/controller/views/mod.rs b/src/controller/views/mod.rs
index 2948cb61f..4eccc672e 100644
--- a/src/controller/views/mod.rs
+++ b/src/controller/views/mod.rs
@@ -28,8 +28,8 @@ impl<E> ViewEngine<E> {
 
 /// A struct representing an inline Tera view renderer.
 ///
-/// This struct provides functionality to render templates using the Tera templating engine
-/// directly from raw template strings.
+/// This struct provides functionality to render templates using the Tera
+/// templating engine directly from raw template strings.
 ///
 /// # Example
 /// ```
diff --git a/src/logger.rs b/src/logger.rs
index ca5b74af6..f44aa29ba 100644
--- a/src/logger.rs
+++ b/src/logger.rs
@@ -5,10 +5,9 @@ use std::sync::OnceLock;
 use serde::{Deserialize, Serialize};
 use serde_variant::to_variant_name;
 use tracing_appender::non_blocking::WorkerGuard;
-use tracing_subscriber::fmt::MakeWriter;
-use tracing_subscriber::layer::SubscriberExt;
-use tracing_subscriber::util::SubscriberInitExt;
-use tracing_subscriber::{fmt, EnvFilter, Layer, Registry};
+use tracing_subscriber::{
+    fmt, fmt::MakeWriter, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer, Registry,
+};
 
 use crate::{
     app::{AppContextTrait, Hooks},
diff --git a/src/mailer/mod.rs b/src/mailer/mod.rs
index 83e5a8314..fd6a3230c 100644
--- a/src/mailer/mod.rs
+++ b/src/mailer/mod.rs
@@ -11,10 +11,9 @@ use include_dir::Dir;
 use serde::{Deserialize, Serialize};
 use sidekiq::Worker;
 
-use crate::app::AppContextTrait;
-
 use self::template::Template;
 use super::{app::AppContext, worker::AppWorker, Result};
+use crate::app::AppContextTrait;
 
 pub const DEFAULT_FROM_SENDER: &str = "System <system@example.com>";
 
diff --git a/src/scheduler.rs b/src/scheduler.rs
index 7db31a3e0..bac3f71bd 100644
--- a/src/scheduler.rs
+++ b/src/scheduler.rs
@@ -1,8 +1,6 @@
 //! # Scheduler Module
 //! TBD
 
-use regex::Regex;
-use serde::{Deserialize, Serialize};
 use std::{
     collections::HashMap,
     fmt, io,
@@ -10,14 +8,16 @@ use std::{
     time::Instant,
 };
 
+use regex::Regex;
+use serde::{Deserialize, Serialize};
+use tokio_cron_scheduler::{JobScheduler, JobSchedulerError};
+
 use crate::{
     app::{AppContextTrait, Hooks},
     environment::Environment,
     task::Tasks,
 };
 
-use tokio_cron_scheduler::{JobScheduler, JobSchedulerError};
-
 lazy_static::lazy_static! {
     static ref RE_IS_CRON_SYNTAX: Regex = Regex::new(r"^[\*\d]").unwrap();
 }
@@ -75,7 +75,7 @@ pub struct Job {
     ///
     /// The format is as follows:
     /// sec   min   hour   day of month   month   day of week   year
-    /// *     *     *      *              *       *             *
+    /// * *     *      *              *       *             *
     pub cron: String,
     /// Tags for tagging the job.
     pub tags: Option<Vec<String>>,
@@ -225,8 +225,8 @@ impl Scheduler {
 
     /// Creates a new scheduler instance from the provided configuration data.
     ///
-    /// When creating a new scheduler instance all register task should be loaded for validate the
-    /// given configuration.
+    /// When creating a new scheduler instance all register task should be
+    /// loaded for validate the given configuration.
     ///
     /// # Errors
     ///
@@ -361,14 +361,14 @@ impl Scheduler {
 #[cfg(test)]
 mod tests {
 
-    use super::*;
-    use crate::{app::AppContext, tests_cfg};
     use insta::assert_debug_snapshot;
-
     use rstest::rstest;
     use tests_cfg::db::AppHook;
     use tokio::time::{self, Duration};
 
+    use super::*;
+    use crate::{app::AppContext, tests_cfg};
+
     fn get_scheduler_from_config() -> Result<Scheduler, Error> {
         let scheduler_config_path = PathBuf::from("tests")
             .join("fixtures")
diff --git a/src/tests_cfg/config.rs b/src/tests_cfg/config.rs
index 7e8651c2e..bfb67e5fb 100644
--- a/src/tests_cfg/config.rs
+++ b/src/tests_cfg/config.rs
@@ -1,8 +1,9 @@
+use std::collections::HashMap;
+
 use crate::{
     config::{self, Config},
     logger, scheduler,
 };
-use std::collections::HashMap;
 
 #[must_use]
 pub fn test_config() -> Config {
diff --git a/src/tests_cfg/db.rs b/src/tests_cfg/db.rs
index 77e66be42..8af150a43 100644
--- a/src/tests_cfg/db.rs
+++ b/src/tests_cfg/db.rs
@@ -1,3 +1,9 @@
+use std::path::Path;
+
+use async_trait::async_trait;
+use sea_orm::DatabaseConnection;
+pub use sea_orm_migration::prelude::*;
+
 #[cfg(feature = "channels")]
 use crate::controller::channels::AppChannels;
 use crate::{
@@ -10,12 +16,6 @@ use crate::{
     Result,
 };
 
-use std::path::Path;
-
-use async_trait::async_trait;
-use sea_orm::DatabaseConnection;
-pub use sea_orm_migration::prelude::*;
-
 /// Creating a dummy db connection for docs
 ///
 /// # Panics