Runtime APIs

Read environment variables and arguments

Read configuration from environment files and accept command-line arguments without hard-coding changing values.

Applications need values that change between machines. The port on your laptop is not the port in production. The database path is different. API tokens are different, and secret. None of this belongs in the source code.

The usual answer is environment variables, values the operating system hands to a process when it starts. Bun reads them, and it also reads .env files automatically, so you don’t need a package like dotenv.

Create .env in the project root:

PORT=3000
DATABASE_PATH=notes.sqlite

Read those values through Bun.env:

const port = Number(Bun.env.PORT ?? 3000)
const databasePath = Bun.env.DATABASE_PATH ?? 'notes.sqlite'

console.log({ port, databasePath })

Run bun index.ts and you get:

{
  port: 3000,
  databasePath: "notes.sqlite",
}

process.env works too, and it’s the same data. I use Bun.env in Bun-only code and process.env in code that also has to run on Node.

Every value in Bun.env is a string or undefined. That’s why we wrap PORT in Number(). Be careful here. If someone sets PORT=three by mistake, Number() returns NaN, and the server fails to start with a confusing message. Validate what you read:

if (Number.isNaN(port)) {
  throw new Error(`PORT must be a number, got "${Bun.env.PORT}"`)
}

A clear error at startup beats a strange one ten minutes later.

Bun also loads .env.local, and .env.development or .env.production depending on NODE_ENV. Values already set in the shell win over the files.

Do not commit secrets in .env. Add the file to .gitignore, and provide an .env.example with the variable names and safe placeholder values, so a new teammate knows what to fill in.

Read command-line arguments

Environment variables are for configuration. For a small tool or a maintenance task, you want to pass a value for this one run. That’s what arguments are for.

Create greet.ts:

const name = Bun.argv[2] ?? 'friend'

console.log(`Hello ${name}`)

Pass the name after the file:

bun greet.ts Flavio

The output is:

Hello Flavio

Run it without a name and you get Hello friend, because of the fallback.

Why index 2? Bun.argv is an array. The first entry is the path to the Bun executable, the second is the script. Your own arguments start at index 2, exactly like process.argv in Node.

Keep the two roles separate. Environment variables describe how the application runs. Arguments describe what one execution should do. Mix them up and your commands become hard to read and harder to reproduce.

Lesson completed