
깃허브 주소
https://github.com/CodeitFS5th-middle-project-team1
CodeitFS5th-middle-project-team1
CodeitFS5th-middle-project-team1 has 3 repositories available. Follow their code on GitHub.
github.com
배포 주소
(프론트)
https://doc-thru-fe.vercel.app/
(백엔드)
https://docthru-be-wstf.onrender.com/
프로젝트 주제
대다수의 개발 시장 콘텐츠가 영어로 작성되어 있어, 영어를 잘 하지 못하는 한국인들이 해당 기술을 습득하는데 어려움을 겪고 있습니다. 그래서 개발 관련 영어 문서를 여러 사람들과 함께 한글화 작업을 하는 챌린지를 진행하고, 번역 에디터에서 번역을 진행하고, 번역물에 대한 피드백을 주고받을 수 있는 서비스입니다.
일정 및 팀원
진행: 2025년 3월 24일 ~ 2025년 4월 16일
팀원 구성: 풀스택 6명으로 구성
담당했던 부분
프론트 : 작업물 에디터 (수정 및 생성 페이지)
백엔드 : 챌린지 관련 조회, 수정, 삭제 (관리자 일부 포함) 등 API 약 9개
기술 스택
프론트엔드: Next.js, TypeScript, Tailwind, Storybook
백엔드: Express, TypeScript, Prisma
DB: PostgreSQL
배포: (프론트) Vercel, (백엔드) Render
협업 방식



노션 및 깃허브 프로젝트를 사용하여 문서화 작업 및 소통하고 작업 현황을 확인하도록 했습니다.

또한 스웨거를 사용하여 프론트 쪽에서 API를 테스트하거나 들어가야할 쿼리 및 바디 값의 조건을 확인할 수 있었습니다.
스웨거 주소:
https://docthru-be-wstf.onrender.com/api-docs/
요구사항 분석

요구사항 분석을 할 때 사용자 입장에서 어떤 행동을 했을 때 어떤 것이 가능해야한 지를 고려했습니다.
하지만, 요구사항을 초반에 작성했을 때보다 프로젝트를 진행하면서 필요한 부분들이 추가적으로 더 보였기 때문에 다음 프로젝트를 진행할 때는 요구사항을 좀 더 면밀히 살펴보려고 합니다.
데이터베이스 모델링

초기에 DB 모델링을 진행할 때는 관계를 설정하지 않았다가, 프로젝트를 진행하면서 백엔드 개발을 시작했을 때 관계 없이 로직만으로 구현하다가 로직이 복잡해지고, 그로 인해 개발 속도가 늦어지는 문제가 발생하였습니다.
따라서 관계를 추가하고, 그에 따라 스키마를 변경해주었습니다. 관계가 걸려있는 테이블끼리는 조회하기가 수월해졌고, 그에 따른 조건을 또 걸기도 하면서 SQL에 대한 이해를 조금 더 할 수 있었던 계기가 되었습니다.
폴더 구조
- 백엔드

기본적으로 도메인형 디렉토리 구조를 따르려고 했습니다. routes에서는 경로 설정 및 인증 미들웨어, 검증 미들웨어를 넣어주었고 controller에서는 service와 상호작용하며 req를 검증하거나, service에서 DB에 접근하여 받은 result를 다시 return받아 response 처리를 해주도록 구현했습니다.
- 프론트

프론트는 기본적인 앱라우터 폴더구조를 따랐으며, 각 페이지마다 쓰이는 컴포넌트를 components 폴더에 따로 분리해서 작업했습니다. api같은 경우에는 공통으로 사용하는 customFetch를 만들어 팀원 전체가 사용하도록 구현되었습니다.
기능 설명

로그인 및 회원가입은 jwt 방식으로 구현이 되었습니다. access 토큰은 쿠키에 httpOnly 옵션 없이, refresh 토큰은 쿠키에 httpOnly 및 secure 옵션을 적용하여 저장하도록 하였습니다.

필터를 통해 원하는 정보만 필터링하여 볼 수 있습니다. 또한 로그인 이후에는 알림이나 나의 챌린지로 갈 수 있는 프로필 메뉴 등이 있습니다.


작업물을 작성했을 시에는 참여 현황에 뜨게되며, 해당 챌린지에 인원수가 추가되게 됩니다.
또한 마감시간이 챌린지 생성시에 설정할 수 있게 되어있는데 이 부분은 크론탭을 사용하여 마감시간이 지나면 백엔드 쪽에서 자동으로 크론탭이 실행되어 DB에서 마감 시간을 현재 시간과 비교하여 마감 처리를 하게됩니다. (매일 자정)


이렇게 챌린지가 마감 된 상태에서는 작업 도전하기 버튼이 비활성화가 되고, 챌린지 목록에서도 태그로 보여지게 됩니다.




제가 프론트 쪽에서 담당했던 부분인데 임시 저장을 하고, 다시 페이지에 방문하면 "임시 저장을 불러오겠습니까?" 라는 모달이 뜨게 됩니다. 그리고 불러오기를 하면 이전에 임시저장했던 작업이 불러와집니다.
해당 데이터는 DB에 저장하였으나 다음에는 이런 꽤 큰 데이터들은 별도의 스토리지(ex: S3)를 사용해야겠다고 판단했습니다.


번역물을 작성하고 좋아요를 누를 수 있습니다. (계정당 번역물 1개에 좋아요 1개만 가능)
마감기한이 끝나면 최다 추천 번역 목록을 챌린지 상세 페이지에서 확인할 수 있습니다.
KPT
keep(잘한 점)
- 챌린지 관련 백엔드 API에서 고려할 점이 많아서 중간 중간 수정이 많이 필요했지만 최종적으로는 잘 동작하도록 구현했습니다.
- 처음 접하는 라이브러리들이 있었는데, 관련 자료를 찾아보면서 사용했습니다.
problem(문제 점)
- UX 개선을 하기 위해 코드를 여러 번 수정 시도 했으나, 아직 개념적인 이해도가 낮아서 어떤 부분을 수정해야할 지는 생각해뒀으나 실제로 그러한 문제점들을 수정하는데에 있어서 어려움을 많이 겪었습니다. 한 번 이론적인 부분을 정리하는 시간을 주 1~2회정도 가지면서 노션이나 블로그에 정리 해야 할 것 같습니다.
- 의사소통 시 적극적으로 참여하고 의견을 제시하고 싶었지만 그 의견을 뒷받침할 논리적인 부분들이 많이 부족해서 다른 팀원 분들을 설득하는 능력이 부족했던 것 같습니다.
- 백엔드 프로젝트 초기 설정 시에 컨벤션을 더 디테일하게 잡았으면 좋았겠다는 생각이 들었습니다.
Try(앞으로 시도할 점)
- 주 1~2회 노션이나 블로그에 학습한 내용 간단하게 정리하면서 복습하기
- 이론적인 부분들 중에 핵심 내용들은 잘 기억해두도록 반복 학습하기
- 초반에 프로젝트 진행 중에 지켜야 할 컨벤션등을 잘 잡아놓기
- 기술적 특징을 잘 고려하여 UX 개선을 더 신경쓰기
- 로직을 작성할 때 설명을 더 자세하게 적고, 확장성과 응집성을 고려하여 코드를 잘 분리함으로서 유지보수성을 높일 수 있도록 계속해서 고민해보기.
기술적 성과
- 백엔드 작업 시 컨벤션을 어떤 식으로 맞춰야 하는 지 경험해볼 수 있었습니다.
- zod를 사용해 body, param, query등 자동으로 타입을 생성해서 Controller 커스텀 제너릭 함수에 적용할 수 있었습니다.
- express 폴더 구조를 도메인 별로 분리해보았는데, 어떤 기준으로 나누어야 할 지 고민 해보는 시간을 가질 수 있었습니다.
- 프론트 작업에서 react-quill을 사용한 에디터를 구현하였는데, 어떤 식으로 반환하는 지, 어떤 식으로 처음에 기본 값이 설정되는 지 등에 대한 고민을 해볼 수 있었습니다.
문제점 및 해결 과정
- 백엔드 컨트롤러에서 타입을 일일이 지정해 주어야 하는 번거로움도 있었고, 작업 하는 사람마다 다르게 작성될 가능성도 있었습니다.(req, res) 그래서 아래와 같이 커스텀 컨트롤러 타입을 맞춰주고 모든 컨트롤러에 해당 타입들을 사용하여 서로가 사용하는 타입에 대해 어느 정도 맞출 수 있었습니다.
import { Request, Response, NextFunction } from 'express';
// GET 요청용 컨트롤러
export type GetController<P, Q, D> = (
req: Request<P, any, any, Q>, // Body는 없음
res: Response<D>,
next: NextFunction
) => Promise<void>;
// POST 요청용 컨트롤러
export type PostController<P, B, D> = (
req: Request<P, any, B>, // Query는 없음
res: Response<D>,
next: NextFunction
) => Promise<void>;
// PATCH 요청용 컨트롤러
export type PatchController<P, B, D> = (
req: Request<P, any, B>, // Query는 없음
res: Response<D>,
next: NextFunction
) => Promise<void>;
// DELETE 요청용 컨트롤러
export type DeleteController<P, Q, D> = (
req: Request<P, any, any, Q>, // Body는 없음
res: Response<D>,
next: NextFunction
) => Promise<void>;
- react quill에 초기값이 잘 들어가지 않는 문제 해결
번역물 수정 페이지에서 react quill에 기존 번역물 데이터를 가져오려고 했는데,
가져와 질 때도 있고, 가져와지지 않을 때도 있었습니다.
그래서 문제 원인을 분석해본 결과 react quill은 초기에 받아온 값에 대해서만 초기값이 세팅되고 그 뒤에는 변경되지 않아서 처음에 빈 값이 먼저 들어와버려서 그 뒤에 들어온 번역물 데이터가 react quill에 들어오지 않았던 것 이었습니다.
그래서 에디터를 보이게 하는 시점을 isLoaded가 진행 된 후 (번역된 데이터 가져온 후)로 설정해두고 그 뒤에만 렌더링 되게 하였습니다.

협업 및 피드백
- 저희는 피드백을 PR에, 공유사항을 노션에 최대한 정리하였습니다. 그리고 서로의 문제상황을 어느정도 고민하다가 막히면 바로 팀원끼리 소통하여 문제를 해결하려고 했습니다. 그런 과정을 겪으면서 문제를 빠르게 해결하기도 하였고, 서로간의 의견 차이가 발생했을 때 어떤 식으로 자신의 의견을 얘기하는지, 어떤 식으로 상대방의 의견에 대해 수렴하는 지 고민해볼 수 있는 시간을 가졌습니다.
코드 품질 및 최적화
- 사실 넉넉한 시간동안 개발한 것이 아니어서 최적화를 많이 고려하지 못했습니다. 하지만, 어떤 부분을 개선하면 좋을 지에 대해서는 생각해봤기 때문에 그러한 부분들을 다음 프로젝트를 진행할 때 개선해 나갈 생각입니다.
- ex: 코드 리팩토링, 폴더 구조, 커스텀 훅 등
향후 개선 사항 및 제안
- UX적인 부분에 대한 수정이나, 새로운 기능들 (ex: 채팅 or AI를 이용한 번역물 추천이나 번역 도우미 등)을 추가하는 것도 좋을 것 같습니다. 또한 폴더 구조를 어떤 식으로 짜고, 컨벤션을 처음에 어떤 것들을 정해야 하는 지에 대해서 더 고민해봐야 할 것 같습니다.
프로젝트를 마치며
팀원분들과 의사소통하며 문제를 해결해나가는 과정들이 즐거웠습니다. 팀원분들이 다들 적극적으로 참여해주셔서 저도 더 열심히 할 수 있는 계기가 되었던 것 같습니다. 이번에는 앱 라우터의 기능들을 많이 사용하지 못했지만 다음 프로젝트에서는 앱 라우터에 대한 이론적인 부분들에 대해 더 학습하고 많은 기능을 경험해보고, UX 관점에서 어떤 식으로 접근해야할 지 꼭 고려해보겠습니다.
'프로젝트' 카테고리의 다른 글
| 공부의 숲 프로젝트 회고록 (2) | 2025.07.11 |
|---|---|
| 무빙(Moving) - 이사 서비스 프로젝트 회고록 (3) | 2025.06.23 |