- How To ?
- Before We Start
- Conventions & Template
- Git Convention : Strategy
- Git Convention : Commit
- Code Convention
- Project Architecture
- 설치 및 실행 방법을 작성해주세요.
./config.json
은 다음과 같은 형식입니다.
사용하실 값을 채워서 사용해주세요.
{
"SERVER": {
"PORT": 7777,
"FRONTEND_URL": "",
"SALT_ROUNDS": 12,
"JWT_SECRET": "",
"CIPHER_SECRET_KEY": ""
},
"DATABASE": {
"MYSQL_USER": "",
"MYSQL_PASSWORD": "",
"MYSQL_HOST": "",
"MYSQL_PORT": 0000,
"MYSQL_DATABASE": ""
},
"KAKAO": {
"KAKAO_REST_API_KEY": "",
"KAKAO_CLIENT_SECRET": "",
"KAKAO_REDIRECT_URI": "",
"KAKAO_REDIRECT_FAILURE": ""
},
"AWS": {
},
"COOLSMS": {
"API_KEY": "",
"API_SECRET": "",
"SENDER": ""
},
"OPEN_AI": {
}
}
test.test.js
라는 파일은 .gitignore
에 의해,
git에 추적되지 않습니다. 여러가지 테스트가 필요할 땐 위와 같은 파일명을 사용해주세요.
혹시라도 test.js
라는 파일이 필요할 수도 있어 test.test.js
로 명명했습니다.
- GitHub Issue Template
- Git 전략 : Git Flow, GitHub Flow
- Code 작성 컨벤션 : 변수, 함수, 파일, 폴더 명명법
- docker 및 docker-compose 파일 : 빠르고 쉬운 배포
- CI/CD (GitHub Actions) : 개인서버 용 CI/CD (SSH 이용), AWS 용 CI/CD
- 다양한 기능에 대한 preset : 로그인, 채팅, 게시판
- .gitignore :
Windows
,macOS
,Linux
,node
,dotenv
기준 +config.json
,*.pem
대중적인 구조는 있지만, 세부적인 부분은 개발자 개인의 취향입니다.
개인적으로 프로젝트나 해커톤에서 활용하기 위해 작성하였기에,
제 개인의 취향에 맞게 적용되어 있고, 시간이 지남에 따라 맞다고 생각하는 것이 바뀌며
템플릿을 수정할 수 있습니다.
템플릿은 여러분의 취향에 맞게 수정하여 사용해주시고, 도움이 되셨다면 ⭐ 한번만 부탁드립니다.
preset 코드에 버그가 있을 경우 issue 생성하시어 남겨주시면 확인 후 반영하겠습니다.
감사합니다.
Repository URL -> Click
made and owned by @kyeoungwoon | Naver Blog
- 규모가 큰 프로젝트에 적합합니다.
- 브랜치 종류 : main, develop, feature, release, hotfix
- main :
- develop :
- feature :
- release :
- hotfix :
- 규모가 작고 잦은 기능수정과 배포가 있는 프로젝트에 적합합니다.
- 브랜치 종류 : main, develop, feature
- Production 환경에 언제 배포해도 문제 없는 stable branch 입니다.
- 장애 혹은 버그 발생 시 main branch를 기준으로 빠르게 수정합니다.
- Initial commit을 제외하고, main branch에 commit이 직접적으로 발생하면 안됩니다.
- 새로운 feature들을 개발할 경우 main을 기준으로 develop branch를 생성 합니다.
- feature branch들을 merge 하는 곳입니다.
- feature들을 모두 merge한 후 발생되는 bug fix를 모두 마친 후, main branch로 PR을 생성합니다.
- 다시 한번 강조하지만, main branch는 모든 작업의 시작점이며, 절대적으로 stable 해야 합니다.
- 불완전한 사항들은 develop branch 내에서 해결되어야 합니다.
- develop branch를 기준으로, 새로운 기능을 개발하는 branch 입니다.
- 새로운 기능에 대한 bug fix는 feature branch 내에서 마친 후 develop branch로 PR을 생성해야 합니다.
- GitHub Flow #1과 유사하지만,
develop
branch를 사용하지 않습니다. main
feature
두 브랜치 종류로 구성되며, 해커톤과 같이 빠르게 완성하는 프로젝트에 적합합니다.main
을 base로,feature
브랜치를 생성해서 지속적으로main
에 merge 하는 전략입니다.
feature/login
branch를 develop/user
branch에 merge 한다고 가정해 봅시다.
feature/login
branch를 push 합니다.- GitHub 레포지토리 페이지에서
Compare & Create Pull Request
버튼이 생성됩니다. - 해당 기능을 클릭한 후,
base
와compare
branch를 각각 아래와 같이 보이게 설정합니다. - base:
develop/user
<- compare:featture/login
과 같이 설정 후 사용하시면 됩니다.
1번과 동일한 상황을 가정합니다.
git switch develop/user
를 통해 이동하고git pull origin develop/user
를 통해 최신 변경사항을 적용합니다.git merge feature/login
을 통해 merge를 시도합니다.- 자동 병합에 성공하면 그대로
Merge: branch 'feature/login'
과 같이 commit을 남기고 push 해주시면 됩니다. - 자동 병합에 실패한 부분은 수동으로 conflict 해결해 주시고,
- 가능하면 해결한 부분에 주석으로 어떤 부분에 conflict가 발생하였고, 어떤 방식으로 해결하였는지 작성해주시면 좋습니다.
Git 전략들에 대해서 더 알아보고 싶다면 : 잘 정리해놓으신 분이 있습니다
제목
, 본문
, 꼬리말
세 부분으로 나뉩니다.
각 부분은 빈 줄로 구분되어야 합니다.
- Tag의 첫 문자는 대문자로 작성합니다.
- 콜론은 Tag에 붙여서 작성하고, 콜론 이후 1칸 뒤에 title을 작성합니다.
- (X)
feat:title
feat: title
feat :title
feat : title
Feat :title
Feat : title
- (O)
Feat: title
- (X)
Feat
: 새로운 기능이 추가되었을 때Fix
: 버그를 수정하였을 때Docs
:README.md
나 주석 등 문서를 수정하였을 때Style
: 코드 구조에 변경 없이 변수명 등을 수정하였을 때Refactor
: 코드 동작 방식을 수정하였을 때, 또는 Style을 대규모로 변경하였을 때에도 활용.Test
: 테스트 코드를 추가하였을 때Chore
:package.json
을 수정하였거나dockerfile
등 분류하기 애매한 상황에서 사용Merge
: branch를 merge하였을 때 사용합니다.
- 본문은 한 줄 당 72자 내로 작성해 주세요. (다양한 환경에서의 가독성을 위하여)
- 본문 내용은 양에 구애받지 않고 최대한 상세히 작성해 주세요.
- 본문 내용은 어떻게 변경했는지 보단, 무엇을 변경했는지 또는 왜 변경했는지를 설명해 주세요.
- Footer는 필수적이지 않습니다.
- 다만, issue에 연관되어 생성된 commit이라면 넣어주시는 것을 추천합니다.
제목
을 쓸 때와 형식은 동일합니다.- eg.
Fixes: something
- eg.
Fixes
: 이슈 수정중 (아직 해결되지 않은 경우)Resolves
: 이슈를 해결했을 때 사용Ref
: 참고할 이슈가 있을 때 사용Related to
: 해당 커밋에 관련된 이슈번호 (아직 해결되지 않은 경우)
- Camel Case를 사용합니다.
- eg.
getUserByUserId
,getEventByDateAndUserId
- eg.
- 길이가 길어지더라도 기능을 명확하게 명시해 주세요.
- 함수명은 겹쳐도 됩니다.
- 하단에 import/export 관련 컨벤션 설명에서 더 자세히 알 수 있지만, 아래의 간단한 예시를 참고해주세요.
user.service.js
에createNewUser
과user.repository.js
에createNewUser
이 동시에 존재하여도 됩니다.- service에서 사용 시에는
userRepository.createNewUser
- controller에서 사용할 떄는
userController.createNewUser
와 같이 사용합니다.
- routes 폴더에서
express.Router()
로 정의하는 변수명은 해당 파일명과 일치하여야 합니다.- eg.
auth.router.js
파일의 경우에는const authRouter = express.Router();
와 같이! authRouter.get("/", handleXX)
,userRouter.get("/", handleYY)
- eg.
- Snake Case를 사용합니다.
- eg.
user_id
,event_id
- eg.
- 팀원간 협의 후 Camel Case 또한 사용 가능합니다. 중요한 부분은 컨벤션의 통일입니다.
- 다만, Database의 컬럼명들이 Snake Case로 작성되었기 때문에 Query문 작성 시 유의가 필요합니다.
- JSON 객체를 생성할 떄는,
{ user_id : user_id }
(X){ user_id }
(O) 와 같이 작성해주세요.- JSON 객체 생성 시 변수명만 입력하면, 자바스크립트는 자동으로
{ 변수명: 변수값 }
형태로 객체를 생성하는 점을 이용합니다. (객체 축약 표기법)
- JSON 객체 생성 시 변수명만 입력하면, 자바스크립트는 자동으로
- 상수값에 해당하는 변수명은 전부 대문자 및 snake case로 작성되어야 합니다.
- 환경변수 등이 해당합니다. eg.
AWS_SECRET_KEY
,API_KEY
등
- 환경변수 등이 해당합니다. eg.
- 전부 소문자를 사용하셔야 합니다.
- 상위 폴더명을 포함하여 기능을 명시해야 합니다.
- 여러 단어를 사용해야 하는 경우 . 으로 단어를 구분합니다
- eg.
user.repository.js
,chat.service.js
- 폴더명은 길어질 경우
-
를 활용하여 구분합니다. (kebab case) - eg.
my-page
,user-info
class CustomError extends Error
와 같이, JavaScript 기본 Error 객체를 extend 하여 Custom Error를 작성하여야 합니다.- Error는 세분화하여 각각 에러를 할당하는 것이 아닌, 대분류로 관리하여 reason으로 세부사항을 알 수 있도록 하여야 합니다.
- Error 명은 Pascal Case로 작성하여야 합니다.
UserNotExistError
UserAuthorizationError
IdNotProvidedError
등과 같이 세분화 된 것이 아니라,NotExistError
InvalidInputError
처럼 최대한 포괄적으로 관리하여야 합니다.- 세분화할 필요가 생긴다면,
UserIdNotExist
UserNameNotExist
수준으로 세분화 하는 것이 아닌,InvalidUserDataInput
과 같이 카테고리까지만 포함하여야 합니다.
- 수정은 자유이나, 다음과 같은 사항을 포함하여야 합니다.
error code
string
이여야 합니다.U001
과 같이 따로 정의해두셔도 되고,ALREADY_EXIST
와 같이 한두 단어 정도로 간략하게 작성해주세요.U001
과 같이 코드값으로 관리하실 예정이면, 문서화 하여 팀원들 사이에 공유해 중복된 에러가 작성되지 않도록 유의해주세요.
status code
- http status code 값 입니다.
- 해당 에러가 발생했을 때 전송할 status code를 입력해주세요.
reason
- 해당 에러가 발생한 이유입니다.
- debug 시에 reason만 보고 알 수 있도록 간결하되, 모든 정보를 포함하도록 작성해 주세요.
class AlreadyExistError extends Error {
errorCode = "ALREADY_EXIST";
statusCode = 409;
constructor(reason, data) {
super(reason);
this.reason = reason;
this.data = data;
}
}
- 테이블명과 컬럼명 등 모든 변수명은 반드시 snake case를 사용하여야 합니다.
- PK값은
{table명}_id
와 같은 형식이여야 합니다.- eg.
user_oauth
테이블의 PK 컬럼 명은user_oauth_id
bigint
자료형을 사용하고, auto increment을 사용하여야 합니다.- 반정규화 (비정규화, denormailzation)로 인해 테이블이 분할된 경우에도, id값을 따로 작성하는 것을 추천합니다.
- eg.
- 모든 테이블에는
created_at
과updated_at
이 있어야 합니다.- DataType은 TIMESTAMP(6) 입니다.
created_at
과updated_at
은default expression
에current_timestamp(6)
을 적용해두어야 합니다.updated_at
은on_update
에current_timestamp(6)
이 적용되어 있어야 합니다.
- 정규화 규칙을 되도록이면 따르는 것을 추천합니다.
- 같은 내용의 Query를 여러번 날리는 것 보다는,
JOIN
이나BETWEEN
등으로 한번에 가져와서Node.js
단에서 처리하는 것을 권합니다. image
등 파일은url
만을 저장해야 합니다.binary data
를 직접적으로 저장하는 행위는 지양해야 할 1순위 입니다.
ES6
와 commonJS
모두 동일하게 적용됩니다.
default export
의 사용을 지양하고,named export
의 사용을 지향합니다.
// 지양 : 함수를 직접 import 하여 사용하는 것
import { createNewUser } from "./user.service";
const result = createNewUser();
// user.repository에도 createNewUser 존재할 가능성이 있기에, 충돌할 우려가 있습니다.
// 지향 : 네임스페이스를 사용하여 코드를 구조화 합니다.
// eg. Java, C++
import userService from "./sample.service.js";
const result = userService.createNewUser();
컨벤션을 모두 지킨, router - controller - service - repository 간의 호출구조 예시입니다.
// user.router.js
import userController from "./user.controller";
const userRouter = express.Router();
userRouter.get("/", userController.createNewUser);
// user.controller.js
import userService from "./user.service";
export const createNewUser = async (req, res, next) => {
const { id, password } = req.body;
// 이렇게도 되고
await userService.createNewUser({ id, password }); // RORO
// 이렇게 해도 됩니다.
const data = { id, password };
userService.createNewUser(data);
return res.status(200).success();
};
// user.service.js
import userRepository from "./user.repository";
export const createNewUser = async (data) => {
const result = await userRepository.createNewUser(data);
return { message: "OK", result };
};
// user.repository.js
export const createNewUser = async (data) => {
const result = await User.create(data);
return result;
};
- 각 엔드포인트의 응답을 핸들링합니다.
- service에 의존적입니다.
controllers
에서 활용할 기능 등을 위한 폴더입니다.- 입력값 validation이나 error handling 등을 담당합니다.
- Database 입출력을 담당합니다.
- 한 함수는 한 Query만을 담당해야 합니다.
try-catch
는 되도록 적용하지 않습니다.- request/response 형식 통일 부분을 참고하여, error 발생 시 next(err) 등으로 middleware가 에러를 캐치하는 로직으로 구현하여야 하며, DB 접속 오류가 아닌 이상
repositories
단에서 에러가 나지 않도록 충분한 입력값 검증 후에 호풀하여야 합니다.
- request/response 형식 통일 부분을 참고하여, error 발생 시 next(err) 등으로 middleware가 에러를 캐치하는 로직으로 구현하여야 하며, DB 접속 오류가 아닌 이상
sequelize
와 같은 ORM을 사용할 때에는 repositories에서 리턴값을 가공하지 않고,attributes
설정 등으로 query단에서 가공하여야 합니다.- eg.
const getUserIdByLoginId async (login_id) => { return await User.findOne({ where : { login_id : login_id }, attributes: ["user_id"] }) }
- ORM 사용 시 model 정의를 위한 폴더입니다.
- sequelize 기준으로, sequelize.define이 아닌 class User Extends Model 과 같은 class 형식으로 작성하여야 합니다.
class User extends Model {
static init(sequelize) {
5;
super.init(
{
user_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
// 기타 컬럼들 ...
created_at: {
type: DataTypes.DATE(6),
defaultValue: DataTypes.NOW,
allowNull: false,
},
updated_at: {
type: DataTypes.DATE(6),
defaultValue: DataTypes.NOW,
allowNull: false,
},
},
{
sequelize,
tableName: "user",
timestamps: false,
}
);
}
static associate(models) {
// 관계형 정의 필요
User.hasMany(models.Question, { foreignKey: "questioned_user_id" });
}
}
- 자주 쓰이는 기능들에 대한 코드 preset 입니다.
- 자유롭게 변형해서 쓰시면 됩니다.