.env là gì? process.env cơ bản cho NestJS và Next.js junior
.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
.envtrong 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=value | Một key một dòng, không khoảng trắng quanh = |
# comment | Hầu hết loader bỏ qua |
.env | File local thật — không commit nếu có secret |
.env.example | Bản mẫu key + placeholder — được commit |
.env.local | Next.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):
- OS / shell —
export API_URL=...trên Linux/macOS, hoặc biến hệ thống Windows - CI / platform — GitHub Actions secrets, Vercel/Cloudflare env dashboard
- 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.
| 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):
.env.$(NODE_ENV).local.env.local(không dùng chotest).env.$(NODE_ENV).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
.env→ restartnext 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:
cp .env.example .env- Điền Postgres local + JWT random cho máy bạn
- Nest: xác nhận
ConfigModuleđã bật →npm run start:dev - Next (nếu monorepo): xác nhận
NEXT_PUBLIC_APP_URLkhớp port frontend - Log thử:
console.log('port', process.env.PORT)phía Nest — thấy3001là 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 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?”
- Có file
.env(không chỉ example) ở root đúng package trong monorepo không? - Framework là Next (auto) hay Nest (cần ConfigModule)?
- Tên key khớp từng chữ với example?
- Đã restart process sau khi sửa?
- Đang log phía server hay client (Next)?
- 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.