Environment Variables & Secrets
Manage database credentials, API tokens, and runtime bindings securely with Dinou's server-only isolation model across Node.js, Bun, Deno, and Cloudflare Workers.
Environment Variables in Dinou
Dinou isolates all environment variables strictly to the server runtime. Values are never automatically bundled, replaced, or inlined into client-side JavaScript chunks, ensuring secrets like database passwords or private API keys remain 100% on the server.
process.env into client chunks. Secrets never reach the browser.process.env across Node, Bun, and Deno, and getContext().env for Cloudflare Workers bindings..env files.The Server-Only Security Model
In Dinou, Client Components cannot access process.env directly. If a client component needs a configuration value (such as a public analytics ID or Stripe publishable key), pass it explicitly as a prop from a Server Component.
Where Environment Variables Work
You can safely access environment variables in any server-side execution context:
Read database URLs or CMS tokens while rendering initial HTML on the server.
Access private payment APIs, database pools, and hashing secrets in RPC mutations.
Fetch data before page execution in page_functions.ts using backend keys.
Read environment variables (such as PORT or service keys) in dinou.config.mjs and plugins.
// src/server-functions/checkout.ts
"use server";
import Stripe from "stripe";
// Securely read secret key inside Server Function
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2023-10-16",
});
export async function createCheckoutSession(amount: number) {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [{ price_data: { currency: "usd", unit_amount: amount }, quantity: 1 }],
mode: "payment",
success_url: `${process.env.APP_URL}/success`,
cancel_url: `${process.env.APP_URL}/cancel`,
});
return session.url;
}Safe Propagation to Client Components
When a Client Component requires a public configuration value (such as a public client ID or feature flag), pass it down through props from an enclosing Server Component:
// src/page.tsx (Server Component)
import PaymentWidget from "./components/payment-widget"; // Client Component
export default function CheckoutPage() {
// Explicitly extract ONLY the safe public key to send to the browser
const publishableKey = process.env.STRIPE_PUBLISHABLE_KEY;
return (
<main className="container max-w-xl mx-auto py-8">
<h1>Complete Your Order</h1>
<PaymentWidget publishableKey={publishableKey} />
</main>
);
}By making data transfer across the server-client boundary explicit, code reviews immediately flag any sensitive secrets inadvertently sent to the client.
Defining .env Files
In local development, Dinou loads variables from your project root .env file:
# .env (Local development template)
NODE_ENV=development
PORT=3000
# Database
DATABASE_URL="postgresql://postgres:password@localhost:5432/my_app_dev"
# Third-party credentials
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_PUBLISHABLE_KEY="pk_test_..."
GITHUB_CLIENT_ID="Iv1..."
GITHUB_CLIENT_SECRET="ghs_..."
# Application URLs
APP_URL="http://localhost:3000"Security Best Practices
- Never commit .env to git: Always ensure
.envand.env.*are in your.gitignorefile (except.env.example). - Commit a .env.example: Maintain a clean
.env.examplecontaining required variable names with placeholder values to onboard new team members. - Production Environments: In production (Fly.io, Railway, AWS, Docker), inject secrets via the host provider's native environment configuration dashboard rather than shipping a file.
Multi-Platform Runtimes
Dinou is runtime-agnostic. For standard environment variables and secrets (like DATABASE_URL or API_SECRET), you can write process.env uniformly across all deployment targets:
Node.js & Bun (process.env)
In Node.js and Bun server adapters, environment variables reside natively on the global process.env object:
const dbUrl = process.env.DATABASE_URL;
const port = Number(process.env.PORT || 3000);Bun also provides Bun.env as an alias, but process.env is fully supported across both runtimes.
Cloudflare Workers (process.env & Bindings)
In Cloudflare Workers, standard text secrets and environment variables configured in wrangler.toml or the Cloudflare dashboard are available via process.env (when compatibility_flags = ["nodejs_compat"] is enabled) or via getContext().env:
By definition, .env files and process.env only store plain text strings. However, Cloudflare also provides Resource Bindings (live JavaScript instances with async methods for KV, D1 SQL databases, and R2 buckets) through its runtime env object. In Dinou, you can access both text secrets and live resource bindings uniformly anywhere on the server via getContext().env.
import { getContext } from "dinou";
// Inside page_functions.ts (getProps), Server Components, or Server Functions:
export async function getProps(params) {
// 1. Text secrets (work via process.env or getContext().env):
const apiSecret = process.env.MY_API_SECRET;
// 2. Cloudflare Resource Bindings (KV, D1, R2 live objects):
const { env } = getContext();
const kv = env?.MY_KV_NAMESPACE;
const user = kv ? await kv.get("user:123") : null;
return { user };
}Inside Server Functions ("use server"), call getContext() directly to access D1 databases (env.DB) or KV namespaces for database queries and mutations.
Deno (process.env & --allow-env)
In Deno, environment variable access is protected by Deno's security sandbox. When launching Dinou on Deno, enable the --allow-env permission flag:
deno run --allow-net --allow-read --allow-write --allow-env dinou/adapters/deno.jsWith --allow-env active, Deno's native Node compatibility allows you to read variables via process.env.KEY uniformly, or via Deno.env.get("KEY").
Type-Safe Environment Validation (Zod)
Missing environment variables in production are notorious for causing silent failures or runtime crashes. We recommend validating your environment variables at application startup using a lightweight schema validator such as Zod:
// src/lib/env.ts
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url("DATABASE_URL must be a valid connection string"),
STRIPE_SECRET_KEY: z.string().min(1, "STRIPE_SECRET_KEY is required"),
STRIPE_PUBLISHABLE_KEY: z.string().min(1, "STRIPE_PUBLISHABLE_KEY is required"),
});
// Parse and validate process.env
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error("❌ Invalid environment variables:", parsed.error.format());
throw new Error("Invalid configuration. Check terminal output for details.");
}
export const env = parsed.data;Now you can import the validated env object in your Server Components and Server Functions with full TypeScript autocompletion and compile-time type safety:
// Full TypeScript autocomplete and guaranteed presence:
import { env } from "@/lib/env";
const db = connectToDatabase(env.DATABASE_URL);Anti-Leak Security & Bot Shield
Malicious web crawlers and vulnerability scanners frequently search for exposed .env files on web servers by issuing requests like:
GET /.env
GET /api/.env
GET /public/.envDinou includes a built-in Anti-Bot Shield in dinou/core/handler.js. The framework automatically matches incoming request paths against common bot attack patterns (including /\.env$/i, /\.git\b/i, /\.sql$/i, etc.) and immediately terminates them before any static file resolver or server route can be triggered.
Production Deployment Checklist:
- Verify
.envis listed in.gitignoreand.dockerignore. - Configure platform secrets via your host's dashboard or CLI (e.g.
railway variables set,fly secrets set,wrangler secret put). - Ensure sensitive API credentials are only referenced inside Server Components or Server Functions.
- Use schema validation (like Zod) to catch missing variables immediately on server boot.