Site
Site is the built Vyuh application. It owns configuration, the router,
database pool, authenticator, template engine, task dispatcher, signal and
emitter engines, services, commands, logging, and shutdown coordination.
Most applications interact with Site in two places:
- At startup, through
Site::build,Site::run,Site::serve, orSite::test. - Inside handlers and workers, where
Siteor subsystem handles can be extracted when framework access is needed.
Overview
The main public pieces are:
SiteConffor application configuration.Site::build(conf, bundle)for building a site without serving it.Site::run(conf, bundle)for command-aware application entrypoints.Site::serve(conf, bundle)for directly building and serving HTTP.Site::test(conf, bundle, pool)for tests with an explicit SQLx pool.site.start()for serving an already-built site.Siteaccessors such asdb(),tasks(),templates(),service(),auth(),signals(), andreverse().vyuh::testing::router(&site)for tests or Axum interop.SiteConf::http(...)for global HTTP middleware and slash behavior.SiteConf::templates(...)for Minijinja environment behavior.
Site is cheap to clone. Clones share the same underlying application state.
Configuration
Start from SiteConf::default() and set only what the application needs:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::db::DbConf;
use vyuh::console::ConsoleConf;
use vyuh::file_storage::UploadConf;
use vyuh::middlewares::{HttpConf, TraceConf};
use vyuh::templates::{TemplateConf, TemplateDateFormats};
let conf = SiteConf::default()
.host("127.0.0.1")
.port(8080)
.project_dir(".")
.database(DbConf::from_url("sqlite://app.db?max=5")?)
.secret_key("replace-with-a-long-random-secret")
.templates(TemplateConf {
date_formats: TemplateDateFormats {
date: "%d %b %Y".into(),
time: "%H:%M".into(),
datetime: "%d %b %Y, %H:%M".into(),
},
..TemplateConf::default()
})
.http(HttpConf {
trace: TraceConf { enabled: true },
..HttpConf::default()
})
.uploads(UploadConf {
dir: "media/uploads".into(),
base_url: Some("/media/uploads".into()),
..UploadConf::default()
})
.console(ConsoleConf::default())
.timezone("UTC");
}
project_dir is the base for relative media, upload, reload, auth key, and log
paths. Static files and templates belong to bundles through asset dirs.
SiteConf::validate() checks required fields and path readability before the
site is built.
With no database backend feature enabled, SiteConf::default() uses a shared
in-memory SQLite database URL and tasks use MemoryTaskStore. This is intended
for quick starts, docs, local experiments, and tests. Production applications
should enable exactly one backend feature (postgres, mysql, or sqlite) and
configure a durable database.
For global HTTP behavior, see Middlewares. For Minijinja environment behavior and formatting helpers, see Templates. For upload storage, see Uploads. For optional operational inspection, see Console.
Environment helpers are available when configuration should come from the process environment:
#![allow(unused)]
fn main() {
let conf = vyuh::SiteConf::from_env_with_files()?;
}
from_env_with_files() loads .env, then .env.test, .env.dev, or
.env.prod depending on the build mode. Environment variables currently patch
common deployment fields such as DATABASE_URL, SECRET_KEY, HOST, PORT,
TZ, and LOG_INIT.
Lifecycle
Vyuh keeps lifecycle on Site:
| Method | Purpose |
|---|---|
Site::build | build the site object without starting HTTP |
Site::run | command-aware application entrypoint; no args defaults to serve |
Site::serve | build and directly serve HTTP, ignoring commands |
Site::test | build a test site with an explicit SQLx pool |
site.start | serve an already-built site |
Use Site::run for ordinary application binaries:
use vyuh::prelude::*;
#[tokio::main]
async fn main() -> Result<(), vyuh::SiteError> {
let bundle = bundles::bundle! {
// routes, services, tasks, signals, assets, commands
};
vyuh::Site::run(SiteConf::from_env_with_files()?, bundle).await
}
Use Site::serve when a binary should ignore commands and only serve HTTP:
#![allow(unused)]
fn main() {
vyuh::Site::serve(SiteConf::from_env_with_files()?, app_bundle()).await?;
}
Use Site::build when the caller needs the site before serving, for example to
inspect configuration, run setup code, or pass the built site to another
runtime:
#![allow(unused)]
fn main() {
let site = vyuh::Site::build(conf, bundle).await?;
site.start().await?;
}
When arguments are supplied, Site::run executes the requested command:
#[tokio::main]
async fn main() -> Result<(), vyuh::SiteError> {
vyuh::Site::run(vyuh::SiteConf::from_env_with_files()?, app_bundle()).await
}
During build, Vyuh validates configuration and bundles, builds the router, creates the database pool, loads templates, initializes services, registers OpenAPI endpoints, prepares task stores when tasks are present, and starts background engines.
Using Site In Handlers
Handlers can extract Site directly:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
#[bundles::route(path = "/health")]
async fn health(site: Site) -> Json<String> {
Json(site.timezone().to_string())
}
}
Prefer subsystem handles for subsystem-specific work:
#![allow(unused)]
fn main() {
let db = site.db();
let templates = site.templates();
let tasks = site.tasks();
let auth = site.auth();
let counter = site.service::<CounterService>()?;
}
Task submission should go through site.tasks().submit(...) or
site.tasks().submit_with(...). Template rendering should usually go through
site.templates().render(...) or the Templates route extractor.
Error Rendering
Route parse errors, validation errors, auth failures, database errors, template
errors, and application vyuh::Error values are normalized into ErrorReport
before they are rendered. The default response is JSON-first. See
Errors for the application/subsystem/rendered error model.
Applications can replace error rendering with SiteConf::errors(...):
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::errors::ErrorConf;
let conf = SiteConf::default().errors(
ErrorConf::default().handler(|ctx, report| async move {
(
report.status,
[("content-type", "application/json")],
serde_json::json!({
"path": ctx.path,
"code": report.code,
"detail": report.detail,
})
.to_string(),
)
.into_response()
}),
);
}
The handler is async and receives request context plus the normalized report, so applications can render templates, add headers, or choose a different content type.
Routing And Reverse URLs
Raw Axum router access is intentionally not part of the normal application
lifecycle. Use Site::serve or site.start() for serving. Use
vyuh::testing::router(&site) only for tests or interop that truly needs an Axum
Router.
Named routes can be reversed through Site::reverse:
#![allow(unused)]
fn main() {
let url = site.reverse("user_detail", &[("id", "42")]);
}
reverse returns None when the route name or required parameters do not
match a registered route.
Testing
Use Site::test when a test should build the real site with a caller-provided
SQLx pool:
#![allow(unused)]
fn main() {
#[sqlx::test]
async fn route_works(pool: vyuh::db::Pool) -> Result<(), vyuh::SiteError> {
let site = vyuh::Site::test(vyuh::SiteConf::default(), app_bundle(), pool).await?;
let app = vyuh::testing::router(&site);
Ok(())
}
}
For route-level tests, build a site and send requests through
vyuh::testing::TestClient or vyuh::testing::router(&site). Use
.log_init(false) in tests when test output should stay quiet.
Shutdown
Site owns a shared shutdown notifier. Long-lived service workers and other
background loops should observe site.shutdown_notifier() and exit when it is
notified.
#![allow(unused)]
fn main() {
let shutdown = site.shutdown_notifier();
tokio::select! {
_ = shutdown.notified() => {}
_ = do_work() => {}
}
}
Site::serve and site.start() install bounded graceful server shutdown. The
first Ctrl+C starts graceful shutdown and prints a message explaining that a
second Ctrl+C will force shutdown. SIGTERM, touch-reload, and
site.shutdown() also start graceful shutdown. If active requests do not drain
within conf.http.shutdown.grace_period_ms, Vyuh forces server shutdown and
returns from Site::serve or site.start().
Channel transports are shutdown-aware: SSE streams end, WebSockets close, and
long-poll requests return promptly when shutdown starts. Long-lived service
workers should still select on site.shutdown_notifier() so they can stop
their own work cleanly before the grace period expires.
shutdown_and_wait() can be used by tests or embedding code that needs to
notify background tasks and abort remaining join handles.
Failure Modes
- Invalid configuration returns
SiteError::ConfError. - Database pool setup returns
SiteError::DatabaseError. - Bundle validation and duplicate registration errors return
SiteError::BundleError. - Template loading errors return
SiteError::TemplateError. - Service construction errors return
SiteError::ServiceError. - Task store migration errors return
SiteError::TaskMigrationError. - Server bind or runtime errors return
SiteError::IOErrororSiteError::ServeError.
Current Limitations
Siteis an in-process application handle, not a distributed coordinator.- Background engines are tied to the process that built the site.
Site::testuses the supplied pool but does not replace application-level schema setup; tests still need the schema their routes and services expect.