Category: reference
Node.js File System — fs/promises, path, Common Patterns
อ่าน เขียน คัดลอก ลบไฟล์และ directory ด้วย fs/promises และ path module — patterns ที่ใช้บ่อยใน scripts
สารบัญ
Import
import fs from 'node:fs/promises'; // promise-based (แนะนำ)
import path from 'node:path';
import { existsSync } from 'node:fs'; // sync version (ใช้กับ startup check)
ใช้ node: prefix เพื่อบอกว่าเป็น built-in module ชัดเจน (Node.js 14.18+)
อ่านไฟล์
// อ่านเป็น string
const content = await fs.readFile('config.json', 'utf-8');
const config = JSON.parse(content);
// อ่านเป็น Buffer (binary)
const buffer = await fs.readFile('image.png');
// อ่าน JSON สั้นกว่า
const data = JSON.parse(await fs.readFile('data.json', 'utf-8'));
// อ่านหลายไฟล์พร้อมกัน
const [a, b, c] = await Promise.all([
fs.readFile('file-a.txt', 'utf-8'),
fs.readFile('file-b.txt', 'utf-8'),
fs.readFile('file-c.txt', 'utf-8'),
]);
เขียนไฟล์
// เขียนทับ (ถ้าไม่มีไฟล์จะสร้างใหม่)
await fs.writeFile('output.txt', 'Hello World', 'utf-8');
// เขียน JSON
await fs.writeFile('config.json', JSON.stringify(config, null, 2));
// ต่อท้ายไฟล์ (append)
await fs.appendFile('log.txt', `${new Date().toISOString()} - Event happened\n`);
// เขียนไฟล์ binary
await fs.writeFile('image.png', buffer);
Path Module
import path from 'node:path';
// ต่อ path (จัดการ separator อัตโนมัติ)
path.join('src', 'components', 'Button.tsx'); // 'src/components/Button.tsx'
// resolve เป็น absolute path
path.resolve('src', 'index.ts'); // '/project/src/index.ts'
path.resolve(__dirname, '../config'); // ใช้ __dirname ใน CommonJS
// ดึง parts
path.basename('/path/to/file.ts'); // 'file.ts'
path.basename('/path/to/file.ts', '.ts'); // 'file' (ตัด extension)
path.extname('/path/to/file.ts'); // '.ts'
path.dirname('/path/to/file.ts'); // '/path/to'
// แยก path
path.parse('/home/user/file.ts');
// { root: '/', dir: '/home/user', base: 'file.ts', ext: '.ts', name: 'file' }
// ตรวจสอบ relative/absolute
path.isAbsolute('/etc/config'); // true
path.isAbsolute('relative/path'); // false
Directory Operations
// สร้าง directory (รวม parent)
await fs.mkdir('dist/assets/images', { recursive: true });
// อ่านรายการไฟล์ใน directory
const files = await fs.readdir('src');
// ['components', 'pages', 'styles', 'index.ts']
// อ่านพร้อม metadata
const entries = await fs.readdir('src', { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile()) console.log('File:', entry.name);
if (entry.isDirectory()) console.log('Dir:', entry.name);
if (entry.isSymbolicLink()) console.log('Link:', entry.name);
}
// ลบ directory (รวมเนื้อหา)
await fs.rm('dist', { recursive: true, force: true });
// หรือ node 14.14+
await fs.rmdir('dist', { recursive: true });
// เช็คว่ามีอยู่ไหม
try {
await fs.access('config.json');
console.log('exists');
} catch {
console.log('not found');
}
// หรือใช้ sync version
if (existsSync('config.json')) { ... }
Copy และ Rename
// คัดลอกไฟล์
await fs.copyFile('source.txt', 'destination.txt');
// ย้ายไฟล์ (rename + move)
await fs.rename('old-path/file.txt', 'new-path/file.txt');
// ลบไฟล์
await fs.unlink('temp.txt');
File Metadata
const stat = await fs.stat('package.json');
stat.size; // bytes
stat.mtime; // Date — last modified
stat.atime; // Date — last accessed
stat.isFile(); // true
stat.isDirectory(); // false
// lstat ไม่ follow symbolic links
const lstat = await fs.lstat('symlink');
lstat.isSymbolicLink(); // true ถ้าเป็น symlink
Pattern: รวบรวมไฟล์ใน Directory แบบ Recursive
import fs from 'node:fs/promises';
import path from 'node:path';
async function* walk(dir: string): AsyncGenerator<string> {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
yield* walk(fullPath); // recurse
} else {
yield fullPath;
}
}
}
// หาไฟล์ .md ทั้งหมด
for await (const file of walk('src/content')) {
if (file.endsWith('.md')) {
console.log(file);
}
}
Pattern: อ่าน/แก้/เขียน JSON Config
async function updateConfig(configPath: string, updates: Record<string, unknown>) {
let config: Record<string, unknown> = {};
try {
const content = await fs.readFile(configPath, 'utf-8');
config = JSON.parse(content);
} catch {
// ไม่มีไฟล์ — เริ่มใหม่
}
Object.assign(config, updates);
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
}
await updateConfig('~/.config/myapp/settings.json', { theme: 'dark' });
Pattern: Atomic Write
import { randomUUID } from 'node:crypto';
async function atomicWrite(filePath: string, content: string) {
const tmp = `${filePath}.${randomUUID()}.tmp`;
try {
await fs.writeFile(tmp, content, 'utf-8');
await fs.rename(tmp, filePath); // atomic บน same filesystem
} catch (err) {
await fs.unlink(tmp).catch(() => {}); // cleanup ถ้า error
throw err;
}
}
Atomic write ป้องกันไฟล์ถูกเขียนครึ่งๆ ถ้าโปรแกรม crash ระหว่างทาง
Pattern: Watch Files
const watcher = fs.watch('src', { recursive: true });
for await (const { eventType, filename } of watcher) {
console.log(`${eventType}: ${filename}`);
// 'change: pages/index.astro'
// 'rename: components/Button.tsx'
}
// หรือ callback style
import { watch } from 'node:fs';
const watcher = watch('src', { recursive: true }, (event, filename) => {
console.log(event, filename);
});
watcher.close();
Error Handling
import { constants } from 'node:fs';
async function safeRead(filePath: string): Promise<string | null> {
try {
return await fs.readFile(filePath, 'utf-8');
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
return null; // ไม่มีไฟล์
}
if ((err as NodeJS.ErrnoException).code === 'EACCES') {
throw new Error(`Permission denied: ${filePath}`);
}
throw err; // error อื่น
}
}
Common error codes:
ENOENT— ไม่มีไฟล์/directoryEEXIST— มีอยู่แล้วEACCES/EPERM— permission errorEISDIR— คาดว่าเป็น file แต่เจอ directoryENOTDIR— คาดว่าเป็น directory แต่เจอ file