This episode tidies up application runtime: loading configuration from environment variables and the .env file, distinguishing NODE_ENV, structured logging with pino, and understanding log levels for production. All of these are requirements for a deployable application.

An application that only runs on its developer's laptop isn't really an application yet. Real applications run in many environments — local, staging, production — each with different configuration: port, database connection, secret keys. This configuration must not be hardcoded.
Episode 12 covers three pillars of runtime configuration: environment variables and the .env file, distinguishing NODE_ENV per environment, and structured logging with pino. After this episode, your application is ready for its journey toward production.
An environment variable is a name-value pair injected into a process. Node.js exposes them through process.env. Secret values like JWT_SECRET from episode 11 and DATABASE_URL live here — not in the code.
The easiest way to manage them during development is the .env file:
node --env-file=.env app.jsThe flag node --env-file=.env app.js makes Node.js read the .env file and export it into process.env — with no extra packages. Add .env to .gitignore so secrets never enter the repository.
const port = Number(process.env.PORT ?? 3000);
const nama = process.env.APP_NAME ?? "belajar-nodejs";
console.log(nama, port);The pattern process.env.PORT ?? 3000 uses a default when the variable isn't set. The Number(...) conversion matters because all process.env values are strings. The nullish operator ?? only replaces null or undefined, so an empty string is still respected.
NODE_ENV is a convention that tells the application its current environment: development, test, or production. Many frameworks use its value to enable or disable features:
NODE_ENV=production node app.js
NODE_ENV=test node --testNODE_ENV=production node app.js makes the application run in production mode — Express disables the stack trace details that leak in error responses. In development, the default value is development when not explicitly set.
if (process.env.NODE_ENV === "production") {
app.set("trust proxy", 1);
}Use NODE_ENV for behavioral differences, not for storing secrets — secrets stay in their own process.env variables. The example above only enables trust proxy in production, because forged proxy headers are dangerous when directly exposed to the internet.
console.log emits text that's hard for machines to parse. In production, you need structured logging — every log line is JSON that can be filtered, collected, and queried. pino is the most popular and very fast choice:
npm install pinonpm install pino adds a logger with JSON support and log levels. pino is very fast because it uses optimized serialization — important for servers serving thousands of requests.
import pino from "pino";
const logger = pino({ level: process.env.LOG_LEVEL ?? "info" });
logger.info("Server mulai");
logger.info({ userId: 42 }, "Pengguna masuk");
logger.error({ err: new Error("koneksi gagal") }, "Database tidak merespons");pino({ level: process.env.LOG_LEVEL ?? "info" }) creates a logger with the default level info. Notice the difference in forms: logger.info({ userId: 42 }, "Pesan") attaches structured context, and logger.error({ err: ... }, "...") includes the full error object with a stack trace — far more useful for debugging than plain text.
pino follows the standard log levels, from quietest to most detailed: fatal, error, warn, info, debug, trace. The principle:
error and fatal: things that need immediate attention.warn: abnormal but not fatal conditions.info: normal events like the server starting.debug: development details, usually turned off in production.Set the level via LOG_LEVEL — production is usually info or warn, development is often debug. Too much logging drowns the important signals.
Don't create several loggers with different configurations. Create a single logger module and import it across the application, so the level and format stay consistent. In episode 22, these JSON logs will be streamed directly into observability systems like Loki or OpenSearch.
Here's what to take away:
process.env, not in code.node --env-file=.env loads a .env file without extra packages.NODE_ENV distinguishes development, test, and production.console.log with fast serialization.fatal to trace; tune LOG_LEVEL per environment.In the next episode, episode 13, we'll discuss relational vs NoSQL databases in Node.js — SQL characteristics and ACID, the document model of NoSQL, choosing factors based on data and scenarios, and how both are used from Node.js code. This opens the data phase of this series.