Express 서버에 데이터베이스 연결하기
지금까지 두 가지를 따로 배웠습니다. Express로 API 서버를 만드는 법, 그리고 SQL로 데이터베이스를 다루는 법. 이번에는 이 둘을 연결합니다.
이전 Express 예제에서 시료 데이터는 JavaScript 배열에 있었습니다:
const samples = [
{ id: "S001", name: "Blood Sample A", od: 1.85, status: "pass" },
// ...
];서버를 껐다 켜면 이 배열은 초기 상태로 돌아갑니다. POST로 새 시료를 등록해도, 서버를 재시작하면 사라집니다. 실험 노트에 연필로 적었다가 지우개로 매번 지우는 것과 같습니다.
데이터베이스를 연결하면 이 문제가 해결됩니다. Express가 데이터를 배열 대신 데이터베이스에 저장하고 꺼내게 만들면 — 서버가 꺼져도, 컴퓨터를 재시작해도, 데이터는 안전합니다.
연결 준비: mysql2 패키지 설치
Node.js에서 MySQL에 접속하려면 mysql2 패키지가 필요합니다:
npm install mysql2그리고 데이터베이스 연결 설정을 코드에 작성합니다:
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 (배열):
app.get("/samples", function(req, res) {
res.json(samples);
});After (DB):
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 인젝션이라는 보안 공격에 노출됩니다. 플레이스홀더(?)를 사용합니다:
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 요청으로 새 시료를 등록하고, 데이터베이스에 영구 저장합니다:
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
지금까지 배운 것을 합친 전체 서버 코드입니다:
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 라우트를 완성하세요.
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;로 변경할 수 있습니다.