제목, 본문, 꼬리말 세 부분으로 나뉩니다.
각 부분은 빈 줄로 구분되어야 합니다. git commit 입력 후 작성하면 됩니다.
- Tag의 첫 문자는 대문자로 작성합니다.
- 콜론은 Tag에 붙여서 작성하고, 콜론 이후 1칸 뒤에 title을 작성합니다.
- (X)
feat:titlefeat: titlefeat :titlefeat : titleFeat :titleFeat : 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로 작성하여야 합니다.
UserNotExistErrorUserAuthorizationErrorIdNotProvidedError등과 같이 세분화 된 것이 아니라,NotExistErrorInvalidInputError처럼 최대한 포괄적으로 관리하여야 합니다.- 세분화할 필요가 생긴다면,
UserIdNotExistUserNameNotExist수준으로 세분화 하는 것이 아닌,InvalidUserDataInput과 같이 카테고리까지만 포함하여야 합니다.
- 수정은 자유이나, 다음과 같은 사항을 포함하여야 합니다.
error codestring이여야 합니다.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 입니다.
- 자유롭게 변형해서 쓰시면 됩니다.