BioPlayground

🧬
목록으로

Express 서버에 데이터베이스 연결하기

Express 서버에 MySQL을 연결해 시료 관리 CRUD API를 만드는 법. 바이오 예제로 백엔드 데이터 영속성을 배웁니다.

입문
|
60
|
검증 완료 (2026-06)
Node.jsMySQL데이터베이스 연동SQL 쿼리CRUD APIREST API
트랙 진행률0/11 (0%)

Express 서버에 데이터베이스 연결하기

지금까지 두 가지를 따로 배웠습니다. Express로 API 서버를 만드는 법, 그리고 SQL로 데이터베이스를 다루는 법. 이번에는 이 둘을 연결합니다.

이전 Express 예제에서 시료 데이터는 JavaScript 배열에 있었습니다:

javascript
const samples = [
  { id: "S001", name: "Blood Sample A", od: 1.85, status: "pass" },
  // ...
];

서버를 껐다 켜면 이 배열은 초기 상태로 돌아갑니다. POST로 새 시료를 등록해도, 서버를 재시작하면 사라집니다. 실험 노트에 연필로 적었다가 지우개로 매번 지우는 것과 같습니다.

데이터베이스를 연결하면 이 문제가 해결됩니다. Express가 데이터를 배열 대신 데이터베이스에 저장하고 꺼내게 만들면 — 서버가 꺼져도, 컴퓨터를 재시작해도, 데이터는 안전합니다.

연결 준비: mysql2 패키지 설치

Node.js에서 MySQL에 접속하려면 mysql2 패키지가 필요합니다:

bash
npm install mysql2

그리고 데이터베이스 연결 설정을 코드에 작성합니다:

javascript
const mysql = require("mysql2");

const db = mysql.createConnection({
  host: "localhost",
  user: "root",
  password: "비밀번호",
  database: "lab_db"
});

db.connect(function(err) {
  if (err) {
    console.log("DB 연결 실패:", err.message);
    return;
  }
  console.log("DB 연결 성공");
});

실험 장비에 로그인하는 것과 같습니다 — 주소(host), 계정(user/password), 어떤 데이터베이스를 쓸지(database)를 알려줍니다.

배열을 DB로 교체: SELECT

기존 Express 코드에서 배열 대신 SQL 쿼리로 데이터를 가져옵니다.

Before (배열):

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

After (DB):

javascript
app.get("/samples", function(req, res) {
  db.query("SELECT * FROM samples", function(err, rows) {
    if (err) {
      res.status(500).json({ error: "DB 조회 실패" });
      return;
    }
    res.json(rows);
  });
});

db.query()는 SQL을 데이터베이스에 보내고, 결과를 콜백으로 받습니다. rows는 배열 형태로 돌아옵니다 — 기존에 직접 만들었던 배열과 같은 구조입니다. 프론트엔드 코드는 바꿀 필요가 없습니다.

특정 시료 조회: WHERE와 플레이스홀더

URL 파라미터로 특정 시료를 조회할 때, SQL에 사용자 입력을 직접 넣으면 SQL 인젝션이라는 보안 공격에 노출됩니다. 플레이스홀더(?)를 사용합니다:

javascript
app.get("/sample/:id", function(req, res) {
  db.query(
    "SELECT * FROM samples WHERE id = ?",
    [req.params.id],
    function(err, rows) {
      if (err) {
        res.status(500).json({ error: "조회 실패" });
        return;
      }
      if (rows.length === 0) {
        res.status(404).json({ error: "시료를 찾을 수 없습니다" });
        return;
      }
      res.json(rows[0]);
    }
  );
});

? 자리에 [req.params.id] 값이 안전하게 삽입됩니다. 이렇게 하면 악의적인 사용자가 URL에 SQL 코드를 넣어도 데이터베이스가 그것을 데이터로만 취급합니다.

프로토콜에서 시료 번호만 바꿔 넣는 칸을 만들어 놓는 것과 같습니다 — 그 칸에 무엇을 넣든 시료 번호로만 해석되지, 프로토콜 자체를 바꿀 수는 없습니다.

시료 등록: INSERT

POST 요청으로 새 시료를 등록하고, 데이터베이스에 영구 저장합니다:

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

app.post("/samples", function(req, res) {
  const { name, od, status } = req.body;

  db.query(
    "INSERT INTO samples (name, od, status, created_at) VALUES (?, ?, ?, NOW())",
    [name, od, status],
    function(err, result) {
      if (err) {
        res.status(500).json({ error: "등록 실패" });
        return;
      }
      res.json({
        message: "시료 등록 완료",
        id: result.insertId
      });
    }
  );
});

result.insertId는 방금 추가된 행의 자동 생성 ID입니다. NOW()는 현재 시간을 자동으로 넣는 MySQL 함수입니다.

종합 예제: 시료 관리 CRUD API

지금까지 배운 것을 합친 전체 서버 코드입니다:

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

app.use(express.json());

const db = mysql.createConnection({
  host: "localhost",
  user: "root",
  password: "비밀번호",
  database: "lab_db"
});

// 전체 시료 목록
app.get("/samples", function(req, res) {
  db.query("SELECT * FROM samples ORDER BY created_at DESC", function(err, rows) {
    if (err) return res.status(500).json({ error: err.message });
    res.json(rows);
  });
});

// 특정 시료 조회
app.get("/sample/:id", function(req, res) {
  db.query("SELECT * FROM samples WHERE id = ?", [req.params.id], function(err, rows) {
    if (err) return res.status(500).json({ error: err.message });
    if (rows.length === 0) return res.status(404).json({ error: "시료 없음" });
    res.json(rows[0]);
  });
});

// 시료 등록
app.post("/samples", function(req, res) {
  const { name, od, status } = req.body;
  db.query(
    "INSERT INTO samples (name, od, status, created_at) VALUES (?, ?, ?, NOW())",
    [name, od, status],
    function(err, result) {
      if (err) return res.status(500).json({ error: err.message });
      res.json({ message: "등록 완료", id: result.insertId });
    }
  );
});

// QC 통과 시료만 필터링
app.get("/samples/passed", function(req, res) {
  db.query("SELECT * FROM samples WHERE status = 'pass'", function(err, rows) {
    if (err) return res.status(500).json({ error: err.message });
    res.json({ count: rows.length, samples: rows });
  });
});

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

이 서버의 특별한 점 — 배열 기반 Express 서버와 API 인터페이스가 동일합니다. /samples, /sample/:id, /samples/passed — 같은 주소, 같은 응답 형식. 바뀐 것은 내부 저장소뿐입니다. 프론트엔드 코드는 한 글자도 수정할 필요가 없습니다.

이것이 백엔드와 프론트엔드를 분리하는 가장 큰 이점입니다. 저장소를 파일에서 MySQL로, MySQL에서 PostgreSQL로 바꿔도, API가 같으면 프론트엔드는 영향을 받지 않습니다.

직접 해보기 (Faded Example)

아래 빈칸을 채워 특정 연구자의 시료를 조회하는 Express 라우트를 완성하세요.

빈칸 채우기javascript
app.get("/researcher/:name/samples", function(req, res) {
db.(
"SELECT * FROM samples WHERE researcher = ",
[req..name],
function(err, rows) {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
}
);
});

흔한 에러 & 해결법

Q: ER_ACCESS_DENIED_ERROR: Access denied for user 에러가 납니다

createConnection의 user, password, database가 맞는지 확인하세요. MySQL에 해당 계정이 존재하고, 해당 데이터베이스에 접근 권한이 있어야 합니다.

Q: ECONNREFUSED 에러가 납니다

MySQL 서버가 실행되고 있지 않습니다. 맥에서는 brew services start mysql, 리눅스에서는 sudo systemctl start mysql로 MySQL을 먼저 시작하세요.

Q: 쿼리 결과가 빈 배열 []로 돌아옵니다

테이블에 데이터가 없거나, WHERE 조건에 맞는 행이 없습니다. 먼저 MySQL 클라이언트에서 SELECT * FROM samples;를 직접 실행해서 데이터가 있는지 확인하세요.

Q: 한글 데이터가 깨져서 저장됩니다

데이터베이스와 테이블의 문자셋(charset)이 utf8mb4인지 확인하세요. ALTER DATABASE lab_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;로 변경할 수 있습니다.