회원관리(MariaDB) - Part1 회원가입

Project Diary/React + MariaDB (PicShare WebApp)

사용자 정보를 입력받아 데이터베이스에 저장하고, 중복된 이메일과 닉네임을 체크하며, 프로필 사진을 업로드하는 기능 기록


 

프론트엔드


- JoinSection.tsx

1. 입력 필드 변경 핸들러

// 입력 필드 변경 핸들러
const handleChange = async (e) => {
  const { value, name } = e.target;

  // 입력된 값을 상태에 저장
  setUserInfo((userInfo) => ({ ...userInfo, [name]: value }));

  // 이메일 중복 체크
  if (name === "email" && value) {
    try {
      await axios.post(`${serverUrl}/auth/check-email`, { email: value });
      setSuccess((success) => ({ ...success, email: "사용 가능한 이메일입니다." }));
    } catch (err) {
      if (err.response && err.response.data) {
        setError((error) => ({ ...error, email: err.response.data.message }));
      }
    }
  }

  // 닉네임 중복 체크
  if (name === "userNickname" && value) {
    try {
      await axios.post(`${serverUrl}/auth/check-nickname`, { userNickname: value });
      setSuccess((success) => ({ ...success, userNickname: "사용 가능한 닉네임입니다." }));
    } catch (err) {
      if (err.response && err.response.data) {
        setError((error) => ({ ...error, userNickname: err.response.data.message }));
      }
    }
  }
};
  • 입력된 값을 상태에 저장
    - setUserInfo를 사용하여 입력된 값을 userInfo 상태에 저장합니다.
  • 이메일 중복 체크
    - 이메일 입력 필드가 변경될 때마다 서버에 중복 체크 요청을 보냅니다.
    - 중복되지 않은 경우 성공 메시지를, 중복된 경우 오류 메시지를 설정합니다.
  • 닉네임 중복 체크
    - 닉네임 입력 필드가 변경될 때마다 서버에 중복 체크 요청을 보냅니다.
    - 중복되지 않은 경우 성공 메시지를, 중복된 경우 오류 메시지를 설정합니다.

이메일, 닉네임 중복체크

 

 

2. 회원가입 요청 핸들러

// 회원가입 요청 핸들러
const register = async (e) => {
  e.preventDefault();
  const formData = new FormData();

  // 폼 데이터에 사용자 정보 추가
  formData.append("email", userInfo.email);
  formData.append("userName", userInfo.userName);
  formData.append("userNickname", userInfo.userNickname);
  formData.append("password", userInfo.password);
  formData.append("photo", useDefaultProfile || !userInfo.photo ? "defaultProfile.jpg" : userInfo.photo);

  try {
    const res = await axios.post(`${serverUrl}/auth/join`, formData, {
      headers: { "Content-Type": "multipart/form-data" },
    });
    if (res.data.affectedRows === 1) {
      alert("회원가입이 성공했습니다.");
      navigate("/login");
    } else {
      alert("회원가입에 실패했습니다.");
    }
  } catch (err) {
    if (err.response && err.response.data) {
      const { field, message } = err.response.data;
      setError((error) => ({ ...error, [field]: message }));
    } else {
      console.error(err);
    }
  }
};
  • 폼 데이터 생성
    - FormData 객체를 사용하여 폼 데이터를 생성합니다.
    - 사용자 정보를 formData에 추가합니다.
  • 회원가입 요청 전송
    - axios.post를 사용하여 서버에 회원가입 요청을 보냅니다.
    - 성공 시 알림을 표시하고 로그인 페이지로 이동합니다.
    - 실패 시 오류 메시지를 설정합니다.

 

3. 프로필 사진 설정

3-1 상태 및 기본프로필 설정

const [useDefaultProfile, setUseDefaultProfile] = useState(true); // 기본 프로필 사용 여부
const [profilePreview, setProfilePreview] = useState(
  `${serverUrl}/uploads/defaultProfile.jpg`
); // 프로필 사진 미리보기
<체크박스 변경 핸들러>
// 체크박스 변경 핸들러
const handleCheckboxChange = () => {
  setUseDefaultProfile((prevUseDefaultProfile) => {
    const newUseDefaultProfile = !prevUseDefaultProfile;
    setProfilePreview(
      newUseDefaultProfile
        ? `${serverUrl}/uploads/defaultProfile.jpg`
        : profilePreview
    );
    return newUseDefaultProfile;
  });
};
  • 기본 프로필 사용 여부
    -useState를 사용하여 기본 프로필 사용 여부와 프로필 사진 미리보기를 설정합니다.
  • 체크박스 변경 시
    - handleCheckboxChange를 사용하여 체크박스 변경 시 기본 프로필 사용 여부를 토글합니다.
    - 기본 프로필 사용 시 기본 프로필 사진을, 사용하지 않을 시 업로드한 사진을 미리보기로 설정합니다.


true < useDefaultProfle > false

 

3-2 파일 입력 변경 핸들러

// 파일 입력 변경 핸들러
const handleFileChange = (e) => {
  const file = e.target.files[0];
  setUserInfo((prevUserInfo) => ({ ...prevUserInfo, photo: file }));
  setProfilePreview(URL.createObjectURL(file));
};

파일 입력 시:
handleFileChange를 사용하여 파일 입력 시 선택한 파일을 userInfo 상태에 저장하고, 프로필 사진 미리보기를 업데이트합니다.

<프로필 사진 설정 UI>
{/* 프로필 이미지 */}
<tr>
  <td>
    <img src={profilePreview} alt="프로필사진" />
  </td>
</tr>
{/* 프로필 이미지 설정 */}
<tr>
  <td>
    <div className="checkbox-label">
      <p>나만의 프로필사진을 추가할까요?</p>
      <label>
        <input
          type="checkbox"
          checked={!useDefaultProfile}
          onChange={handleCheckboxChange}
        />
        {useDefaultProfile ? (
          <span style={{ color: "#09fc52", fontWeight: "bold" }}>사진 추가하기</span>
        ) : (
          <span style={{ color: "#09fc52", fontWeight: "bold" }}>기본 이미지로 변경</span>
        )}
      </label>
    </div>
    {!useDefaultProfile && (
      <input
        type="file"
        name="photo"
        id="photo"
        onChange={handleFileChange}
        placeholder="Profile Image"
      />
    )}
  </td>
</tr>

 

 


 

백앤드

- authRouter.js

 

1. 파일 업로드

Multer란?

  • Multer는 Node.js의 미들웨어로, 파일 업로드를 처리하는 데 사용됩니다. 주로 사용자가 업로드한 파일을 서버에 저장할 때 사용됩니다.

왜 사용하는가?

  • 파일 업로드는 단순한 텍스트 데이터 전송과는 다르기 때문입니다. 파일은 바이너리 데이터로 다뤄야 하며, 이를 처리하기 위해 특별한 미들웨어가 필요합니다. Multer는 이렇나 파일 업로드는 처리할 수 있도록 도와주는 미들웨어입니다.

어떻게 사용하는가?

  • Multer를 사용하려면, 먼저 이를 설치하고 설정해야 합니다. 설정을 통해 업로드된 파일이 저장될 위치와 파일명을 정의합니다.
import multer from "multer";

// Multer 설정
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "uploads/"); // 파일이 저장될 폴더 경로
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + "-" + file.originalname); // 파일명 설정
  },
});
const upload = multer({ storage: storage });

 

파일 업로드 처리

// JOIN 기능
authRouter.post("/join", upload.single("photo"), (req, res) => {
  const { email, userName, userNickname, password } = req.body;
  let photo = req.file ? req.file.filename : "defaultProfile.jpg"; // 기본 프로필 사진 설정

  // 회원 정보를 데이터베이스에 삽입
  db.query(
    `INSERT INTO users (email, userName, userNickname, password, profilePicture) VALUES (?, ?, ?, ?, ?)`,
    [email, userName, userNickname, password, photo],
    (err, result) => {
      if (err) {
        if (err.code === "ER_DUP_ENTRY") {
          return res.status(400).json({ field: "email", message: "이미 존재하는 이메일입니다." });
        } else {
          return res.status(500).json({ message: "서버 오류가 발생했습니다. 다시 시도해주세요." });
        }
      } else {
        res.status(200).json({ affectedRows: result.affectedRows });
      }
    }
  );
});

upload.single("photo") 는 Multer가 파일 업로드를 처리하도록 하는 미들웨어입니다.
이 미들웨어는 요청 객체(req)에 file 속성을 추가하여 업로드된 파일에 접근할 수 있게 합니다

  • req.file.filename은 저장된 파일명을 나타내며, 이를 통해 데이터베이스에 파일 정보를 저장합니다.
  • 파일이 업로드되지 않은 경우, 기본 프로필 사진("defaultProfile.jpg")을 사용하도록 설정합니다.

 

2. API 엔드포인트 쿼리문 설명

 

2-1 이메일 중복 체크

// 이메일 중복 체크
authRouter.post("/check-email", (req, res) => {
  const { email } = req.body;

  // 이메일을 데이터베이스에서 조회
  db.query(`SELECT email FROM users WHERE email = ?`, [email], (err, results) => {
    if (err) {
      // 서버 오류가 발생했을 경우
      return res.status(500).json({ message: "서버 오류가 발생했습니다. 다시 시도해주세요." });
    }
    if (results.length > 0) {
      // 이미 존재하는 이메일일 경우
      return res.status(400).json({ field: "email", message: "이미 존재하는 이메일입니다." });
    }
    // 사용 가능한 이메일일 경우
    res.status(200).json({ message: "사용 가능한 이메일입니다." });
  });
});
  • SELECT email FROM users WHERE email = ?
    users 테이블에서 입력된 이메일이 존재하는지 확인하는 쿼리입니다.

2-2 닉네임 중복 체크

// 닉네임 중복 체크
authRouter.post("/check-nickname", (req, res) => {
  const { userNickname } = req.body;

  // 닉네임을 데이터베이스에서 조회
  db.query(`SELECT userNickname FROM users WHERE userNickname = ?`, [userNickname], (err, results) => {
    if (err) {
      // 서버 오류가 발생했을 경우
      return res.status(500).json({ message: "서버 오류가 발생했습니다. 다시 시도해주세요." });
    }
    if (results.length > 0) {
      // 이미 존재하는 닉네임일 경우
      return res.status(400).json({ field: "userNickname", message: "이미 존재하는 닉네임입니다." });
    }
    // 사용 가능한 닉네임일 경우
    res.status(200).json({ message: "사용 가능한 닉네임입니다." });
  });
});
  • SELECT userNickname FROM users WHERE userNickname = ?
    users 테이블에서 입력된 닉네임이 존재하는지 확인하는 쿼리입니다.