Introduction: Why Working with Files in JavaScript is a Must-Have
\n\nImagine you are the captain of a spaceship. Your task is to manage cargo holds, oxygen supplies, and the ship's log. In the world of web development, the file system is those cargo holds, and JavaScript is your control panel. Without the ability to work with files, you'll be stuck in orbit: you won't be able to save user settings, load a server configuration, or process log files.
\n\nMany beginners think that JavaScript only lives in the browser and cannot touch files on the disk. This is a myth! With the advent of Node.js (and even in modern browsers via the File API), JavaScript has become a full-fledged tool for working with the file system. Today, we'll break down how to read, write, delete, and move files, as well as how to avoid the async trap.
\n\nBy the way, if you are just starting your programming journey, I recommend first studying JavaScript basics — this will help you understand the examples below faster.
\n\nWhere Does JavaScript Work with Files? Two Worlds — Two Approaches
\n\nBefore diving into the code, let's clarify: file operations in JS are divided into two camps.
\n\n1. Server-Side JavaScript (Node.js)
\nHere we have full access to the file system: creating, deleting, renaming, reading streams — whatever your heart desires. We work through the built-in fs (File System) module. It's like a Swiss Army knife for working with files.
2. Client-Side JavaScript (Browser)
\nThe browser is a "sandbox" with restrictions. You can't just go and read any file from the user's computer (otherwise hackers would celebrate). But you can work with files that the user has chosen themselves via <input type="file">, or through the FileReader and Blob APIs.
In this article, we will focus on Node.js, but at the end we'll give a bonus — an example for the browser.
\n\nThe fs Module: Your Main Tool
\n\nIn Node.js, working with files begins by importing the fs module:
const fs = require('fs'); // old style\n// or\nimport fs from 'fs'; // if using ES modules\n\nThe fs module has three modes of operation: synchronous, asynchronous with callbacks, and asynchronous with promises. We will use the latter — with async/await, as it is a modern and readable approach.
Reading a File: From Simple to Complex
\n\nLet's say we have a file data.txt with text. The simplest way to read it:
import { readFile } from 'fs/promises';\n\nasync function readMyFile() {\n try {\n const data = await readFile('data.txt', 'utf-8');\n console.log(data);\n } catch (err) {\n console.error('Read error:', err.message);\n }\n}\n\nreadMyFile();\n\nWhat's important here? 'utf-8' is the encoding. If you don't specify it, you'll get a raw buffer that needs to be converted to a string. For text files, always specify the encoding.
If the file is large (e.g., a 500 MB log file), reading it entirely is a bad idea. This is where streams come to the rescue:
\n\nimport { createReadStream } from 'fs';\nimport { createInterface } from 'readline';\n\nasync function readLargeFile() {\n const stream = createReadStream('bigfile.log', 'utf-8');\n const rl = createInterface({ input: stream });\n\n for await (const line of rl) {\n console.log('Line:', line);\n // Process each line as it is read\n }\n}\n\nreadLargeFile();\n\nWriting to a File: Creating and Appending
\n\nWriting comes in two types: overwriting (write) and appending (append). The difference is obvious: write will erase the old file, append will add to the end.
import { writeFile, appendFile } from 'fs/promises';\n\n//