Configuration
Importing HttpModule::for_root(...) in AppModule.imports attaches the
HTTP transport at boot. Pass None to read every option from the
environment, or pin HttpConfig in code:
use nest_rs_core::module;use nest_rs_http::{HttpConfig, HttpModule};use nest_rs_opentelemetry::OpenTelemetryModule;
use features::hello::HelloHttpModule;
#[module( imports = [ OpenTelemetryModule, HttpModule::for_root(HttpConfig { port: 3000, ..Default::default() }), HelloHttpModule, ],)]pub struct HelloModule;use nest_rs_http::{HttpConfig, HttpModule};
#[module(imports = [ HttpModule::for_root(HttpConfig { port: 3002, ..Default::default() }),])]pub struct ApiModule;Default values: host 0.0.0.0, port 3000, no TLS (plain HTTP), no CORS,
no framework Server header.
HttpConfig fields
Section titled “HttpConfig fields”Every field of HttpConfig is settable both by env var and in the pinned
struct — the same dual-path rule every config in the framework follows.
Real env always wins over the .env cascade.
| Field | Env variable | Default | Pinned form |
|---|---|---|---|
host | NESTRS_HTTP__HOST | 0.0.0.0 | HttpConfig { host: "127.0.0.1".into(), ..Default::default() } |
port | NESTRS_HTTP__PORT | 3000 | HttpConfig { port: 3002, ..Default::default() } |
tls.cert | NESTRS_HTTP__TLS_CERT (inline PEM) or NESTRS_HTTP__TLS_CERT_FILE (path) | unset ⇒ plain HTTP | HttpConfig { tls: Some(TlsConfig::new(cert, key)), ..Default::default() } |
tls.key | NESTRS_HTTP__TLS_KEY or NESTRS_HTTP__TLS_KEY_FILE | unset ⇒ plain HTTP | (set together with tls.cert) |
cors.origins | NESTRS_HTTP__CORS_ORIGINS (comma list) | unset ⇒ CORS off | CorsConfig { origins: vec!["https://app.example.com".into()], ..Default::default() } |
cors.methods | NESTRS_HTTP__CORS_METHODS | empty | methods: vec!["GET".into(), "POST".into()] |
cors.headers | NESTRS_HTTP__CORS_HEADERS | empty | headers: vec!["Content-Type".into()] |
cors.exposed_headers | NESTRS_HTTP__CORS_EXPOSED | empty | exposed_headers: vec!["X-Total-Count".into()] |
cors.credentials | NESTRS_HTTP__CORS_CREDENTIALS (true/false) | false | credentials: true |
cors.max_age | NESTRS_HTTP__CORS_MAX_AGE (seconds) | unset | max_age: Some(Duration::from_secs(3600)) |
server_header | NESTRS_HTTP__SERVER_HEADER (true/false) | false | HttpConfig { server_header: true, ..Default::default() } |
Serve HTTPS
Section titled “Serve HTTPS”Setting both NESTRS_HTTP__TLS_CERT[_FILE] and NESTRS_HTTP__TLS_KEY[_FILE]
makes the transport serve over rustls (through
poem’s listener) instead of plain HTTP. Setting only one of the pair fails
the boot — a half-configured TLS is a deployment mistake, not a silent
fall back to plaintext.
# Inline (suits k8s secrets, systemd EnvironmentFile, …)NESTRS_HTTP__TLS_CERT="$(cat fullchain.pem)" \NESTRS_HTTP__TLS_KEY="$(cat privkey.pem)" \nestrs run dev api
# Or by path (the transport reads the file at boot)NESTRS_HTTP__TLS_CERT_FILE=/etc/letsencrypt/.../fullchain.pem \NESTRS_HTTP__TLS_KEY_FILE=/etc/letsencrypt/.../privkey.pem \nestrs run dev apiPinning TLS material in code uses TlsConfig::new — rarely useful outside
tests (production deploys carry secrets in the environment):
use nest_rs_http::{HttpConfig, HttpModule, TlsConfig};
let cert = std::fs::read("fullchain.pem")?;let key = std::fs::read("privkey.pem")?;
#[module(imports = [ HttpModule::for_root(HttpConfig { port: 3002, tls: Some(TlsConfig::new(cert, key)), ..Default::default() }),])]pub struct ApiModule;CORS uses poem’s Cors middleware
under the hood. The transport installs it outermost, so a preflight
(OPTIONS) is answered before any guard or interceptor runs.
CORS activates only when cors.origins is non-empty — the default is no
CORS layer. Set the origins (and any other knob you need) via either path:
NESTRS_HTTP__CORS_ORIGINS=https://app.example.com,https://admin.example.comNESTRS_HTTP__CORS_METHODS=GET,POST,PUT,DELETENESTRS_HTTP__CORS_HEADERS=Content-Type,AuthorizationNESTRS_HTTP__CORS_CREDENTIALS=trueNESTRS_HTTP__CORS_MAX_AGE=3600use std::time::Duration;use nest_rs_http::{CorsConfig, HttpConfig, HttpModule};
#[module(imports = [ HttpModule::for_root(HttpConfig { port: 3002, cors: Some(CorsConfig { origins: vec!["https://app.example.com".into()], methods: vec!["GET".into(), "POST".into()], headers: vec!["Content-Type".into(), "Authorization".into()], credentials: true, max_age: Some(Duration::from_secs(3600)), ..Default::default() }), ..Default::default() }),])]pub struct ApiModule;origins: vec!["*".into()] is allowed for fully open APIs (the wildcard
is passed straight through to poem).
Mounting under a shared prefix
Section titled “Mounting under a shared prefix”Behind a reverse proxy that hands off a sub-path (/api/*), every
controller can be mounted under one prefix without touching path = "…"
on each:
use nest_rs_core::App;use nest_rs_http::HttpTransport;use api::ApiModule;
#[tokio::main]async fn main() -> anyhow::Result<()> { App::builder() .module::<ApiModule>() .transport(HttpTransport::new().global_prefix("/api")) .build() .await? .run() .await}HttpTransport::global_prefix("/api") normalizes the input
("api", "/api", "/api/" all yield Some("/api"); empty / "/"
collapse to no-op), then prepends it to every route at mount time —
#[get("/users")] ends up at GET /api/users. The boot log and the
OpenAPI document reflect the prefix.
Framework Server: header
Section titled “Framework Server: header”Off by default — a production-safe choice: no fingerprint of the
framework or its version is exposed. Flip on for local development to see
Server: nestrs/<crate version> on every response (the same shape Apache
and nginx use):
NESTRS_HTTP__SERVER_HEADER=trueHttpModule::for_root(HttpConfig { server_header: true, ..Default::default()})The value is sourced from the nest-rs-http crate’s CARGO_PKG_VERSION at
build time — it tracks the framework, not your app version.
Going further
Section titled “Going further”- Controllers & routes — write the handlers this transport serves.
- Configuration — the framework-wide
NESTRS_<NS>__*scheme and the.envcascade.