Handling Experiment Data Files with Node.js
Last time you learned variables, functions, conditionals, and loops in JavaScript. But all that code ran inside the browser. Browser JavaScript can't read or write files on your computer for security reasons.
For researchers, this is a critical limitation. To read experiment result CSVs, parse FASTA sequence files, and save analysis results to new files โ JavaScript needs direct access to your computer's file system.
Node.js solves exactly this problem. It's a runtime that lets JavaScript run outside the browser. In lab terms โ JavaScript was a protocol you could only run on a specific instrument called the browser, but Node.js lets you run it on any computer.
What Is a Runtime?
Runtime = the environment that executes code.
- Browser = JavaScript runtime (only inside web pages)
- Node.js = JavaScript runtime (anywhere on your computer)
It's like making analysis software that only ran on the qPCR machine executable on any computer.
# Check installation (in terminal)node --version# If you see a version like v22.x.x, it's installed
# Run a JavaScript filenode my_script.jsIn the browser, you put code inside <script> tags, but in Node.js you create .js files and run them with node filename.js in the terminal.
Modules: Pulling Out Feature Toolboxes
Node.js can read files, handle network communication, and process paths thanks to modules. A module is a toolbox bundling specific functionality.
Just as you pull out PCR kits, DNA extraction kits, and electrophoresis kits in the lab, in Node.js you pull out the modules you need with require():
const fs = require("fs");
const path = require("path");fsโ File System module. Read, write, delete filespathโ Path processing module. Auto-handles OS-specific path differences
These modules are built into Node.js โ no separate installation needed.
Reading Files: fs.readFileSync
The most basic operation โ read a file and print its contents:
const fs = require("fs");
const data = fs.readFileSync("samples.csv", "utf8");
console.log(data);The Sync in readFileSync means synchronous. It won't move to the next line until the file is fully read. Like waiting next to the centrifuge until it stops.
"utf8" is the character encoding โ omit it and you get an unreadable Buffer (binary data).
An example reading and processing experiment data:
const fs = require("fs");
const raw = fs.readFileSync("qc_results.csv", "utf8");
const lines = raw.trim().split("\n");
const header = lines[0].split(",");
console.log("Columns:", header);
console.log("Data rows:", lines.length - 1);
for (let i = 1; i < lines.length; i++) {
const cols = lines[i].split(",");
const sampleId = cols[0];
const od = parseFloat(cols[1]);
if (od < 0.5) {
console.log(sampleId, "โ OD", od, "โ FAIL");
}
}Reads a CSV file, splits by lines, and filters samples below the OD threshold. You can do QC checks right in the terminal without opening Excel.
Sync vs Async: Why Two Options?
readFileSync (synchronous) is intuitive but has a problem. If the file is large, the entire program freezes while reading. Read a multi-GB sequencing raw data file synchronously and nothing else can happen in the meantime.
readFile (asynchronous) solves this:
const fs = require("fs");
fs.readFile("sequences.fasta", "utf8", function(err, data) {
if (err) {
console.log("File read failed:", err.message);
return;
}
console.log("Sequence data length:", data.length);
});
console.log("File request sent, continuing other work");Output:
File request sent, continuing other work
Sequence data length: 4823910Notice the order. console.log("File request sent...") prints first. readFile kicks off the file read and immediately moves to the next line. When the file reading finishes, the callback function function(err, data) is called.
It's like sending samples to an external sequencing service. You don't stand there waiting until results arrive โ you continue other experiments and check the results when they come back.
| Synchronous (Sync) | Asynchronous (Async) | |
|---|---|---|
| Function | fs.readFileSync() | fs.readFile() |
| Behavior | Waits until complete | Requests then immediately moves on |
| Getting result | Return value (const data = ...) | Callback function (function(err, data)) |
| Analogy | Waiting by the centrifuge | Outsourcing sequencing and doing other experiments |
| Best for | Small config files, initialization | Large data, server request handling |
Callbacks and Error Handling
Async function callbacks always follow the (err, data) pattern. First argument is the error, second is the result. This is a Node.js convention:
const fs = require("fs");
fs.readFile("experiment_log.txt", "utf8", function(err, data) {
if (err) {
console.log("Error type:", err.code);
console.log("Error message:", err.message);
return;
}
console.log("Log contents:", data);
});If the file doesn't exist, err.code is "ENOENT" (Error NO ENTry) โ meaning "file not found." Remember this pattern:
- Check
errfirst - If there's an error, handle it and
return - If no error, use
data
Writing Files: Saving Analysis Results
Writing is just as important as reading. To save QC results to a file:
const fs = require("fs");
const results = [
"Sample_ID,OD,Status",
"S001,1.85,PASS",
"S002,0.42,FAIL",
"S003,2.10,PASS"
];
const output = results.join("\n");
fs.writeFileSync("qc_report.csv", output, "utf8");
console.log("QC report saved:", results.length - 1, "entries");writeFileSync creates the file if it doesn't exist, or overwrites if it does. To append to existing content, use fs.appendFileSync().
Creating a Simple Web Server
Node.js's real power is the ability to create servers. The http module lets you run a web server in 3 lines:
const http = require("http");
const server = http.createServer(function(req, res) {
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end("LIMS server running");
});
server.listen(3000, function() {
console.log("Server started: http://localhost:3000");
});Run node server.js and visit http://localhost:3000 in your browser to see "LIMS server running."
But serving different data based on URLs or handling POST requests makes the code extremely complex. Express โ the framework you'll learn next โ solves exactly this complexity.
npm: Using Other Developers' Tools
Node.js comes with npm (Node Package Manager) installed. You can install libraries made by other developers with a single command:
# Initialize project (creates package.json)npm init -y
# Install packages examplenpm install csv-parsernpm install expressJust as you browse a reagent catalog and order what you need in the lab, npm lets you pick and install from millions of JavaScript packages. package.json is the "reagent list" showing which packages your project needs.
Try It Yourself (Faded Example)
Fill in the blanks to complete Node.js code that reads a CSV and counts lines.
const fs = require("");const data = fs.readFileSync("samples.csv", "");const lines = data.trim().("\n");console.log("Total", lines., "lines");
Common Errors & Solutions
Q: Error: Cannot find module 'fs'
You're likely running in the browser. The fs module is Node.js only. Run with node filename.js in the terminal. It cannot be used in browser console or HTML <script> tags.
Q: File read returns strange characters (<Buffer 48 65 6c ...>)
You forgot "utf8" encoding in readFileSync. Without encoding, it returns a Buffer (raw bytes). Fix with fs.readFileSync("file.txt", "utf8").
Q: ENOENT: no such file or directory
The file path is wrong. Node.js resolves relative paths from the directory where the node command was run. Check the file exists in the current directory with ls (Mac/Linux) or dir (Windows). For certainty, construct an absolute path with path.join(__dirname, "filename").
Q: Can't use async function results outside the callback
let result;
fs.readFile("data.txt", "utf8", function(err, data) {
result = data;
});
console.log(result); // undefined!readFile moves to the next line immediately, so console.log runs before the callback executes. Code that uses the result must be placed inside the callback function. Or use readFileSync to avoid this issue.
Q: What exactly is a "runtime"?
A runtime is the execution environment that enables running code in a specific language.
JavaScript originally could only run inside browsers. You'd put code in an HTML file's <script> tag and open it in a browser. The browser was JavaScript's runtime.
With Node.js installed on your computer, you can run JavaScript from the terminal with node script.js without a browser. Here Node.js is JavaScript's runtime.
Running JS in browser: HTML file โ open in browser (runtime)
Running JS in Node.js: .js file โ run with node command (runtime) in terminalAnalogy โ just as you can't observe cells without a microscope, you can't execute code without a runtime. Writing code (typing in VS Code) and executing it (runtime interprets it) are separate steps.