같은 목표를 가지고 같이 공부할 사람을 찾는 웹 사이트 구현 프로젝트 (2022/08/17 ~ 2022/09/01)
각 팀원이 하고 싶은 주제를 몇 가지 제안하고, 그중 투표를 통해서 주제가 선정되었다. 맛집 리뷰사이트, 일정 관리사이트, 문제 풀이 사이트, 퀴즈 만들기 사이트 등 여러 주제가 제안되었고, 감사하게도 그중에 내가 제안했던 스터디그룹을 찾는 사이트가 주제로 선정되었다!
위스터디 https://westudy.kr/
공부작당소 https://www.gongzakso.com/
위 두 개의 사이트와 앱을 참고하게 되었다. 우리가 개발하는 것은 웹 사이트이지만, 앱에서 제공하는 좋은 UI&UX가 있다면 참고해서 웹 사이트에 적용해도 좋을 것 같다는 생각이었다.
처음 어떤 서비스를 제공하는 사이트를 운영할 것인지 팀원들과 회의를 통해 주요 기능을 정하고, 추가로 진행할 수 있을 때 제공할 부가 가능으로 나누어서 정해보았다.
후에 세부 기능을 작성해보자고 해서, 나는 구글 시트로 간단하게 요구사항 정의서 같은 템플릿을 제작해서 팀원들에게 공유했다. 위에서 크게 정했던 주요 기능을 조금 더 세부적으로 나누어 프론트에서 필요한 이벤트들이나, 백에서 처리해야 할 것들을 조금 더 자세하게 설계해보았다.
리더는 시하님이,
Front-end
는 세준님, 유하님, 미진님이 맡게 되었고Back-end
은 시하님과 내가 맡게 되었다. 하지만 프로젝트를 진행하다 보니Front-end
가 할 일이 많아 거의Full-stack
으로 하게 되었다(?)
설계 기간에 시하님과 함께
DB
모델링을 했다. 주요 기능을 미리 설계해두었더니,DB
구조를 생각하는 데 훨씬 수월했다. 처음엔User
,Studygroup
,Studymember
,Likes
정도의table
만 설계했었다.
❗ 하지만 개발을 진행하면서 생각해보니, 스터디 가입과 가입한 스터디 그룹을 관리하는 기능만 있고 그룹 멤버들 간에 서로 소통할 수 있는 창구가 없다는 걸 깨닫게 되었다. 그래서 가입한 멤버들끼리 게시물을 올리고 댓글을 달 수 있는
Studylounge
라는 공간을 추가로 개발하기로 결정하게 되었고, 나중에Studylounge
와Reply
table
을 설계하게 되었다.
/* init.sql file example*/
CREATE DATABASE swith; -- DB 생성
USE swith; -- DB 사용
-- TABLE 생성
CREATE TABLE user (
user_id VARCHAR(20) NOT NULL PRIMARY KEY, -- 유저 id
user_password VARCHAR(45) NOT NULL, -- 유저 비밀번호
hint VARCHAR(10) NOT NULL, -- 비밀번호 찾기 질문 (1, 2, 3, ..)
hint_answer VARCHAR(50) NOT NULL, -- 비밀번호 찾기 답
user_name VARCHAR(6) NOT NULL, -- 유저 닉네임
user_email VARCHAR(45) NOT NULL, -- 유저 이메일
user_image VARCHAR(100) DEFAULT 'user_default.jpg', -- 유저 프로필 이미지
category1 VARCHAR(20) NOT NULL, -- 관심 카테고리 1
category2 VARCHAR(20) NOT NULL, -- 관심 카테고리 2
category3 VARCHAR(20) NOT NULL, -- 관심 카테고리 3
join_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -- 유저 가입 날짜
);
먼저,
DB
설계를 토대로MySQL
문법으로database
와table
을 생성하는 코드를 작성했다.Studylounge
와Reply
table
생성SQL
문은 시하님이, 나머지는 내가 작성했다.
❗ 개발을 진행하다 보니 생각보다 문자열의 길이 제한을 수정하는 일이 많았다. 예를 들면, 유저 닉네임을 넉넉히 받으려고 했는데 디자인적으로 통일감을 주기 위해 6자로 다시 제한하는 수정이 필요했다.
/* model/User.js file example */
//foreign key 설정
User.associate = models => {
// 1:N 관계, studygroup의 head_id가 user의 user_id를 참조 하고 있다.
User.hasMany(models.Studygroup, {foreignKey: "head_id", sourceKey: 'user_id', onDelete: 'cascade', onUpdate: 'cascade'});
// N:M 관계
User.belongsToMany(models.Studygroup, { through: 'Studymember' });
// N:M 관계
User.belongsToMany(models.Studygroup, { through: 'Likes' });
// N:M 관계
User.belongsToMany(models.Studygroup, { through: 'Studylounge' });
// N:M 관계
User.belongsToMany(models.Studylounge, { through: 'Reply' });
};
모델은
sequelize
를 이용해서 생성했다. 나는index
,User
,Studymember
,Studygroup
,Studylounge
,Reply
,Likes
이렇게 총 7개의 파일을 생성했다. (어쩌다 보니 다 만들게 되었다)
migration
은 따로 설정해주지 않았다.index.js
파일에 모든 모델의 관계 설정 코드를 작성하려니 너무 길고 보기에 깔끔하지 않아서foreign key
설정은index.js
파일이 아닌 각 모델 파일에서 설정해 주었다.
❗
sequelize
를 이용하여 DB 관계를 정의해주는 부분이 조금 어려웠다.DB
설계 단계에서는 관계형 데이터베이스에 대한 이해가 필요했다면,sequelize
에서는migration
을 사용하지 않고foregin key
를 설정해주는 방법이 필요했다.1:N
관계 정의는hasMany()
와belongsTo()
를,N:M
은belongsToMany()
,belongsToMany()
를 사용하면 된다는 것을 알게 되었다.
❗ 또한 처음
N:M table
을 생성할 때(ex. Studymember table
)primary key
를 사용할 일이 없다고 생각해 따로 생성해주지 않았다. 그랬더니 나중에Controller
에서query
문을 실행하니 해당 테이블에서 PK를 임의로 만들고, 그 키가 없다는 오류가 뜨는 것이다. 찾아보니 기본키가 없는 것이 규칙에 어긋나는 것은 아니지만, 관계형 모델을 따르려면 한 테이블에 있는 특정 로우와 나머지 로우를 구별할 수 있어야 하므로 기본키가 있어야 한다는 것을 알게 되었다. 그래서 모든N:M
table
에autoIncrement
를 사용해서 기본키 컬럼을 생성해주었다.
처음엔 회원가입 페이지
SignupController
를, 그 후에는 게시물 상세 조회 페이지DetailedPostController
와 게시물 관리페이지ManagementController
의 백을 진행했고,sequelize
로CRUD
를 진행했다.
회원가입 페이지에서는 사용자에게 입력받은 데이터를
User table
에 저장하고, 닉네임과 아이디 중복확인 기능을 구현했다.
스터디 그룹 모집 게시물을 보여주는 상세 조회 페이지에서는 유저에 따라
view
에서 보여줘야 하는 데이터가 달랐다.
1. 게시물을 작성한 그룹장이 조회한 경우
2. 이미 해당 스터디 그룹에 가입한 멤버인 경우
3. 아직 해당 스터디 그룹에 가입하지 않은 경우
그래서findOne()
을 이용해서check
하고, 해당 페이지로 바로 렌더링하는 형식으로 구현했다.
❗ 이 페이지에서는 스터디 그룹 정보 + 그룹장 정보 + 가입한 멤버의 정보 + 현재 유저의 해당 게시물 좋아요 여부까지 한 번에 보내줘야 했기 때문에
join
을 사용하고도union
까지 사용하게 되었는데, 좋은 코드는 아니지 않았나 생각한다.
❗
sequelize
innerjoin(=include)
을 사용하고 싶었는데, 위와 같은 이유로union
에 이중join
을 사용했기 때문에 너무 복잡해서 raw query를 사용하게 되었다.
그룹장일 경우에만 조회할 수 있는 게시물 관리 페이지에서는 게시물 정보를 수정하거나 삭제할 수 있는 기능을 구현했다.
❗ 게시물을 삭제할 때, 스터디 그룹 정보
studygroup table
의 내용을 삭제하는 것뿐만 아니라 가입한 멤버의 정보studymember table
, 스터디 라운지 정보studylounge table
, 여기에 댓글 정보reply table
까지 삭제해버려야 했다. 하지만 이table
들은N:M
관계로 연결되어 있어ondelete 'cascade'
설정을 해줄 수 없었다. 결국 일일이 먼저 삭제해줘야 비로소studygroup
정보를 삭제할 수 있었다.
Front-end
에선 크게 메인페이지, 네비바, 회원가입 페이지, 스터디 상세조회 페이지, 스터디 관리 페이지를 맡게 되었다.
메인 페이지는
front
담당이신 세준님이 구현해 주셨다. 구현된 페이지를 보니 fullpage.js 라이브러리를 사용하면 어울릴 것 같다는 조언을 듣고, 저번 클론코딩 때 사용해 본 적이 있던 내가 라이브러리 적용을 하게 되었다.
네브바에서 로그인 한 상태에선 로그아웃이, 그 외에는 로그인으로 띄워서 로그인 페이지로 넘어갈 수 있게 끔 구현했다. 세션 값을 이용해서 세션 값이 있을 경우 로그아웃으로 보이게 설정하고, 로그아웃 클릭 시
LoginController
에서destroy()
로 세션값을 삭제해주었다.
❗ 하지만 이렇게 세션값을 삭제하니, 로그인의 "아이디 기억하기" 기능이 제대로 실행되지 않았다. 그래서 세션값을 아예 삭제하지 않고, 비어있는 문자열로 바꾸어주었다.
회원가입 페이지에서는
css
부분을 제외한axios
,form tag
처리를 진행했다. 서버로 프로필 사진을 업로드 하는 기능은 multer 미들웨어로 구현했다.
❗ 특히 프로필 사진을 올리면 바로 해당 이미지를 띄우고 싶었다. 그러려면 동적 폼 전송으로는 불가능하고,
axios
를 사용해서 비동기 처리를 해주어야 했다.formdata
객체를 사용하여data
를post
로 보내주고 받은 파일명을 회원가입시User table
에 같이 저장해주었다.
스터디 상세조회 페이지에서는
script
와 약간의css
, 데이터를ejs
문법을 사용해서 불러오는 것, 해시태그 띄우는 부분을 구현했다. 추가로 카카오 공유 API를 사용해서 공유하기 기능을 구현했다.
스터디 관리 페이지에서는
Axios
처리와 같은sciprt
부분과 데이터 불러오는 것을 구현했다.
❗ 데이터를 불러올 떼 날짜나 카테고리 정보 같은 경우는
DB
에 저장된 정보를 그대로input
창에 띄울 수 없었다.jQuery
의prop("checked", true)
를 통해 각 데이터가 이미 선택되어 있는 것 처럼 보이도록 했다.
❗ 회원가입과 로그인은 물론 스터디 그룹 게시물 등록과 수정, 스터디라운지에서의 게시물 등록 등 입력된 데이터를
insert
,update
하는 기능이 많았다. 하지만 이런 기능은 바로query
조회로 이어지기 때문에 데이터의 유효성 검사가 필요했다. 유효성 검사 프레임 워크를 따로 사용하진 않고, 직접 코드를 구현했다. 그리고 sweetalert 라이브러리를 이용해서 유저에게 알림창을 띄우도록 구현했다.
❗ 회원가입시 유저의 프로필이미지, 스터디 그룹 모집 게시물 작성시 그룹 이미지를 받을 때 모두 이미지를 업로드 하지 않았을 때를 고려해야 했다. 또한 그렇게 저장되었을 때 조회나 수정 페이지에서 어떤 사진을 띄워야 하는지도 고려해야 했다. 그래서 임의로 기본 이미지를 고르고, 이미지를 업로드 하지 않았을 때 해당 사진을
default
로 가지도록 설계했다.
❗ 스터디 그룹 모집 게시물을 등록할 때 소개글은
textarea
로 입력 받았기 때문에 개행문자\n
가 포함되어DB
에 저장되었는데, 상세 조회 페이지에서<div>
태그로는data
의 개행문자가 인식되지 않았다. 그래서\n
을</br>
로 바꿔주는 작업을 통해 해결했다.
Team
이란..
Team convention
을 먼저 정하고 진행하지 못했던 점이 아쉽다.Github
을 사용하는 것은commit
이나merge
를 할 때 내가 어떤 작업을 했는 지 다른 팀원에게 명확하게 알려줄 수 있다는 장점이 있는데,convention
없이 각자 중구난방으로 진행한 것이 매우매우 아쉽다.
Back-end
로써..
1. 데이터베이스에서 테이블과 여러 컬럼이 생성되고 수정되는 상황에 더 안정적인 서비스를 운영하기 위한 트랜잭션 처리를 해주지 못한 점이 아쉽다. 처음에는 막상migration
이 꼭 필요하지 않을 것이라 생각했는데, 개발을 진행하다 보니 롤백 기능이 있는migration
을 그냥 같이 했었으면,,,하는 생각이 들었다.
2. 보안 관련 처리를 하지 못했다. 가장 흔한password
암호화 마저 구현하지 못했다. 규리쌤이 알려주신다고 할 때 일단 배워두고 볼 걸 그랬다.
전체적으로 갈 길이 멀다..
줄줄이 얘기해보자면 일단 중복된 코드 정리도 안되어 있다.navbar
와css
, 라이브러리와 같이 중복된script
마저..
그리고try .. catch
문을 적극적으로 활용해서 에러 핸들링 처리를 했다면 손해보는 건 없었을 듯 하다.
또한 테스트 코드를 구현할 생각은 전혀 해보지 못했다. 테스팅 라이브러리 종류가 많던데 나중에 사용해보고 싶다.일단 시작하기 편하다는 Jest로?
그리고css
스타일 초기화(Reset.css
,Normalize.css
)를 할 수 있다는 사실을 프로젝트가 끝나고 나서 알게 되었다. 미리 알았더라면 적용한 후에 진행해보았을 텐데 아쉬웠다.
이번 프로젝트를 진행하면서
API
를 사용해보고 싶었는데, 크게 어려운 건 아니였지만 카카오톡 공유API
를 사용해보았다는 점이 뿌듯하다. 흔하게 사용하는 로그인API
도 디벨롭할 때 꼭 적용해보고 싶다.
개인적으로는 코드를 구현하면서 주석을 다 달아주었는데, 나중에 돌아보니 가장 잘한 일이 아닌 가 싶다. 남는 건 기록 뿐.
그래도 훌륭한 팀원들과 으쌰으쌰하면서 짧은 기간동안 이만큼의 기능을 일단 구현했다는 것 자체가 자랑스럽다! 앞으로도 계속해서 디벨롭시켜 더 더 안정적인 서비스로 구축해보고 싶다.
확실히 데이터베이스를 좀 더 깊게 배울 수 있었다. 관계형 데이터베이스에 대한 지식도 부족했었고, join이나 서브쿼리 등 쿼리를 다루는 것에 조금은 두려움이 있었는 데 프로젝트를 진행하면서 많이 친근해지고, 만들고 싶은 쿼리문을 작성할 수 있게 되었다.
클라이언트와 서버 사이의 동작과정을 이해하고, 비동기 HTTP요청과 응답을 구현할 수 있게 되었다.
맡은 역할은 백엔드 였지만, 상황에 따라 유동적으로 프론트 처리를 같이 진행했더니 오히려 양측면에서 모두 실력이 오른 것 같아서 역량이 많이 커진 것 같다.
그리고 지금 회고록 작성을 일기처럼 꼼꼼하게 작성하다 보니 굉장히 시간이 많이 소요되고 힘들다... 다음에는 그때그때 작성해두면 더 좋을 듯 싶다!
Github https://github.com/SeSAC43-Project/StudyWith
Notion https://hushed-hospital-019.notion.site/StudyWith-e93a4cc462c24d248243f0695eeaca56