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 features::hello::HelloHttpModule;
#[module( imports = [ 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, a 2 MiB request-body cap, a 30-second
request timeout, fail-secure boot on, and the default security headers on.
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() } |
global_prefix | NESTRS_HTTP__GLOBAL_PREFIX | unset ⇒ no prefix | HttpConfig::default().with_global_prefix("/api") |
max_body_bytes | NESTRS_HTTP__MAX_BODY_BYTES (bytes) | 2 MiB | HttpConfig::default().with_max_body_bytes(4 * 1024 * 1024) |
request_timeout_secs | NESTRS_HTTP__REQUEST_TIMEOUT_SECS (seconds) | 30 | HttpConfig { request_timeout_secs: Some(15), ..Default::default() } |
fail_secure_strict | NESTRS_HTTP__FAIL_SECURE_STRICT (true/false) | true | HttpConfig { fail_secure_strict: false, ..Default::default() } |
security_headers | NESTRS_HTTP__SECURITY_HEADERS (master), __FRAME_OPTIONS, __HSTS, __CONTENT_TYPE_OPTIONS | on (safe values) | HttpConfig { security_headers: SecurityHeadersConfig { enabled: false, ..Default::default() }, ..Default::default() } |
compression | NESTRS_HTTP__COMPRESSION (true/false) | false | HttpConfig { compression: true, ..Default::default() } |
See Compression for what the flag negotiates and when to leave it off.
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. Like every other field it follows the dual path — set it in the
environment:
NESTRS_HTTP__GLOBAL_PREFIX=/apior pin it in code with with_global_prefix:
use nest_rs_http::{HttpConfig, HttpModule};
#[module(imports = [ HttpModule::for_root(HttpConfig::default().with_global_prefix("/api")),])]pub struct ApiModule;The prefix is normalized ("api", "/api", "/api/" all yield
Some("/api"); empty / "/" collapse to no-op), then prepended 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.
Request-body size limit
Section titled “Request-body size limit”RawBody (and every extractor built on it) accepts at most
max_body_bytes — 2 MiB by default, so a runaway upload can’t exhaust
memory before a handler ever sees it. Raise or lower it globally:
NESTRS_HTTP__MAX_BODY_BYTES=4194304 # 4 MiBA single route that needs a different cap overrides it per call with
RawBody::extract_with_limit; the config value is the default for
everything else.
Request timeout
Section titled “Request timeout”request_timeout_secs bounds how long one request may run. A handler
that exceeds it is aborted and the client gets 504 Gateway Timeout, so a
slow or stuck request can’t tie up a connection indefinitely. Default
30; set None (env unset with the field pinned to None) to disable:
NESTRS_HTTP__REQUEST_TIMEOUT_SECS=15Default security headers
Section titled “Default security headers”On by default — a freshly-scaffolded app ships safe headers without having to remember them:
X-Content-Type-Options: nosniff— defeats MIME sniffing.X-Frame-Options: DENY— no framing (clickjacking).Strict-Transport-Security: max-age=31536000; includeSubDomains— emitted only when TLS is active (HSTS over plain HTTP is meaningless and a footgun on localhost).
Every value is tunable through the dual path. Disable the whole set with the master switch, or drop one header by setting its value to an empty string:
NESTRS_HTTP__SECURITY_HEADERS=false # all offNESTRS_HTTP__FRAME_OPTIONS=SAMEORIGIN # override one valueNESTRS_HTTP__HSTS= # empty ⇒ drop just HSTSuse nest_rs_http::{HttpConfig, HttpModule, SecurityHeadersConfig};
HttpModule::for_root(HttpConfig { security_headers: SecurityHeadersConfig { frame_options: Some("SAMEORIGIN".into()), ..Default::default() }, ..Default::default()});Fail-secure boot
Section titled “Fail-secure boot”fail_secure_strict is true by default: when global guards are
registered and an endpoint the transport can’t shape (an imperative
mount(...)) would bypass the guard pool, the boot fails naming the
offending mount rather than silently serving it unguarded. Setting it to
false downgrades that failure to a warn — a deliberate opt-out, not
the default:
NESTRS_HTTP__FAIL_SECURE_STRICT=falseGoing further
Section titled “Going further”- Controllers & routes — write the handlers this transport serves.
- Configuration — the framework-wide
NESTRS_<NS>__*scheme and the.envcascade.
Built by YV17labs