fs.writeFile(path, data, callback) ghi một chuỗi hoặc Buffer vào tệp. Nếu tệp chưa tồn tại, Node sẽ tạo nó; nếu đã có, nội dung cũ bị thay thế. fs.writeFileSync(path, data) làm điều tương tự và chờ đến khi ghi xong, còn fs.appendFile ghi thêm vào cuối tệp thay vì thay thế.
Chương trình in ra saved rồi đến đoạn văn bản đọc lại được; callback chỉ nhận lỗi (hoặc null), không bao giờ nhận dữ liệu. Truyền 42 thay cho chuỗi thì lệnh gọi ném TypeError: dữ liệu phải là chuỗi hoặc Buffer.
writeFile, writeFileSync và fs/promises
Ba dạng này nhận cùng các đối số và chỉ khác nhau ở cách bạn chờ kết quả.
const fs = require('fs');
const fsp = require('fs/promises');
fs.writeFileSync('a.txt', 'one'); // blocks, throws on error
fs.writeFile('b.txt', 'two', (err) => {}); // callback with err
await fsp.writeFile('c.txt', 'three'); // promise, use in async code
Dùng writeFileSync trong script và code khởi động, còn bản promise dùng trên server, nơi một lần ghi sync chặn mọi request khác cho đến khi ổ đĩa ghi xong. Trang fs/promises trình bày phần còn lại của API promise.
Ghi thêm vào tệp bằng appendFile
writeFile thay thế toàn bộ tệp. Để thêm một dòng vào log, hãy dùng appendFile, hàm này cũng tạo tệp ở lần đầu tiên.
Lần đọc đầu in ra "second\n", vì lệnh writeFileSync thứ hai đã thay thế first, còn lần đọc thứ hai in ra "second\nthird\nfourth\n" (JSON.stringify giúp nhìn thấy các ký tự xuống dòng). Bạn phải tự thêm \n: cả hai hàm đều không tự thêm.
writeFile | appendFile | |
|---|---|---|
| Tệp chưa có | Tạo mới | Tạo mới |
| Tệp đã có | Thay thế nội dung | Ghi thêm vào cuối |
| Cờ mặc định | 'w' | 'a' |
| Dùng thường cho | Lưu cả một tài liệu hoặc JSON | Log, mỗi lần một dòng |
Không ghi đè: tùy chọn flag
Đối số thứ ba có thể là một object tùy chọn. Thuộc tính flag của nó quyết định điều gì xảy ra với tệp đã tồn tại. 'wx' nghĩa là "ghi, nhưng thất bại nếu tệp đã tồn tại", đây là cách an toàn để chỉ tạo tệp một lần.
Lệnh gọi thứ hai thất bại với EEXIST, nên tệp vẫn chứa abc123. Đổi tên tệp ở lệnh gọi thứ hai thành id2.txt thì cả hai lệnh đều in ra created.
Các cờ bạn gặp nhiều nhất: 'w' (mặc định, thay thế), 'a' (ghi thêm), 'wx' (chỉ tạo mới) và 'ax' (ghi thêm, nhưng chỉ vào tệp mới).
Kiểm tra bằng fs.existsSync trước rồi mới ghi sẽ để lại một khoảng hở: tiến trình khác có thể tạo tệp giữa lúc kiểm tra và lúc ghi. 'wx' làm cả hai việc trong một bước.
Ghi JSON vào tệp
writeFile không nhận object. Hãy chuyển chúng thành văn bản bằng JSON.stringify; đối số thứ ba, 2, thụt lề đầu ra để tệp dễ đọc.
Tệp chứa object trải trên nhiều dòng có thụt lề, và parse ngược lại cho ra một object bằng với ban đầu, nên dòng cuối in ra 2. Truyền object mà không qua JSON.stringify thì lệnh gọi ném TypeError.
Tạo thư mục trước bằng mkdir
writeFile tạo tệp, không tạo thư mục. Ghi vào reports/2026/june.txt khi chưa có reports sẽ thất bại với ENOENT. Hãy tạo đường dẫn trước bằng mkdir với { recursive: true }, lệnh này tạo mọi thư mục còn thiếu và không làm gì nếu chúng đã tồn tại.
Lần ghi đầu thất bại với ENOENT; sau mkdir, cùng lệnh ghi đó thành công và readdir liệt kê [ 'june.txt' ]. Trong code thật, hãy ghép các đường dẫn này bằng path.join để chúng chạy được cả trên Windows.
Lỗi thường gặp
Tưởng tệp đã được ghi ngay sau lệnh gọi. fs.writeFile trả về trước khi dữ liệu nằm trên ổ đĩa. Hãy đọc tệp hoặc làm bước tiếp theo bên trong callback, hoặc await bản promise.
Mất dữ liệu khi ghi trong vòng lặp. Gọi writeFile một lần cho mỗi phần tử sẽ thay thế tệp mỗi lần, chỉ còn lại phần tử cuối cùng. Hãy dùng appendFile, hoặc ghép toàn bộ chuỗi rồi ghi một lần.
Ghi ra [object Object]. Đưa object qua String() hoặc template literal sẽ ghi ra [object Object]. Hãy dùng JSON.stringify.
Câu hỏi thường gặp
fs.writeFile có ghi đè tệp không?
Có. Với cờ mặc định 'w', nó xóa trắng tệp rồi ghi dữ liệu mới, nên nội dung cũ mất hẳn. Truyền { flag: 'a' } hoặc dùng appendFile để ghi thêm vào cuối.
fs.writeFile có tạo tệp nếu tệp chưa tồn tại không?
Có, nó tạo tệp. Tuy nhiên nó không tạo thư mục còn thiếu: ghi vào logs/app.log khi chưa có logs sẽ thất bại với ENOENT.
Có thể truyền gì làm dữ liệu?
Một chuỗi, một Buffer hoặc một typed array. Mọi thứ khác, như object hay số, đều ném TypeError, nên hãy chuyển object bằng JSON.stringify và số bằng String() trước.
Gọi writeFile nhiều lần cùng lúc trên một tệp có an toàn không?
Không. Hai lệnh writeFile chạy song song trên cùng một tệp có thể đan xen nhau và để lại nội dung lẫn lộn. Hãy chờ lệnh này xong rồi mới bắt đầu lệnh tiếp theo, hoặc gom dữ liệu lại rồi ghi một lần.