.env là gì? process.env cơ bản cho NestJS và Next.js junior

(Cập nhật: 19 tháng 7, 2026 ) .env process.env NestJS Next.js dotenv môi trường junior
Kết luận

.env là file cấu hình local; process.env là nơi app thật sự đọc giá trị. Next.js tự load .env*; NestJS cần ConfigModule/dotenv. Client Next chỉ thấy NEXT_PUBLIC_*. Junior nên: copy .env.example.env, hiểu ai load file, rồi mới debug undefined. Khi example lộn A–Z, dùng .env Formatter trên trình duyệt — không upload secret.

Bạn clone repo Nest hoặc Next từ khách outsourcing, chạy npm run start:dev / next dev, và gặp Cannot read properties of undefined hoặc DB URL trống. Lead nói “thiếu env”. Bạn mở folder — có .env.example, không có .env, và cũng không rõ process.env lấy giá trị từ đâu.

Bài này dành cho junior NestJS / Next.js: .env là gì, process.env hoạt động ra sao, framework load file thế nào, và lỗi undefined phổ biến. Không thay bài best practices bảo mật hay bài format/sort trước PR — đây là nền tảng đọc biến trước khi nói vault hay convention diff.

Ai nên đọc

  • Sinh viên / fresher lần đầu thấy file .env trong monorepo
  • Freelancer nhận task Nest API hoặc Next App Router
  • Ai hay lẫn “file trên đĩa” với “object lúc runtime”

.env là gì?

.env (dotenv) là file text ở thư mục gốc project, mỗi dòng một cặp:

DATABASE_URL=postgresql://postgres:local@localhost:5432/app
API_URL=http://localhost:3000
JWT_SECRET=dev-only-change-me
# Comment bắt đầu bằng #
FEATURE_FLAG=true

Quy ước phổ biến:

Quy ướcÝ nghĩa
KEY=valueMột key một dòng, không khoảng trắng quanh =
# commentHầu hết loader bỏ qua
.envFile local thật — không commit nếu có secret
.env.exampleBản mẫu key + placeholder — được commit
.env.localNext.js: override local, ưu tiên cao, thường gitignore

File này không phải API đặc biệt của Node. Nó chỉ là text. Ai đó (framework hoặc thư viện) phải đọc và gán vào môi trường process.


process.env là gì?

Trong Node.js, process.env là object chứa biến môi trường của process hiện tại:

// Sau khi đã load .env (hoặc set trên OS/CI)
console.log(process.env.DATABASE_URL);
// => postgresql://postgres:local@localhost:5432/app

console.log(process.env.NODE_ENV);
// => development | production | test

Ba nguồn hay gặp (thứ tự tùy toolchain):

  1. OS / shellexport API_URL=... trên Linux/macOS, hoặc biến hệ thống Windows
  2. CI / platform — GitHub Actions secrets, Vercel/Cloudflare env dashboard
  3. File .env* — load lúc start dev/build qua dotenv hoặc framework

App Nest/Next gần như luôn đọc process.env.XXX, không tự fs.readFile('.env') mỗi request. Nếu bạn sửa .env mà không restart process, giá trị cũ vẫn nằm trong memory.

.env file vs process.env
Khái niệm Vai trò
File .env Nguồn cấu hình trên đĩa cho máy dev
process.env Snapshot biến lúc Node đang chạy
Platform env (Vercel…) Nguồn production — không cần file trên server
.env.example Hợp đồng tên key cho người onboard

Next.js: load .env thế nào?

Next.js tự đọc các file .env* khi bạn chạy next dev / next build. Thứ tự ưu tiên (tóm tắt thực dụng):

  1. .env.$(NODE_ENV).local
  2. .env.local (không dùng cho test)
  3. .env.$(NODE_ENV)
  4. .env

Giá trị ở file ưu tiên cao hơn ghi đè file thấp hơn.

NEXT_PUBLIC_* — ranh giới client/server

// server-only — OK trong Route Handler / Server Component
const secret = process.env.STRIPE_SECRET_KEY;

// lộ ra browser — CHỈ dùng cho URL công khai, analytics ID…
const apiBase = process.env.NEXT_PUBLIC_API_URL;

Junior hay làm:

// ❌ Secret bị bundle vào client
const bad = process.env.NEXT_PUBLIC_JWT_SECRET;

Checklist nhanh Next:

  • Key chỉ dùng server → không thêm NEXT_PUBLIC_
  • Cần dùng trên client → chấp nhận public, hoặc gọi API route lấy data đã authorize
  • Đổi .envrestart next dev

NestJS: không “tự nhiên có” như Next

Nest không magically load .env chỉ vì bạn tạo file. Pattern phổ biến:

// app.module.ts
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.local', '.env'],
    }),
  ],
})
export class AppModule {}

Đọc trong service:

import { ConfigService } from '@nestjs/config';

constructor(private config: ConfigService) {}

getDb() {
  return this.config.get<string>('DATABASE_URL');
  // hoặc: process.env.DATABASE_URL — sau khi ConfigModule đã load
}

Nếu quên ConfigModule.forRoot(), process.env.DATABASE_URL vẫn undefined dù file .env nằm đúng root — đây là bug #1 khi chuyển từ tutorial Next sang Nest API.

Một số template gọi config({ path: '.env' }) từ dotenv ngay đầu main.ts. Đọc README repo khách trước khi đoán.


Ví dụ: onboard 10 phút

Giả sử .env.example của dự án freelance:

# Nest API + Next web
DATABASE_URL=postgresql://USER:PASS@localhost:5432/DB
JWT_SECRET=change-me
NEXT_PUBLIC_APP_URL=http://localhost:3000
PORT=3001

Bạn làm:

  1. cp .env.example .env
  2. Điền Postgres local + JWT random cho máy bạn
  3. Nest: xác nhận ConfigModule đã bật → npm run start:dev
  4. Next (nếu monorepo): xác nhận NEXT_PUBLIC_APP_URL khớp port frontend
  5. Log thử: console.log('port', process.env.PORT) phía Nest — thấy 3001 là load OK

Khi example dài 60+ key lộn thứ tự, paste bản placeholder vào .env Formatter để sort A–Z, dễ đối chiếu thiếu/thừa key — tool chạy local trên trình duyệt.


Lỗi junior hay gặp (và cách đọc)

Triệu chứng → nguyên nhân thường
Triệu chứng Nguyên nhân hay gặp Cách kiểm
undefined mọi key Chưa có .env / sai cwd / Nest chưa ConfigModule In process.cwd() và một key test
Chỉ một key undefined Typo DATA_BASE_URL vs DATABASE_URL Diff với .env.example
Client Next không thấy key Thiếu NEXT_PUBLIC_ Đưa logic sang server hoặc thêm prefix có chủ đích
CI fail, local OK Secret chỉ có trên laptop, chưa set trên CI Thêm env trên GitHub Actions / platform
Giá trị cũ sau khi sửa file Chưa restart process Tắt và chạy lại dev

Case study ngắn

Fresher Nest nhận task “thêm Stripe”. Thêm STRIPE_SECRET_KEY vào .env, inject vào service, vẫn undefined. Nguyên nhân: module feature import trước khi ConfigModule.forRoot được đăng ký ở AppModule, hoặc đang đọc key trong file được evaluate quá sớm. Sửa: đảm bảo ConfigModule global load trước; đọc qua ConfigService trong constructor/onModuleInit — không đọc top-level ngoài class khi module chưa init.


Checklist trước khi hỏi lead “env đâu?”

  1. Có file .env (không chỉ example) ở root đúng package trong monorepo không?
  2. Framework là Next (auto) hay Nest (cần ConfigModule)?
  3. Tên key khớp từng chữ với example?
  4. Đã restart process sau khi sửa?
  5. Đang log phía server hay client (Next)?
  6. CI/staging: key đã được set trên dashboard chưa — đừng giả định file .env được deploy?

Khi nào dùng .env Formatter

Tool Kawa .env Formatter sort key, giữ comment, giúp example dễ đọc và lộ duplicate. Phù hợp:

  • Example khách gửi lộn xộn
  • Chuẩn bị PR thêm nhiều key mới
  • So hai bản example giữa branch

Không thay thế hiểu process.env, không validate schema Zod, không phải nơi dán production secret. Bài format & sort đi sâu convention PR; bài best practices đi sâu gitignore/vault.


FAQ nhanh trong đầu

Có bắt buộc dùng thư viện dotenv không?
Next: không cần tự cài cho flow chuẩn. Nest: dùng @nestjs/config (bọc dotenv) hoặc dotenv trực tiếp — chọn một và ghi trong README.

Windows vs macOS có khác file .env không?
Cú pháp file giống nhau. Khác ở cách set biến ngoài file (PowerShell $env:FOO=... vs export FOO=...). Path trong giá trị Windows nên cân nhắc forward slash hoặc quote.

TypeScript có type cho process.env không?
Có thể augment NodeJS.ProcessEnv hoặc dùng Zod parse lúc boot để vừa type vừa fail-fast — nên làm sau khi đã hiểu load cơ bản.


Tóm tắt một dòng

File .env viết cấu hình; process.env mang cấu hình vào runtime; Next tự load + phân NEXT_PUBLIC_*; Nest cần ConfigModule — master ba ý này thì hết nửa ticket “thiếu env” của junior.

Liên kết liên quan