[Flask] 플라스크로 회원가입 구현하기
필수 입력, 선택 입력, 프로필 설정의 3단계로 회원가입 시스템을 구현해 보았다.
1. 필수 입력
우선 필수 입력 파트에서는 이메일. 비밀번호. 비밀번호 확인. 휴대폰 번호를 입력받고 유효성 검사를 거친 후 다음 필드인 선택 입력 파트로 넘어간다.
1-1. Email 중복확인
이메일을 입력 받은 후 이메일이 데이터베이스에 저장된 이메일과 중복되는지 확인한다.
<HTML>
<div class="form-group">
<label for="user-email">이메일</label>
<div class="email-form">
<input type="email" id="user-email" name="user-email" placeholder="Email" required>
<button type="button" id="confirm-email">중복 확인</button>
</div>
<p id="email-result"></p>
</div>
<Javascript>
우선 이메일 형식이 올바른지 검증한 후, 올바르지 않으면 경고 메세지를 emailResult에 띄우고 함수는 종료한다.
이메일 형식이 올바르면, AXIOS 통신을 통해 Flask로 전송한다. Flask에서 온 응답인 available에 따라 적절한 메세지를 emailResult에 띄운다.
document.getElementById('confirm-email').addEventListener('click', function(e) {
e.preventDefault();
const userEmail = document.getElementById('user-email').value;
const emailResult = document.getElementById('email-result');
// 이메일 형식 검증을 위한 정규표현식
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
if (!emailRegex.test(userEmail)) {
emailResult.textContent = '올바른 이메일 형식이 아닙니다';
emailResult.style.color = 'red';
return;
}
axios.post('/confirm_email', {
userEmail: userEmail
})
.then(function(response) {
if (response.data.available) {
emailResult.textContent = "사용 가능한 이메일입니다.";
emailResult.style.color = 'green';
}
else {
emailResult.textContent = "이미 등록된 이메일입니다.";
emailResult.style.color = 'red';
}
})
.catch(function(error) {
console.error('에러 발생: ', error);
});
});
<Flask>
Flask에서는 전달 받은 이메일을 User 테이블과 비교한 후, 일치하는 것이 없으면 available을 False로, 만약 일치하는 이메일이 존재한다면 True로 설정하여 Javascript로 응답을 보낸다.
@api.route('/confirm_email', methods=['POST'])
def confirm_email():
data = request.json
user_email = data.get('userEmail')
user = User.query.filter_by(email=user_email).first()
if user:
return jsonify({
'available' : False
})
else:
return jsonify({
'available' : True
})
<결과 화면>


1-2. 비밀번호 확인
비밀번호와 비밀번호 확인이 동일한지 확인한다.
<HTML>
<div class="form-group">
<label for="user-pwd">비밀번호</label>
<input type="password" name="user-pwd" placeholder="Password" required>
</div>
<div class="form-group">
<label for="user-repwd">비밀번호 확인</label>
<input type="password" name="user-repwd" placeholder="Comfirm password" required>
<p id="pwd-result"></p>
</div>
<Javascipt>
입력 필드에 입력이 들어올 때마다 checkPasswordMatch를 실행하여 비밀번호 일치 여부를 확인한다.
- 비밀번호 o, 비밀번호 확인 x => 메세지를 띄우지 않음
- 비밀번호 o, 비밀번호 확인 o, 두 입력값 일치o => 비밀번호 일치 메세지를 띄움
- 비밀번호 o, 비밀번호 확인 o, 두 입력값 불일치 => 비밀번호 불일치 메세지를 띄움
document.addEventListener('DOMContentLoaded', function() {
const userPwd = document.querySelector('input[name="user-pwd"]');
const userRepwd = document.querySelector('input[name="user-repwd"]');
const pwdResult = document.getElementById('pwd-result');
function checkPasswordMatch() {
if (userRepwd.value === '') {
pwdResult.textContent = '';
}
else if (userPwd.value === userRepwd.value) {
pwdResult.textContent = "비밀번호가 일치합니다.";
pwdResult.style.color = "green";
}
else {
pwdResult.textContent = "비밀번호가 일치하지 않습니다.";
pwdResult.style.color = "red";
}
}
userPwd.addEventListener('input', checkPasswordMatch);
userRepwd.addEventListener('input', checkPasswordMatch);
});
<결과 화면>



1-3. 휴대폰 번호 유효성 검사
휴대폰 번호 형식이 올바른지 검사한다.
<HTML>
<div class="form-group">
<label for="user-phone">휴대폰 번호</label>
<input type="tel" name="user-phone" placeholder="Phone number" required>
<p id="phone-result"></p>
</div>
<Javascript>
비밀번호 유효성 검사와 동일하게 입력이 들어올 때마다 checkPhoneValidate를 실행한다. 미리 검증용 정규표현식을 만든다음 통과하지 않으면 경고 메세지를 띄운다.
document.addEventListener('DOMContentLoaded', function() {
const phone = document.querySelector('input[name="user-phone"]');
const phoneResult = document.getElementById('phone-result');
function checkPhoneValidate() {
const phoneRegex = /^(01[016789]{1})[0-9]{3,4}[0-9]{4}$/;
if (phone.value === '') {
phoneResult.textContent = '';
}
else if (!phoneRegex.test(phone.value)) {
phoneResult.textContent = "휴대폰 번호를 다시 확인해주세요.";
phoneResult.style.color = "red";
}
else {
phoneResult.textContent = '사용 가능한 휴대폰 번호입니다.';
phoneResult.style.color = "green";
}
}
phone.addEventListener('input', checkPhoneValidate);
})
<결과 화면>


1-4. 모든 입력이 완료되었는지 확인
필수 입력에 모두 입력되었는지 확인하고 다음 파트인 선택 입력으로 넘어간다.
<HTML>
button에 validateAndShowstep 함수를 달아 다음으로 넘어가기 전에 함수를 실행시켜 입력 필드를 확인한다.
<div class="button-div">
<button type="button" id="comfirm-allfilled" class="step-button" onclick="validateAndShowstep(2)">다음</button>
</div>
<p id="warning-message" style="color: red; display: none">모든 필드를 입력해주세요.</p>
<Javascript>
required인 input들을 모두 불러와 forEach로 하나하나 확인한다. 만약 하나라도 입력이 되어있지 않다면 allFilled가 false로 변경되어 경고 메세지가 나타날 것이고, 모두 입력되었다면 showStep을 통해 선택 입력창으로 넘어간다.
function validateAndShowstep(step) {
const inputs = document.querySelectorAll('#step1 input[required]');
let allFilled = true;
inputs.forEach(input => {
if (input.value.trim() === '') {
allFilled = false;
}
});
if (allFilled) {
document.getElementById('warning-message').style.display = 'none';
showStep(step);
}
else {
document.getElementById('warning-message').style.display = 'block';
}
}
<결과 화면>

2. 선택 입력
2번째 단계인 선택 입력에서는 유저의 생년월일, 관심. 직장. 학교, 위치, 웹사이트를 입력받는다. 굳이 입력안해도 되며 따로 유효성 검사를 거칠 필요 없다.
<HTML>
<div class="step" id="step2">
<div class="form-group">
<label for="user-birth">생년월일</label>
<input type="date" max="9999-12-31" name="user-birth" class="user-birth" placeholder="Birthday">
</div>
<div class="form-group">
<label for="user-interest">관심</label>
<input type="text" name="user-interest" placeholder="Interest">
</div>
<div class="form-group">
<label for="user-work">직장, 학교</label>
<input type="text" name="user-work" placeholder="Work">
</div>
<div class="form-group">
<label for="user-location">위치</label>
<input type="text" name="user-location" placeholder="Location">
</div>
<div class="form-group">
<label for="user-website">웹사이트</label>
<input type="text" name="user-website" placeholder="Website">
</div>
<div class="button-div">
<button type="button" class="step-button" onclick="showStep(1)">이전</button>
<button type="button" class="step-button" onclick="showStep(3)">다음</button>
</div>
</div>
<결과 화면>

3. 프로필 설정
프로필 설정에서는 유저의 프로필 이미지, 이름, 설명을 입력받는다. 프로필 이미지를 데이터 베이스에 올리는 방법은 이전 포스팅에서 설명하였다. 이제 submit 버튼을 누르면 폼이 Flask에 제출되어 회원가입이 완료된다.
<HTML>
<div class="step" id="step3">
<div class="form-group">
<div class="image-div">
<img src="{{ url_for('static', filename='images/woman.png') }}" class="profile-image" id="profile-image">
</div>
<input type="file" id="image-input" name="user-image" style="display: none;" accept="image/*">
</div>
<div class="form-group">
<label for="user-name">이름</label>
<input type="text" name="user-name" placeholder="Name" required>
</div>
<div class="form-group">
<label for="user-description">설명</label>
<input type="text" name="user-description" placeholder="Description">
</div>
<div class="button-div">
<button type="button" class="step-button" onclick="showStep(2)">이전</button>
<button type="submit" class="submit-button">가입하기</button>
</div>
</div>
<Javascript>
FormData를 Flask에 AXIOS 통신을 통해 제출한다. 정상적으로 회원가입이 완료되면 환영메세지를 띄운다.
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('.signup-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
axios.post('/register', formData)
.then(function(response) {
document.querySelector('.form-container').style.display = 'none';
document.querySelector('.welcome-message').style.display = 'block';
document.getElementById('welcome-name').textContent = response.data.name;
})
.catch(function(error) {
console.error('Error: ', error);
});
});
});
<Flask>
입력폼에 있는 모든 데이터들을 User 객체의 인자로 전달해준다. 비밀번호는 보안을 위해 generate_password_hash로 해쉬값을 사용해준다.
db.sesson.add를 통해 테이블에 추가해주고 커밋하면 회원가입이 완료된다. user name을 응답으로 전달하여 환영 메세지에 유저 이름이 띄워지도록 해준다.
@api.route('/register', methods=['POST'])
def register():
user_email = request.form['user-email']
user_pwd = request.form['user-pwd']
hashed_password = generate_password_hash(user_pwd) # Hash 생성
user_phone = request.form['user-phone']
user_birth = request.form['user-birth']
if (user_birth == ''):
user_birth = None
user_interest = request.form['user-interest']
user_work = request.form['user-work']
user_location = request.form['user-location']
user_website = request.form['user-website']
user_image = request.files['user-image']
if user_image.filename == '':
db_file_path = ''
else:
file_name = secure_filename(user_image.filename)
file_path = os.path.join('app/static/images/profile', file_name)
user_image.save(file_path)
# db에 저장할 경로는 static 이후부터
db_file_path = f'images/profile/{file_name}'
user_name = request.form['user-name']
user_description = request.form['user-description']
new_user = User(username=user_name,
email=user_email,
password=hashed_password,
phone=user_phone,
birth=user_birth,
interest=user_interest,
work=user_work,
location=user_location,
website=user_website,
description=user_description,
image_path=db_file_path)
db.session.add(new_user)
db.session.commit()
return jsonify ({
'name': user_name
})
<결과 화면>

4. 가입 환영 메세지
가입 환영 메세지에는 유저의 이름을 함께 보여주고, 로그인 페이지로 안내한다.
<HTML>
<div class="welcome-message" style="display: none;">
<img src="{{ url_for('static', filename='images/logo_v2.png') }}" class="logo-picture">
<h1>안녕하세요, <span id="welcome-name"></span>님!</h1>
<p>회원가입이 성공적으로 완료되었습니다.</p>
<button id="goto-login">로그인 페이지로 이동</button>
</div>
<Javascript>
document.getElementById('goto-login').addEventListener('click', function() {
window.location.href = '/';
});
<결과 화면>
