유저 테이블과 API를 완성했으니 이번에는 할일 테이블과 API를 만든다.
유저 테이블의 PK인 id를 할일 테이블에서 user_id로 FK로 사용해서 데이터 무결성을 유지하고 두 테이블 간의 관계를 명확하게 표현하며, 효율적인 데이터 조회와 일관성을 유지할 수 있다.
CREATE TABLE todos (
id INT PRIMARY KEY AUTO_INCREMENT,
task VARCHAR(255) NOT NULL,
status VARCHAR(50) DEFAULT 'active',
user_id INT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
// 할일 등록
router.post("/todo/:userId", function (req, res) {
const { userId } = req.params;
const { task, status } = req.body;
// 먼저 userId에 해당하는 회원을 찾고 그 회원의 정수형 id 값을 가져옴
db.query("SELECT id FROM users WHERE userId = ?", [userId], (err, result) => {
if (err) {
return res.status(500).json({ message: "데이터베이스 오류", error: err });
}
if (result.length > 0) {
const userPrimaryKey = result[0].id; // 정수형 id 값 가져오기
// todos 테이블에 할일 추가
db.query(
"INSERT INTO todos (user_id, task, status) VALUES (?, ?, ?)",
[userPrimaryKey, task, status],
(err, result) => {
if (err) {
return res.status(400).json({
message: "할일 등록 중 오류가 발생했습니다.",
error: err,
});
}
res.status(201).json({
message: "성공적으로 할일을 등록하였습니다.",
});
}
);
} else {
res.status(400).json({
message: "존재하지 않는 회원입니다.",
});
}
});
});
로그인 한 회원을 먼저 조회하고 있는 경우에 할일을 등록해준다.
할일을 등록해줄때 FK로 지정한 user_id를 가져와야하기 때문에 변수 userPrimaryKey에 저장해서 사용한다.

// 할일 조회
router.get("/todo/:userId", function (req, res) {
const { userId } = req.params;
// 먼저 userId에 해당하는 회원을 찾고 그 회원의 정수형 id 값을 가져옴
db.query("SELECT id FROM users WHERE userId = ?", [userId], (err, result) => {
if (err) {
return res.status(500).json({ message: "데이터베이스 오류", error: err });
}
if (result.length > 0) {
const userPrimaryKey = result[0].id; // 정수형 id 값 가져오기
db.query(
"SELECT * FROM todos WHERE user_id = ?",
[userPrimaryKey],
(err, result) => {
if (err) {
return res.status(400).json({
message: "할일 조회 중 오류가 발생했습니다.",
error: err,
});
}
res.status(201).json({
message: "성공적으로 할일을 조회하였습니다.",
result,
});
}
);
} else {
res.status(400).json({
message: "존재하지 않는 회원입니다.",
});
}
});
});
해당 API는 param에 담긴 회원Id를 가지고 해당 회원이 users 테이블에 저장된 회원인지 확인 후 존재하는 회원이면 해당 회원의 id를 조회하여 정수형인 id를 userPrimaryKey에 저장해 해당 회원이 저장한 할일들을 조회하는 기능을 한다.
데이터베이스를 확인해보면 users를 전체 조회하면 
2명의 유저가 존재하고 있는 것을 볼 수 있고, 2명의 유저중에 id가 9번인 gunyongbokId2인 회원이 저장한 할일을 조회하게 되면 todos 테이블에 저장된 전체 할일들 중에서

user_id가 9인 정보만 response 해주게 된다.
아래는 localhost:7777/todo/gunyongbokId2 get 응답결과이다.
할 일을 삭제하는 API를 만들기전에 기존 API들처럼 회원이 존재하는지 확인을 해주는 코드를 넣어야 하는지 고민해봤다.
할 일을 삭제하는 상황이라면 이미 유저는 회원가입 후에 로그인을 하고 할 일을 등록 완료한 상태일텐데 회원 여부를 판단해야 할까? 생각이 들었다.
이때 문득 전에 독서록 프로젝트를 하며 벡엔드 개발자분이 만든 API들 헤더에 발급된 AccessToken 값을 필수적으로 담아서 보냈던 기억이 났다.
그때는 왜 담아서 보내는지 정확히 필요성을 몰랐는데 지금 돌이켜 생각해보니 로그인이 만료되어 로그인이 안되어 있는 상태에서 삭제 요청을 보냈는데 해당 API가 로그인 여부를 판단하지 않고 삭제해버리면 DB가 꼬이게 된다.
그래서 내린 결론은 삭제 요청을 하기 전에 user_id를 사용해서 해당 유저가 users 테이블에 존재하는 유저인지 확인 후에 존재한다면 삭제 요청을 할 수 있게 구현하였다.
삭제를 할때도 todos 테이블에 접근해서 삭제를 하기 때문에 로그인한 유저 외의 할일에 접근해서 삭제할 수 있기 때문에 아래와 같이 코드를 작성하였다.
// 할일 삭제
router.delete("/todo/:id", function (req, res) {
const { id } = req.params;
const { name } = req.body;
// 먼저 회원이 존재하는지 확인
db.query("SELECT id FROM users WHERE name = ?", [name], (err, result) => {
if (err) {
return res.status(500).json({
message: "데이터베이스 오류",
error: err,
});
}
// 회원이 존재하지 않으면
if (result.length === 0) {
return res.status(404).json({
message: "존재하지 않는 회원입니다.",
});
}
if (result.length > 0) {
const userPrimaryKey = result[0].id; // 정수형 id 값 가져오기
// 해당 회원이 존재할 때, 할일 삭제 진행
db.query(
"DELETE FROM todos WHERE id = ? AND user_id = ?",
[id, userPrimaryKey],
(err, result) => {
if (err) {
return res.status(500).json({
message: "할일 삭제 중 오류가 발생했습니다.",
error: err,
});
}
// affectedRows가 0이면 해당 id의 할일이 없거나, user_id가 일치하지 않는다는 뜻
if (result.affectedRows === 0) {
return res.status(404).json({
message: "해당 할일이 없거나, 권한이 없습니다.",
});
}
// 성공적으로 삭제되었을 경우
res.status(200).json({
message: "성공적으로 할일을 삭제하였습니다.",
});
}
);
}
});
});
코드를 작성하고 난 후 설계와 구조에 대해서 더 고민해보고 고쳐야겠다고 생각이 들었다.
아마 프론트와 연결하면서 요청값과 응답값 형태를 변경하고 수정하게 될 것 같다.
프론트 프로젝트를 진행했던 경험이 있어서 그런지 어떻게 API를 만들어 놓으면 화면 구성할 때 프론트엔드 개발자가 더 편하고 최소한으로 props를 전달해서 구현할 수 있을지 알 수 있었다.
비록 프론트엔드 개발을 희망하고 있지만 타 직군의 역할을 이해하고 경험해보는 것은 좋은 결과물을 내는데 엄청 중요하고 유익하다는 것을 직접 해보면서 알게 되었다.
추가로 할 일 수정 부분은 프론트와 연결한 후에 구현할 예정이다.