Node.js로 실험 데이터 파일 다루기
지난 시간에 JavaScript로 변수, 함수, 조건문, 반복문을 배웠습니다. 하지만 그 코드는 전부 브라우저 안에서 실행되었죠. 브라우저 안의 JavaScript는 보안상 여러분 컴퓨터의 파일을 읽거나 쓸 수 없습니다.
연구자에게 이건 치명적인 제약입니다. 실험 결과 CSV를 읽고, FASTA 서열 파일을 파싱하고, 분석 결과를 새 파일로 저장하려면 — JavaScript가 컴퓨터의 파일 시스템에 직접 접근해야 합니다.
Node.js가 바로 이 문제를 해결합니다. JavaScript를 브라우저 밖에서 실행할 수 있게 만든 **런타임(Runtime)**입니다. 실험실 비유로 말하면 — JavaScript라는 프로토콜을 브라우저라는 특정 장비에서만 돌릴 수 있었는데, Node.js 덕분에 아무 컴퓨터에서나 돌릴 수 있게 된 것입니다.
런타임이란?
런타임(Runtime) = 코드를 실행하는 환경입니다.
- 브라우저 = JavaScript 런타임 (웹페이지 안에서만)
- Node.js = JavaScript 런타임 (컴퓨터 어디서나)
qPCR 장비에서만 돌아가던 분석 소프트웨어를, 어떤 컴퓨터에서든 실행할 수 있게 만든 것과 같습니다.
# 설치 확인 (터미널에서)node --version# v22.x.x 같은 버전이 나오면 설치되어 있음
# JavaScript 파일 실행node my_script.js브라우저에서는 <script> 태그 안에 코드를 넣었지만, Node.js에서는 .js 파일을 만들고 터미널에서 node 파일명.js로 실행합니다.
모듈: 기능 상자 꺼내 쓰기
Node.js가 파일을 읽고, 네트워크 통신을 하고, 경로를 처리할 수 있는 이유는 모듈(Module) 덕분입니다. 모듈은 특정 기능을 묶어놓은 도구 상자입니다.
실험실에서 PCR 키트, DNA 추출 키트, 전기영동 키트를 각각 꺼내 쓰듯, Node.js에서도 필요한 모듈을 require()로 꺼내 씁니다:
const fs = require("fs");
const path = require("path");fs— 파일 시스템(File System) 모듈. 파일 읽기, 쓰기, 삭제path— 경로 처리 모듈. 운영체제별 경로 차이를 자동 처리
이 모듈들은 Node.js에 내장되어 있어서 별도 설치 없이 바로 쓸 수 있습니다.
파일 읽기: fs.readFileSync
가장 기본적인 작업 — 파일을 읽어서 내용을 출력합니다:
const fs = require("fs");
const data = fs.readFileSync("samples.csv", "utf8");
console.log(data);readFileSync의 Sync는 **동기(synchronous)**라는 뜻입니다. 파일을 다 읽을 때까지 다음 줄로 넘어가지 않습니다. 마치 원심분리기가 멈출 때까지 옆에서 기다리는 것과 같습니다.
"utf8"은 문자 인코딩 — 이걸 빠뜨리면 사람이 읽을 수 없는 Buffer(이진 데이터)가 출력됩니다.
실험 데이터를 읽어서 처리하는 예제:
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("컬럼:", header);
console.log("데이터 행 수:", 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");
}
}CSV 파일을 읽고, 줄 단위로 쪼개고, OD 값이 기준 미달인 시료를 걸러냅니다. 엑셀을 열지 않아도 터미널에서 바로 QC 판정이 가능합니다.
동기 vs 비동기: 왜 두 가지가 있나?
readFileSync(동기)는 직관적이지만 문제가 있습니다. 파일이 크면 읽는 동안 프로그램 전체가 멈춥니다. 시퀀싱 원시 데이터처럼 수 GB짜리 파일을 동기로 읽으면 그동안 아무것도 할 수 없습니다.
readFile(비동기)은 이 문제를 해결합니다:
const fs = require("fs");
fs.readFile("sequences.fasta", "utf8", function(err, data) {
if (err) {
console.log("파일 읽기 실패:", err.message);
return;
}
console.log("서열 데이터 길이:", data.length);
});
console.log("파일 요청 완료, 다른 작업 계속 진행");실행 결과:
파일 요청 완료, 다른 작업 계속 진행
서열 데이터 길이: 4823910순서에 주목하세요. console.log("파일 요청 완료...")가 먼저 출력됩니다. readFile은 파일 읽기를 시작만 해놓고 바로 다음 줄로 넘어갑니다. 파일 읽기가 끝나면 그때 function(err, data) — 콜백(callback) 함수가 호출됩니다.
외부 시퀀싱 업체에 시료를 보내는 것과 같습니다. 보내놓고 결과가 올 때까지 멍하니 기다리지 않고, 다른 실험을 계속 진행하다가, 결과가 도착하면 그때 확인합니다.
| 동기 (Sync) | 비동기 (Async) | |
|---|---|---|
| 함수 | fs.readFileSync() | fs.readFile() |
| 동작 | 완료될 때까지 대기 | 요청 후 다음 줄로 즉시 이동 |
| 결과 받기 | 반환값 (const data = ...) | 콜백 함수 (function(err, data)) |
| 비유 | 원심분리기 앞에서 대기 | 시퀀싱 외주 맡기고 다른 실험 |
| 적합한 상황 | 작은 설정 파일, 초기화 | 대용량 데이터, 서버 요청 처리 |
콜백과 에러 처리
비동기 함수의 콜백은 항상 (err, data) 형태입니다. 첫 번째 인자가 에러, 두 번째가 결과. Node.js의 약속입니다:
const fs = require("fs");
fs.readFile("experiment_log.txt", "utf8", function(err, data) {
if (err) {
console.log("에러 종류:", err.code);
console.log("에러 메시지:", err.message);
return;
}
console.log("로그 내용:", data);
});파일이 없으면 err.code는 "ENOENT" (Error NO ENTry) — "파일을 찾을 수 없습니다"라는 뜻입니다. 이 패턴을 기억하세요:
err을 먼저 확인- 에러가 있으면 처리하고
return - 에러가 없으면
data사용
파일 쓰기: 분석 결과 저장
읽기만큼 중요한 것이 쓰기입니다. QC 판정 결과를 파일로 저장하려면:
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 리포트 저장 완료:", results.length - 1, "건");writeFileSync는 파일이 없으면 새로 만들고, 있으면 덮어씁니다. 기존 내용 뒤에 추가하려면 fs.appendFileSync()를 씁니다.
간단한 웹 서버 만들기
Node.js의 진짜 힘은 서버를 만들 수 있다는 것입니다. http 모듈로 3줄이면 웹 서버가 돌아갑니다:
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.listen(3000, function() {
console.log("서버 시작: http://localhost:3000");
});node server.js를 실행하고 브라우저에서 http://localhost:3000에 접속하면 "LIMS 서버 가동 중"이 표시됩니다.
하지만 이 상태로 URL에 따라 다른 데이터를 보내거나, POST 요청을 처리하려면 코드가 급격히 복잡해집니다. 다음 시간에 배울 Express가 바로 이 복잡성을 해결해주는 프레임워크입니다.
npm: 다른 개발자의 도구 가져다 쓰기
Node.js에는 npm(Node Package Manager)이라는 패키지 관리자가 함께 설치됩니다. 다른 개발자들이 만든 라이브러리를 한 줄 명령으로 설치할 수 있습니다:
# 프로젝트 초기화 (package.json 생성)npm init -y
# 패키지 설치 예시npm install csv-parsernpm install express실험실에서 시약 카탈로그를 보고 필요한 시약을 주문하듯, npm은 수백만 개의 JavaScript 패키지 카탈로그에서 필요한 것을 골라 설치합니다. package.json은 "이 프로젝트에 어떤 시약(패키지)이 필요한지" 적어놓은 시약 목록입니다.
직접 해보기 (Faded Example)
아래 빈칸을 채워 CSV 파일을 읽고 줄 수를 세는 Node.js 코드를 완성하세요.
const fs = require("");const data = fs.readFileSync("samples.csv", "");const lines = data.trim().("\n");console.log("총", lines., "줄");
흔한 에러 & 해결법
Q: Error: Cannot find module 'fs' 에러가 납니다
브라우저에서 실행하고 있을 가능성이 높습니다. fs 모듈은 Node.js 전용입니다. 터미널에서 node 파일명.js로 실행하세요. 브라우저 콘솔이나 HTML의 <script> 태그 안에서는 사용할 수 없습니다.
Q: 파일을 읽었는데 이상한 문자가 출력됩니다 (<Buffer 48 65 6c ...>)
readFileSync에 "utf8" 인코딩을 빠뜨렸습니다. 인코딩 없이 호출하면 Buffer(원시 바이트)가 반환됩니다. fs.readFileSync("file.txt", "utf8")로 수정하세요.
Q: ENOENT: no such file or directory 에러가 납니다
파일 경로가 잘못되었습니다. Node.js는 node 명령을 실행한 디렉토리 기준으로 상대 경로를 해석합니다. ls(맥/리눅스) 또는 dir(윈도우)로 파일이 현재 디렉토리에 있는지 확인하세요. 확실하게 하려면 path.join(__dirname, "파일명")으로 절대 경로를 구성합니다.
Q: 비동기 함수의 결과를 바깥에서 쓸 수 없습니다
let result;
fs.readFile("data.txt", "utf8", function(err, data) {
result = data;
});
console.log(result); // undefined!readFile은 바로 다음 줄로 넘어가기 때문에 콜백이 실행되기 전에 console.log가 먼저 실행됩니다. 결과를 사용하는 코드는 반드시 콜백 함수 안에 넣으세요. 또는 readFileSync를 사용하면 이 문제가 없습니다.
Q: "런타임(Runtime)"이 정확히 뭔가요?
런타임은 특정 언어의 코드를 실행할 수 있게 해주는 실행 환경 자체를 말합니다.
JavaScript는 원래 브라우저 안에서만 실행할 수 있었습니다. HTML 파일의 <script> 태그 안에 코드를 넣고, 브라우저에서 열어야만 동작했습니다. 이때 브라우저가 JavaScript의 런타임이었습니다.
Node.js를 컴퓨터에 설치하면, 브라우저 없이도 터미널에서 node script.js로 JavaScript를 실행할 수 있습니다. 이때 Node.js가 JavaScript의 런타임입니다.
브라우저에서 JS 실행: HTML 파일 → 브라우저(런타임)에서 열기
Node.js에서 JS 실행: .js 파일 → 터미널에서 node 명령(런타임)으로 실행비유하면 — 현미경 없이는 세포를 관찰할 수 없듯, 런타임 없이는 코드를 실행할 수 없습니다. 코드를 작성하는 것(VS Code에서 타이핑)과 실행하는 것(런타임이 해석)은 별개의 단계입니다.