When JavaScript Goes Beyond the Browser
\n\nMost developers are accustomed to perceiving JavaScript as a language for "animating" web pages. But what if I told you that you can write scripts in JavaScript to manage the operating system, work with the file system at a low level, and even create network daemons? Sounds like science fiction? Well, it's not. Thanks to runtime environments like Node.js and Deno, JavaScript for system programming has become not just possible, but a quite effective tool. In this article, we will analyze real code examples and see how JS handles tasks that were previously considered the prerogative of C, Rust, or Python.
\n\nWhy JavaScript? Advantages and Pitfalls
\n\nBefore diving into the code, let's honestly assess the situation. Node.js system programming has its strengths and weaknesses.
\n\nPros:
\n- \n
- Unified technology stack — if you are a frontend developer, you don't need to learn a new language to write command-line utilities. \n
- Huge community — npm offers thousands of packages for working with the system. \n
- Asynchrony out of the box — non-blocking I/O is ideal for working with networks and files. \n
- Fast prototyping — you can write a script in JS in just a few minutes. \n
Cons:
\n- \n
- Performance — JS is slower than C or Rust in computational tasks. \n
- Limited hardware access — there is no direct work with memory or I/O ports (though there are workarounds). \n
- Memory consumption — V8 is not the lightest engine. \n
Nevertheless, for 80% of system administration and automation tasks, JavaScript for system programming is perfectly suitable. Let's move on to practice.
\n\nWorking with the File System: the fs Module
\n\nLet's start with the basics — working with files JS. The fs module in Node.js provides both synchronous and asynchronous methods for interacting with the file system.
Reading and Writing Files (Asynchronous Version)
\n\nconst fs = require('fs').promises;\nconst path = require('path');\n\nasync function processLogFile(filePath) {\n try {\n // Read the file\n const data = await fs.readFile(filePath, 'utf-8');\n const lines = data.split('\\n');\n \n // Filter only lines with errors\n const errors = lines.filter(line => line.includes('ERROR'));\n \n // Write the result\n const outputPath = path.join(__dirname, 'errors.log');\n await fs.writeFile(outputPath, errors.join('\\n'), 'utf-8');\n \n console.log(`Processed ${lines.length} lines, found ${errors.length} errors`);\n } catch (err) {\n console.error('Error working with file:', err);\n }\n}\n\nprocessLogFile('/var/log/application.log');\n\nThis script is a typical example of a system script in JavaScript. It reads a large log file, filters the data, and writes the result. The asynchronous version does not block the event loop, which is critical for server applications.
\n\nMonitoring Changes in the File System
\n\nOne of the powerful capabilities is real-time file watching:
\n\nconst fs = require('fs');\n\nconst watcher = fs.watch('/etc/config', { recursive: true });\n\nwatcher.on('change', (eventType, filename) => {\n console.log(`Change in file: ${filename} (${eventType})`);\n \n // Automatically reload configuration\n if (filename.endsWith('.conf')) {\n const config = fs.readFileSync(`/etc/config/${filename}`, 'utf-8');\n console.log('New configuration loaded');\n }\n});\n\nconsole.log('Configuration monitoring started');