Menu

fs.writeFile trong Node.js: ghi, ghi thêm và tạo tệp

Ghi tệp trong Node.js bằng fs.writeFile, fs.writeFileSync hoặc await writeFile từ fs/promises. Tệp được tạo nếu chưa có và bị ghi đè nếu đã tồn tại; dùng appendFile để ghi thêm vào cuối.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

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.

writeFileappendFile
Tệp chưa cóTạo mớiTạo mới
Tệp đã cóThay thế nội dungGhi thêm vào cuối
Cờ mặc định'w''a'
Dùng thường choLưu cả một tài liệu hoặc JSONLog, 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.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu