개발 목적
- 만들고 있는 프로젝트에 회원가입 로직을 추가하기 위함.
- 인증이나 유효성 검증 등에 다소 민감한 회원가입 이라는 로직을 백과 프론트에서 직접 구현해보면서 구현 역량을 늘리고, 에러를 경험해보기 위함.
회원가입 로직 개발 (백엔드)
회원가입한 유저의 데이터를 보관하기 위해 User 모델을 다시 설계했습니다.
User 모델 구성
User 모델(users 테이블)
- id : 테이블 내 유일성 보장. 기본키, 암호화
- loginId : 유저가 로그인할 때 사용하는 id. 문자열. not null, unique
- password : 로그인 시 사용하는 password. 암호화, not null (개인정보 보호법에 의해 반드시 암호화해야함)
- email : 가입할때 사용하는 이메일. 문자열, email 유효성 검증 추가, not null, unique
- nickname : 커뮤니티에서 사용할 별명. 문자열. not null, unique
- created_at : Date 형식으로, 유저를 생성한 날짜를 기록함.
import Sequelize, { Sequelize as SequelizeType } from "sequelize";
import { SequelizeDBConfig } from "../types/config";
// @ts-ignore
export default class User extends Sequelize.Model {
static init(sequelize: SequelizeType) {
return super.init(
{
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true,
},
loginId: {
type: Sequelize.STRING(20),
allowNull: false,
unique: true,
},
password: {
type: Sequelize.STRING(255),
allowNull: false,
},
email: {
type: Sequelize.STRING(100),
allowNull: false,
validate: {
isEmail: true,
},
unique: true,
},
nickname: {
type: Sequelize.STRING(20),
allowNull: false,
unique: true,
},
created_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW,
},
},
{
sequelize,
timestamps: false,
underscored: false,
modelName: "User",
tableName: "users",
paranoid: false,
charset: "utf8",
collate: "utf8_general_ci",
}
);
}
static associate(db: SequelizeDBConfig) {
db.User.hasMany(db.Comment, { foreignKey: "commenter", sourceKey: "id" });
}
}
Controller 구성
(1) DB에 동일한 ID가 있는지 체크하는 로직(+ nickname 체크 로직)
export const checkNickNameDuplicate = (req: Request, res: Response) => {
const { nickname } = req.body;
// @ts-ignore
User.findOne({
attributes: ["nickname"],
where: {
nickname,
},
})
.then((response) => {
if (response === null) {
res.status(200).json({
statusCode: 200,
message: "사용 가능한 닉네임입니다.",
});
} else {
res.status(409).json({
statusCode: 409,
message: "중복된 닉네임이 있습니다.",
});
}
})
.catch((error) => {
res.status(500).json({
...error,
statusCode: 500,
message: "잘못된 DB 접근입니다.",
});
});
};
사용자가 사용하려는 loginId와 nickname이 데이터베이스에 이미 기록이 되어있는지를 확인하는 로직입니다.
연습 차원에서 만든 것이라 세부 구현은 다소 모자랄 수 있습니다.
그래도 뼈대는 여기서 크게 벗어나지 않을 것이므로, 이 로직을 기준으로 기능을 더 추가해보려 합니다.
(2) 이메일 인증 로직
export const sendEmail = (req: Request, res: Response) => {
const { userEmail, authCode } = req.body;
const smtpTransport = nodemailer.createTransport(senderConfig);
const mail = setSendEmail(userEmail, authCode);
smtpTransport.sendMail(mail, (error, response) => {
if (error) {
console.error(error);
res.status(421).json({
statusCode: 421,
message: "이메일이 전송되지 않았습니다.",
});
} else {
console.log(response);
res.status(200).json({
statusCode: 200,
message: "이메일이 전송되었습니다.",
});
}
smtpTransport.close();
});
};
- nodemailer라는 라이브러리를 사용하여 보내고자 하는 메일을 특정 사람에게 발송시키는 로직입니다.
- 인증번호를 프론트엔드에서 받아온 뒤, 가입하려는 사람의 이메일로 전달합니다.
- 가입하시려는 분이 이메일을 통해 인증번호를 가져와 프론트에 입력하면 인증이 완료됩니다.
- 즉, 인증번호를 프론트에서 보내고 프론트에서 검증합니다.
- 이 로직은 인증번호를 보내는 역할만 하므로, 기능 구현 단위에서는 프론트와의 결합도가 상당히 큽니다.
(3) 회원가입 로직
export const createUser = async (req: Request, res: Response) => {
const { loginId, password, email, nickname } = req.body;
const hashedPassword = 비밀번호암호화로직;
// @ts-ignore
User.create({
loginId: loginId,
password: hashedPassword,
email: email,
nickname: nickname,
})
.then((data) => {
console.log("성공했어요!");
res.status(201).json({
statusCode: 201,
message: "User 생성 성공!",
});
})
.catch((error) => {
console.log(error);
res.status(500).json({
...error,
statusCode: 500,
message: "잘못된 DB 접근입니다.",
});
});
};
- 회원가입 로직입니다. 회원가입에 필요한 모든 정보를 받아 users 테이블에 기록합니다.
- 기본적인 유효성 검증은 프론트에서 도맡아 할 것이고, 프론트단 유효성 검증이 끝나야만 이 로직으로 post를 보낼 수 있도록 설계할 것입니다.
- 설령, 오류 등의 문제로 인해 프론트 검증이 되지 않더라도, 테이블 구조상 중복된 데이터가 들어갈 수 없으므로 안전하다고 볼 수 있습니다.
Router 구성
export const authRouter = express.Router();
authRouter.post("/sendauthcode", sendEmail);
authRouter.post("/iddupcheck", checkIdDuplicate);
authRouter.post("/nicknamedupcheck", checkNickNameDuplicate);
authRouter.post("/signin", createUser);
- 그냥... 라우터입니다!
프론트에 로직 이식 후 동작 확인
- 의도한대로 잘 돌아갑니다.
- 아이디, 닉네임, 비밀번호, 이메일에 대한 프론트단의 유효성 검증이 꼭 들어가야할 것 같습니다.
- 중복체크와 이메일 검증등이 모두 완료되면 회원가입 버튼을 클릭할 수 있도록 설계해야할 것 같습니다.
- trim() 등을 사용해서 빈 문자열이 서버로 전달되지 않게끔 해야겠습니다.
개선할 점
- 변수명이 예쁘지 않습니다. 개발하면서 좋은 변수명이란 어떤 것인가.. 에 대한 생각을 계속 해봐야 겠습니다.
- 모델(User)의 타입을 맞추기가 어려워 @ts-ignore를 때려박아 버렸습니다. 타입 에러를 해결하기 위해 직접 타입을 뜯어보았으나 해결이 어려워 stackoverflow에 검색을 해보았습니다. 관련된 질문의 답변 대부분은 타입을 강제로 맞춰라 였습니다. 일단 기능 구현에는 문제가 없으므로 타입을 무시하고 개발을 진행하기로 했습니다.
부주의로 인한 타입에러가 아닌 것들은 중요한 기능 구현 이후에 고쳐도 좋지 않을까 생각했습니다.
'개발 일지' 카테고리의 다른 글
nextjs에서 react-quill 사용하기 (0) | 2023.11.06 |
---|---|
(nextjs) shadcn/ui를 활용한 signin 컴포넌트 개발 (1) | 2023.10.20 |
Context와 Redux의 리렌더링에 관한 고찰 (0) | 2023.08.04 |
[클린코드] Header 코드 정리 (0) | 2023.07.14 |
실무에서 바로 쓰는 프론트엔드 클린코드 감상 (0) | 2023.07.14 |