Configuration Structure
Main Configuration
src/config/mod.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub server: ServerConfig,
pub db_postgres: PostgresConfig,
pub db_mysql: Option<MySqlConfig>,
pub mongodb: Option<MongoDBConfig>,
pub jwt: JwtConfig,
pub bcrypt: BcryptConfig,
}
Server Configuration
src/config/mod.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub env: String,
}
Database Configuration
PostgreSQLsrc/config/mod.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostgresConfig {
pub postgres_url: String,
pub max_connections: u32,
pub min_connections: u32,
pub acquire_timeout: u64,
pub idle_timeout: u64,
}
src/config/mod.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MySqlConfig {
pub mysql_url: String,
pub max_connections: u32,
pub min_connections: u32,
pub acquire_timeout: u64,
pub idle_timeout: u64,
}
src/config/mod.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MongoDBConfig {
pub mongo_url: String,
pub database_name: String,
}
Security Configuration
JWTsrc/config/mod.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtConfig {
pub secret: String,
pub expiration: i64,
}
src/config/mod.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BcryptConfig {
pub cost: u32,
}
Loading Configuration
From Environment Variables
src/config/mod.rs
impl AppConfig {
pub fn from_env() -> Result<Self> {
dotenv::dotenv().ok();
let config = AppConfig {
server: ServerConfig {
host: env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
port: env::var("SERVER_PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse()?,
env: env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string()),
},
db_postgres: PostgresConfig {
postgres_url: env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgresql://user:password@localhost/template_db".to_string()),
max_connections: env::var("DB_MAX_CONNECTIONS")
.unwrap_or_else(|_| "5".to_string())
.parse()?,
min_connections: env::var("DB_MIN_CONNECTIONS")
.unwrap_or_else(|_| "1".to_string())
.parse()?,
acquire_timeout: env::var("DB_ACQUIRE_TIMEOUT")
.unwrap_or_else(|_| "5".to_string())
.parse()?,
idle_timeout: env::var("DB_IDLE_TIMEOUT")
.unwrap_or_else(|_| "300".to_string())
.parse()?,
},
jwt: JwtConfig {
secret: env::var("JWT_SECRET")
.unwrap_or_else(|_| "your-secret-key-change-in-production".to_string()),
expiration: env::var("JWT_EXPIRATION")
.unwrap_or_else(|_| "86400".to_string())
.parse()?,
},
bcrypt: BcryptConfig {
cost: env::var("BCRYPT_COST")
.unwrap_or_else(|_| {
match env::var("ENVIRONMENT").as_deref() {
Ok("production") => "12",
Ok("staging") => "10",
_ => "8",
}
.to_string()
})
.parse()
.unwrap_or(10),
},
// Optional databases
db_mysql: if let Ok(mysql_url) = env::var("MYSQL_URL") {
Some(MySqlConfig { /* ... */ })
} else {
None
},
mongodb: if let Ok(mongo_url) = env::var("MONGODB_URL") {
Some(MongoDBConfig { /* ... */ })
} else {
None
},
};
Ok(config)
}
}
In Application Startup
src/main.rs
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Load configuration
let app_config = AppConfig::from_env().expect("Failed to load config");
// Configure logging
tracing_subscriber::fmt()
.without_time()
.with_target(false)
.with_env_filter(
tracing_subscriber::EnvFilter::new("info,actix_server=warn,ironclads=debug")
)
.init();
tracing::info!("Server: http://{}:{}", app_config.server.host, app_config.server.port);
// Validate security configuration
validate_security_config(&app_config);
// Initialize database
let pg_pool = db::postgres::init_pool(&app_config.db_postgres)
.await
.expect("Failed to initialize PostgreSQL pool");
// Start server
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(app_config.clone()))
// ... middleware and routes
})
.bind(&address)?
.run()
.await
}
Environment Variables
.env File Example
.env
# Server Configuration
SERVER_HOST=127.0.0.1
SERVER_PORT=8080
ENVIRONMENT=development
# PostgreSQL Database
DATABASE_URL=postgresql://user:password@localhost:5432/template_db
DB_MAX_CONNECTIONS=5
DB_MIN_CONNECTIONS=1
DB_ACQUIRE_TIMEOUT=5
DB_IDLE_TIMEOUT=300
# MySQL Database (optional)
MYSQL_URL=mysql://user:password@localhost:3306/template_db
MYSQL_MAX_CONNECTIONS=5
MYSQL_MIN_CONNECTIONS=1
MYSQL_ACQUIRE_TIMEOUT=5
MYSQL_IDLE_TIMEOUT=300
# MongoDB (optional)
MONGODB_URL=mongodb://localhost:27017
MONGODB_NAME=template_db
# JWT Authentication
JWT_SECRET=your-secret-key-change-in-production
JWT_EXPIRATION=86400
# Bcrypt Password Hashing
BCRYPT_COST=8
Environment-Specific Configuration
Development (.env.development)ENVIRONMENT=development
SERVER_HOST=127.0.0.1
SERVER_PORT=8080
BCRYPT_COST=8
RUST_LOG=debug
ENVIRONMENT=staging
SERVER_HOST=0.0.0.0
SERVER_PORT=8080
BCRYPT_COST=10
RUST_LOG=info
ENVIRONMENT=production
SERVER_HOST=0.0.0.0
SERVER_PORT=8080
JWT_SECRET=<strong-random-secret-at-least-32-chars>
BCRYPT_COST=12
RUST_LOG=warn
Configuration Validation
Security Validation
src/config/validators.rs
pub fn validate_security_config(config: &AppConfig) {
validate_bcrypt_cost(config);
validate_jwt_config(config);
}
Bcrypt Cost Validation
src/config/validators.rs
fn validate_bcrypt_cost(config: &AppConfig) {
if config.bcrypt.cost < 4 {
tracing::error!(
"🔴 BCRYPT_COST={} is CRITICALLY LOW for security (minimum: 4)",
config.bcrypt.cost
);
}
match config.server.env.as_str() {
"production" => validate_production_bcrypt(config),
"staging" => validate_staging_bcrypt(config),
"development" => validate_development_bcrypt(config),
_ => {
tracing::warn!("⚠️ Unknown environment: {}", config.server.env);
}
}
}
fn validate_production_bcrypt(config: &AppConfig) {
if config.bcrypt.cost < 10 {
tracing::error!(
"🔴 BCRYPT_COST={} is TOO LOW for production (minimum: 10, recommended: 12)",
config.bcrypt.cost
);
std::process::exit(1); // Block production deployment
} else if config.bcrypt.cost >= 12 {
tracing::info!("✅ BCRYPT_COST={} is IDEAL for production", config.bcrypt.cost);
}
}
JWT Validation
src/config/validators.rs
fn validate_jwt_config(config: &AppConfig) {
if config.server.env == "production" {
if config.jwt.secret.len() < 32 {
tracing::error!("🔴 JWT_SECRET is too short for production (minimum: 32 characters)");
std::process::exit(1);
}
if config.jwt.secret.contains("change") || config.jwt.secret.contains("secret") {
tracing::error!("🔴 JWT_SECRET appears to be a default value - change it!");
std::process::exit(1);
}
tracing::info!("✅ JWT configuration is secure for production");
}
}
Configuration Best Practices
Environment-Based DefaultsThe framework automatically adjusts security settings based on environment:
- Development:
BCRYPT_COST=8(fast, less secure) - Staging:
BCRYPT_COST=10(balanced) - Production:
BCRYPT_COST=12(secure, slower)
Production Security RequirementsThe application will exit on startup if production settings are insecure:
BCRYPT_COSTmust be ≥ 10 (recommended: 12)JWT_SECRETmust be ≥ 32 charactersJWT_SECRETcannot contain “change” or “secret”
Generating Secure Secrets
# Generate 32-byte random secret (Linux/Mac)
openssl rand -base64 32
# Generate 32-byte random secret (PowerShell)
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 }))
# Example output
R8vQ2x9mP4kL7nW5tY8jH1oU6zE3bN0c
Accessing Configuration
In Application State
use actix_web::web;
use crate::config::AppConfig;
pub async fn handler(
config: web::Data<AppConfig>,
) -> Result<HttpResponse, ApiError> {
let jwt_expiration = config.jwt.expiration;
let environment = &config.server.env;
// Use configuration
Ok(HttpResponse::Ok().json(json!({
"environment": environment,
"token_expiration": jwt_expiration,
})))
}
In Services
use crate::config::AppConfig;
pub struct AuthService {
config: AppConfig,
}
impl AuthService {
pub fn new(config: AppConfig) -> Self {
Self { config }
}
pub fn generate_token(&self, user_id: Uuid) -> Result<String, ApiError> {
let expiration = chrono::Utc::now()
.checked_add_signed(chrono::Duration::seconds(self.config.jwt.expiration))
.unwrap();
// Generate token with configured expiration
// ...
}
}
Database Connection Pooling
PostgreSQL Pool Configuration
use sqlx::postgres::{PgPoolOptions, PgPool};
use crate::config::PostgresConfig;
pub async fn init_pool(config: &PostgresConfig) -> Result<PgPool, sqlx::Error> {
PgPoolOptions::new()
.max_connections(config.max_connections)
.min_connections(config.min_connections)
.acquire_timeout(std::time::Duration::from_secs(config.acquire_timeout))
.idle_timeout(std::time::Duration::from_secs(config.idle_timeout))
.connect(&config.postgres_url)
.await
}
Connection Pool Best Practices
Pool Size Guidelines
- Development: 2-5 connections
- Staging: 5-10 connections
- Production: 10-50 connections (based on load)
max_connections = (CPU cores * 2) + effective_disk_spindlesConfiguration Testing
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bcrypt_cost_validation() {
let config = AppConfig {
server: ServerConfig {
env: "production".to_string(),
// ...
},
bcrypt: BcryptConfig { cost: 8 },
// ...
};
// This should fail validation
let result = std::panic::catch_unwind(|| {
validate_bcrypt_cost(&config);
});
assert!(result.is_err());
}
#[test]
fn test_jwt_secret_length() {
let config = AppConfig {
jwt: JwtConfig {
secret: "short".to_string(),
expiration: 86400,
},
server: ServerConfig {
env: "production".to_string(),
// ...
},
// ...
};
assert!(config.jwt.secret.len() < 32);
}
}
Environment-Specific Features
Conditional Middleware
use crate::config::AppConfig;
HttpServer::new(move || {
let mut app = App::new();
// Add debug middleware only in development
if app_config.server.env == "development" {
app = app.wrap(actix_web::middleware::Logger::default());
}
// Add strict security headers in production
if app_config.server.env == "production" {
app = app.wrap(actix_web::middleware::DefaultHeaders::new()
.add(("X-Content-Type-Options", "nosniff"))
.add(("X-Frame-Options", "DENY")));
}
app
})
Troubleshooting
Configuration not loading
Configuration not loading
Problem: Environment variables not loadingSolution: Ensure
.env file is in project root# Check .env file location
ls -la .env
# Test loading
dotenv -- cargo run
Production startup fails
Production startup fails
Problem: Application exits on startup in productionSolution: Check security validation errors
# Logs will show:
# 🔴 BCRYPT_COST=8 is TOO LOW for production
# 🔴 JWT_SECRET is too short for production
# Fix in .env:
BCRYPT_COST=12
JWT_SECRET=<generate-32-char-secret>
Database connection fails
Database connection fails
Problem: Cannot connect to databaseSolution: Verify connection string format
# PostgreSQL
DATABASE_URL=postgresql://username:password@host:port/database
# MySQL
MYSQL_URL=mysql://username:password@host:port/database
# MongoDB
MONGODB_URL=mongodb://host:port
Next Steps
Logging
Configure structured logging
Deployment
Deploy your application