Introduction: Why Work with the File System in TypeScript?
\n\nEvery application sooner or later needs to interact with the file system: save configurations, process uploaded files, maintain logs, or manage projects. TypeScript, as a superset of JavaScript, provides developers with powerful tools for working with files and directories through the fs (File System) module from Node.js.
In this article, we will cover the main file system operations in TypeScript using practical examples: reading and writing files, creating and deleting directories, and error handling. All examples will be presented using strict typing, which is a key advantage of TypeScript.
\n\nMain Modules for Working with the File System
\n\nBefore moving to practice, let's get acquainted with the three main modules needed for the work:
\n\n- \n
- fs — the main module for file system operations (reading, writing, deleting) \n
- fs/promises — a version of the
fsmodule with promise support (recommended for modern code) \n - path — a utility for working with file and directory paths \n
To import modules in TypeScript, use the following syntax:
\n\nimport * as fs from 'fs';\nimport * as fsPromises from 'fs/promises';\nimport * as path from 'path';\n\nNote: for correct work with TypeScript, make sure that in your tsconfig.json file the options "moduleResolution": "node" and "esModuleInterop": true are set.
Reading Files: Synchronous and Asynchronous Approaches
\n\nSynchronous Reading with Typing
\n\nSynchronous methods block code execution until the operation completes. Use them only for small files or during initial application loading:
\n\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nfunction readConfigSync(): Record<string, unknown> {\n const configPath = path.join(__dirname, 'config.json');\n \n try {\n const data = fs.readFileSync(configPath, 'utf-8');\n return JSON.parse(data) as Record<string, unknown>;\n } catch (error) {\n if (error instanceof Error) {\n console.error('Error reading config:', error.message);\n }\n return {};\n }\n}\n\nconst config = readConfigSync();\nconsole.log('Configuration loaded:', config);\n\nAsynchronous Reading with async/await (Recommended Method)
\n\nFor production code, always use asynchronous methods. TypeScript works great with async/await:
\n\nimport { readFile } from 'fs/promises';\nimport * as path from 'path';\n\ninterface UserData {\n id: number;\n name: string;\n email: string;\n}\n\nasync function readUserData(fileName: string): Promise<UserData[]> {\n const filePath = path.resolve('data', fileName);\n \n try {\n const rawData = await readFile(filePath, { encoding: 'utf-8' });\n const users: UserData[] = JSON.parse(rawData);\n return users;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Could not read file ${fileName}: ${error.message}`);\n }\n throw error;\n }\n}\n\n// Usage\n(async () => {\n try {\n const users = await readUserData('users.json');\n console.log(`Loaded ${users.length} users`);\n } catch (error) {\n console.error(error);\n }\n})();\n\nWriting and Updating Files
\n\nCreating and Overwriting Files
\n\nTo write data to a file, use the writeFile method. It will create the file if it does not exist, or overwrite an existing one:
import { writeFile, mkdir } from 'fs/promises';\nimport * as path from 'path';\n\ninterface LogEntry {\n timestamp: string;\n level: 'info' | 'warn' | 'error';\n message: string;\n}\n