Tutorial Part 1: Requests and Responses
This tutorial guides you through building a Polls application in Djangors, mirroring the structure of Django’s official tutorial. In Part 1, we will set up a new project, create our first HTTP view, configure routing, and launch the development server.
After the tutorial, use the Application Architecture guide to see how a larger feature is split into models, contracts, repositories, services, views, URLs, permissions, and tests.
Note
The source of truth for all code snippets in this tutorial is the working
examples/pollsproject in this repository.
1. Creating a New Project
To create a new Djangors project, run the dj new command provided by djangors-cli:
dj new polls
This sets up a standard Rust binary crate tailored for Djangors. The generated Cargo.toml specifies the required framework dependencies:
[package]
name = "polls"
description = "Polls app for Djangors"
version = "0.0.1"
edition = "2021"
publish = false
[lib]
name = "polls"
path = "src/lib.rs"
[[bin]]
name = "polls"
path = "src/main.rs"
[dependencies]
djangors-core = { path = "../../crates/djangors-core" }
djangors-orm = { path = "../../crates/djangors-orm" }
djangors-macros = { path = "../../crates/djangors-macros" }
djangors-db = { path = "../../crates/djangors-db" }
djangors-auth = { path = "../../crates/djangors-auth" }
djangors-admin = { path = "../../crates/djangors-admin" }
djangors-sessions = { path = "../../crates/djangors-sessions" }
tokio = { version = "1.53.0", features = ["full"] }
tower = { version = "0.5.3", features = ["util"] }
hyper = { version = "1.10.1", features = ["full"] }
chrono = { version = "0.4.45", features = ["std", "serde"] }
serde = { version = "1.0.228", features = ["derive"] }
async-trait = "0.1.86"
2. Writing Your First View
In Djangors, HTTP views are asynchronous functions that accept a Request and PathParams, returning a Result<Response, DjangorsError>.
Create src/views.rs and add an index view that returns a simple HTML string:
#![allow(unused)]
fn main() {
use djangors_core::{DjangorsError, PathParams, Request, Response, StatusCode};
pub async fn index(req: Request, _params: PathParams) -> Result<Response, DjangorsError> {
Ok(Response::html(
StatusCode::OK,
"Hello, world. You're at the polls index.".to_string(),
))
}
}
3. Configuring the Router
Next, map your view to a URL endpoint. Create src/urls.rs and define a function that constructs a Router:
#![allow(unused)]
fn main() {
mod views {
use djangors_core::{DjangorsError, PathParams, Request, Response, StatusCode};
pub async fn index(_: Request, _: PathParams) -> Result<Response, DjangorsError> {
Ok(Response::html(StatusCode::OK, ""))
}
}
use djangors_core::Router;
pub fn urls() -> Router {
Router::new().get("/", views::index)
}
}
Expose the URL module in src/lib.rs:
#![allow(unused)]
fn main() {
pub mod admin;
pub mod models;
pub mod urls;
pub mod views;
}
4. Setting Up the Server Entrypoint
In src/main.rs, initialize dev logging, load environment settings, construct the Tower middleware pipeline, and start the HTTP server:
mod polls {
pub mod urls {
use djangors_core::Router;
pub fn urls() -> Router { Router::new() }
}
}
use djangors_core::{Djangors, DjangorsError, DjangorsSettings, Router};
use polls::urls;
#[tokio::main]
async fn main() -> Result<(), DjangorsError> {
djangors_core::logging::init_dev_logging();
let (settings, warnings) = DjangorsSettings::load()?;
for w in warnings {
eprintln!("settings warning: {w}");
}
let db_url = match std::env::var("DATABASE_URL") {
Ok(url) => url,
Err(_) => {
eprintln!("DATABASE_URL environment variable is not set");
std::process::exit(1);
}
};
let config = djangors_db::config::DatabaseConfig::new(db_url);
let db = djangors_db::Database::connect(&config)
.await
.map_err(|e| DjangorsError::Internal(e.to_string()))?;
let router = urls::urls().with_state(db);
let router_service = djangors_core::router::RouterService::new(router, settings.debug);
let secret_key = if settings.secret_key.is_empty() {
"dev-only-secret-key-at-least-32-bytes-long-for-signing-cookies".to_string()
} else {
settings.secret_key.clone()
};
let service = tower::ServiceBuilder::new()
.layer(djangors_core::middleware::security_headers_layer())
.layer(djangors_sessions::SessionLayer::new(
djangors_sessions::SignedCookieStore::new(secret_key.as_bytes()),
))
.layer(djangors_core::middleware::csrf_layer())
.service(router_service);
Djangors::new(settings, Router::new())
.run_service(service)
.await
}
What’s Real vs. What Django Has That Djangors Doesn’t Yet
Important
Key Architecture Differences from Django:
- Async by Default: Djangors handlers are native Rust
async fnsignatures built on Tokio and Hyper/Tower.- Typed Signatures: Views take
(Request, PathParams)and explicitly returnResult<Response, DjangorsError>.- Explicit Router Chain: Routes are chained on
Router::new()using HTTP verb methods (.get(),.post()), rather than Python lists ofpath()objects.- Middleware Stack: Middleware is composed using standard Rust
tower::ServiceBuilderlayers rather than string class paths insettings.py.
Running the Dev Server
To launch your development server, set the database connection string and run dj run (or cargo run):
DATABASE_URL="postgres://postgres:postgres@localhost/djangors_dev" dj run --port 8000
Navigate to http://localhost:8000/ in your browser or curl it to confirm:
curl http://localhost:8000/
# Output: Hello, world. You're at the polls index.