BioPlayground

🧬
목록으로

Express로 실험 데이터 서버 만들기

Express.js로 실험 데이터 API 서버를 만드는 법. 라우팅, 미들웨어, REST API를 바이오 예제로 배웁니다.

입문
|
60
|
검증 완료 (2026-06)
ExpressNode.js서버라우팅미들웨어REST API
트랙 진행률0/11 (0%)

Express로 실험 데이터 서버 만들기

지금까지 fetch로 서버에 데이터를 요청하는 법을 배웠습니다. 이번에는 반대편 — 데이터를 보내주는 서버를 직접 만듭니다.

실험실에서 LIMS(Laboratory Information Management System)를 쓰듯, 웹에서도 데이터를 저장하고 꺼내주는 서버가 있어야 합니다. Express는 그 서버를 만드는 도구입니다. 앞에서 배운 Node.js 위에서 동작하는 웹 프레임워크 — 쉽게 말해, 서버 개발용 실험 키트입니다.

Node.js만으로도 서버를 만들 수 있지만(http.createServer를 기억하시죠?), 모든 것을 직접 구현해야 합니다. Express를 쓰면 URL에 따라 다른 데이터를 보내주는 것, 파일을 제공하는 것, 에러를 처리하는 것 같은 반복 작업을 프레임워크가 대신합니다. 시약을 하나씩 구매해서 직접 조합하는 대신, 검증된 Kit을 쓰는 것과 같습니다.

Hello World: 서버 3줄로 시작하기

Express 서버의 가장 기본적인 형태입니다:

javascript
const express = require("express");
const app = express();

app.get("/", function(req, res) {
  res.send("LIMS Server Running");
});

app.listen(3000, function() {
  console.log("서버 시작: http://localhost:3000");
});

이 코드를 server.js로 저장하고 터미널에서 node server.js를 실행하면, 브라우저에서 http://localhost:3000으로 접속할 수 있습니다.

핵심 요소를 분해하면:

  • require("express") — Express 라이브러리를 불러옵니다
  • app.get("/", ...) — "/" 주소로 들어오면 실행할 함수를 등록합니다
  • req — 요청(request). 클라이언트가 보낸 정보
  • res — 응답(response). 서버가 보낼 정보
  • res.send() — 클라이언트에게 데이터를 보냅니다
  • app.listen(3000) — 3000번 포트에서 요청을 기다립니다

라우팅: URL에 따라 다른 데이터 보내기

라우팅(routing)은 "어떤 주소로 요청이 오면, 어떤 데이터를 보내줄지" 정하는 것입니다. LIMS에서 시료 번호를 입력하면 해당 시료 정보가 나오고, 프로토콜 이름을 입력하면 해당 프로토콜이 나오는 것과 같습니다.

javascript
const express = require("express");
const app = express();

app.get("/", function(req, res) {
  res.send("실험 데이터 서버에 오신 것을 환영합니다");
});

app.get("/genes", function(req, res) {
  const genes = [
    { name: "TP53", chromosome: "17p13.1", type: "tumor suppressor" },
    { name: "BRCA1", chromosome: "17q21.31", type: "DNA repair" },
    { name: "EGFR", chromosome: "7p11.2", type: "receptor tyrosine kinase" }
  ];
  res.json(genes);
});

app.get("/protocols", function(req, res) {
  const protocols = ["DNA Extraction", "PCR", "Western Blot", "ELISA"];
  res.json(protocols);
});

app.listen(3000, function() {
  console.log("서버 시작: http://localhost:3000");
});

http://localhost:3000/genes에 접속하면 유전자 목록이, /protocols에 접속하면 프로토콜 목록이 JSON으로 표시됩니다. res.json()은 JavaScript 객체를 JSON 형식으로 변환해서 보내줍니다.

동적 라우팅: URL에서 파라미터 받기

특정 유전자의 정보만 조회하고 싶다면 URL에 파라미터를 넣습니다:

javascript
const geneDatabase = {
  TP53: { name: "TP53", chromosome: "17p13.1", function: "tumor suppressor", length_bp: 19149 },
  BRCA1: { name: "BRCA1", chromosome: "17q21.31", function: "DNA repair", length_bp: 81189 },
  EGFR: { name: "EGFR", chromosome: "7p11.2", function: "receptor tyrosine kinase", length_bp: 188307 }
};

app.get("/gene/:id", function(req, res) {
  const geneId = req.params.id;
  const gene = geneDatabase[geneId];

  if (gene) {
    res.json(gene);
  } else {
    res.status(404).json({ error: "해당 유전자를 찾을 수 없습니다", query: geneId });
  }
});

/gene/TP53으로 접속하면 TP53 정보가, /gene/XYZ123으로 접속하면 404 에러가 반환됩니다. :id 부분이 URL 파라미터 — 요청할 때마다 다른 값이 들어오고, req.params.id로 꺼내 씁니다.

미들웨어: 실험 전처리 단계

미들웨어(middleware)는 Express의 핵심 개념입니다. 요청이 서버에 도착해서 응답이 나가기 사이에 거치는 처리 단계입니다.

실험에서 시료가 분석 장비에 들어가기 전에 거치는 전처리 과정을 생각해보세요:

text
[시료 접수] → [라벨 확인] → [QC 검사] → [전처리] → [분석] → [결과 보고]

Express에서도 요청이 같은 파이프라인을 거칩니다:

text
[요청 도착] → [미들웨어 1] → [미들웨어 2] → [라우트 핸들러] → [응답 전송]

미들웨어는 app.use()로 등록합니다:

javascript
const express = require("express");
const app = express();

app.use(function(req, res, next) {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
});

app.get("/genes", function(req, res) {
  res.json([{ name: "TP53" }, { name: "BRCA1" }]);
});

app.listen(3000);

app.use()로 등록한 함수는 모든 요청에 대해 실행됩니다. 여기서는 요청이 올 때마다 시간, 방법(GET/POST), 주소를 콘솔에 기록합니다. 실험 노트에 "몇 시에, 어떤 시료를, 어떤 분석을 했는지" 적는 것과 같습니다.

핵심은 next()입니다. 이 함수를 호출해야 다음 단계(다음 미들웨어 또는 라우트 핸들러)로 넘어갑니다. next()를 호출하지 않으면 요청이 여기서 멈추고 응답이 나가지 않습니다 — QC에서 불합격한 시료가 다음 단계로 넘어가지 않는 것과 같습니다.

실전 미들웨어: POST 데이터 파싱

클라이언트가 POST 방식으로 데이터를 보내면 (예: 새 시료 등록), 그 데이터를 읽을 수 있게 파싱해야 합니다. Express는 이를 위한 내장 미들웨어를 제공합니다:

javascript
app.use(express.json());

app.post("/samples", function(req, res) {
  const newSample = req.body;
  console.log("새 시료 등록:", newSample);
  res.json({ message: "시료 등록 완료", sample: newSample });
});

express.json()이 없으면 req.bodyundefined입니다. 이 미들웨어가 JSON 형식으로 들어온 데이터를 자동으로 파싱해서 req.body에 넣어줍니다.

종합 예제: 시료 관리 API

지금까지 배운 것을 합쳐 간단한 시료 관리 서버를 만들어 보겠습니다:

javascript
const express = require("express");
const app = express();

app.use(express.json());

app.use(function(req, res, next) {
  console.log(`[LOG] ${req.method} ${req.url}`);
  next();
});

const samples = [
  { id: "S001", name: "Blood Sample A", od: 1.85, status: "pass" },
  { id: "S002", name: "Tissue Sample B", od: 0.42, status: "fail" },
  { id: "S003", name: "Serum Sample C", od: 2.10, status: "pass" }
];

app.get("/samples", function(req, res) {
  res.json(samples);
});

app.get("/sample/:id", function(req, res) {
  const found = samples.find(function(s) {
    return s.id === req.params.id;
  });

  if (found) {
    res.json(found);
  } else {
    res.status(404).json({ error: "시료를 찾을 수 없습니다" });
  }
});

app.get("/samples/passed", function(req, res) {
  const passed = samples.filter(function(s) {
    return s.status === "pass";
  });
  res.json({ count: passed.length, samples: passed });
});

app.listen(3000, function() {
  console.log("시료 관리 서버 시작: http://localhost:3000");
});

이 서버는 세 개의 API 엔드포인트를 제공합니다:

  • GET /samples — 전체 시료 목록
  • GET /sample/S001 — 특정 시료 조회
  • GET /samples/passed — QC 통과 시료만 필터링

앞에서 배운 fetch로 이 서버에 요청하면:

javascript
fetch("http://localhost:3000/samples/passed")
  .then(function(response) { return response.json(); })
  .then(function(data) { console.log(data); });
// { count: 2, samples: [{ id: "S001", ... }, { id: "S003", ... }] }

프론트엔드(fetch)와 백엔드(Express)가 연결되는 순간입니다. NCBI에서 유전자를 검색할 때 뒤에서 벌어지는 일의 축소판입니다.

직접 해보기 (Faded Example)

아래 빈칸을 채워 유전자 정보를 반환하는 Express 라우트를 완성하세요.

빈칸 채우기javascript
const express = require("express");
const app = express();
app.get("/gene/", function(req, res) {
const geneId = req..id;
res.json({ gene: geneId, found: true });
});
app.listen();

흔한 에러 & 해결법

Q: Cannot GET /경로 에러가 나옵니다

해당 경로에 대한 라우트가 등록되지 않은 것입니다. app.get("/경로", ...) 코드가 있는지, 오타가 없는지 확인하세요. Express는 등록된 라우트가 없으면 자동으로 Cannot GET 메시지를 보냅니다.

Q: req.bodyundefined입니다

app.use(express.json())이 라우트보다 위에 선언되어 있는지 확인하세요. 미들웨어는 코드 순서대로 실행됩니다. JSON 파싱 미들웨어가 라우트 뒤에 있으면, 요청이 라우트에 도달할 때 아직 파싱이 안 된 상태입니다.

Q: 서버를 수정했는데 반영이 안 됩니다

Node.js는 코드를 수정해도 자동으로 반영되지 않습니다. 터미널에서 Ctrl+C로 서버를 중단하고 다시 node server.js로 실행하세요. 자동 재시작이 필요하면 nodemon을 설치해서 nodemon server.js로 실행합니다.

Q: Error: listen EADDRINUSE: address already in use :::3000

이미 3000번 포트를 사용하는 프로그램이 있습니다. 이전에 실행한 서버가 아직 살아있을 수 있습니다. lsof -i :3000으로 확인하고 종료하거나, 포트 번호를 3001 등으로 바꾸세요.

Q: Express가 "프레임워크"라고 하는데, npm에서 설치하는 다른 패키지(mysql2 등)와 뭐가 다른가요?

핵심 차이는 누가 누구를 부르는가입니다. mysql2는 라이브러리 — 내가 필요할 때 connection.query()를 호출합니다. Express는 프레임워크 — app.get("/path", 콜백)처럼 내 코드를 등록하면, 요청이 들어왔을 때 Express가 내 콜백을 호출합니다.

이 개념을 "제어의 역전(Inversion of Control)"이라고 합니다. 더 자세한 비유와 설명은 프레임워크와 라이브러리 토픽에서 다룹니다.