이전 내용
[파이썬] FastAPI - 메모 앱 프로젝트 5: 웹페이지 개선
이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 4: 사용자별 메모 관리(feat. Almebic)이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 3: 사용자 인증이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 2: CRUD 구
puppy-foot-it.tistory.com
프론트엔드 페이지 개선
기존의 home.html 코드는 로그인 및 회원가입 성공 또는 실패 시 팝업창만 띄우므로 몇 가지 개선이 필요하다.
- 로그인 성공 시: 자동으로 /memos 라우트로 넘어감
- 로그인 또는 회원가입 실패 시: 팝업으로 에러 메시지 띄움
- 화면 개선
[home.html 전체 코드]
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>마이 메모 앱</title>
<style>
body {
font-family: 'Noto Sans KR', sans-serif;
background-color: #f8f9fa;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
}
.container {
max-width: 400px;
padding: 2rem;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
margin: 1rem;
width: 100%;
}
h1 {
font-size: 1.5rem;
color: #007bff;
margin-bottom: 2rem;
}
p {
margin-bottom: 2rem;
color: #666;
}
.form-group {
margin-bottom: 1rem;
width: 100%;
}
.form-group label {
margin-bottom: .5rem;
color: #888;
text-align: left;
display: block;
}
.form-group input {
padding: 0.75rem;
border: 1px solid #ced4da;
border-radius: 5px;
width: 100%;
box-sizing: border-box;
}
.form-group input {
padding: 0.75rem;
border: 1px solid #ced4da;
border-radius: 5px;
width: 100%;
box-sizing: border-box;
}
.form-group input:focus {
border-color: #80bdff;
box-shadow: 0 0 0 2px rgba(0,123,255,.25);
}
.buttons button {
width: 100%
padding: 0.75rem;
border: none;
border-radius: 5px;
background-color: #007bff;
color: white;
margin-top: 0.5rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
box-sizing: border-box;
}
.buttons button:hover {
background-color: #0056b3;
}
@media (max-width: 768px) {
.container {
width: 90%;
padding: 1.5rem;
}
h1 {
font-size: 1.25rem;
}
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet">
<script>
function submitLoginForm(event) {
event.preventDefault();
const formData = new FormData(event.target);
const data = {
username: formData.get('username'),
password: formData.get('password')
};
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json().then(body => ({
status: response.status, body: body})))
.then(result => {
if (result.status == 200) {
alert(result.body.message); // 로그인 성공 메시지를 팝업으로 표시
window.location.href = '/memos'; // 메모 페이지로 리다이렉트
} else {
throw new Error(result.body.detail || '로그인이 실패하였습니다.'); // 로그인 실패 시 에러 발생 (서버 제공 에러 또는 기본 메시지)
}
})
.catch((error) => {
console.error('Error:', error);
alert(error.message); // 에러 메시지를 팝업으로 표시
});
}
function submitSignupForm(event) {
event.preventDefault();
const formData = new FormData(event.target);
const data = {
username: formData.get('username'),
email: formData.get('email'),
password: formData.get('password')
};
fetch('/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json().then(body => ({
status: response.status, body: body})))
.then(result => {
if (result.status == 200) {
alert(result.body.message); // 회원가입 성공 메시지를 팝업으로 표시
window.location.href = '/'; // 메인 페이지로 리다이렉트
} else {
throw new Error(result.body.detail || '회원가입이 실패하였습니다.'); // 회원가입 실패 시 에러 발생 (서버 제공 에러 또는 기본 메시지)
}
})
.catch((error) => {
console.error('Error:', error);
alert(error.message); // 에러 메시지를 팝업으로 표시
});
}
</script>
</head>
<body>
<div class="container">
<h1>마이 메모 앱에 오신 것을 환영합니다!</h1>
<p>간단한 메모를 작성하고 관리할 수 있는 앱입니다.</p>
<form id="loginForm" onsubmit="submitLoginForm(event)">
<div class="form-group">
<label for="username">사용자 이름:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">비밀번호:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="buttons">
<button type="submit">로그인</button>
</div>
</form>
<form id="signupForm" onsubmit="submitSignupForm(event)">
<div class="form-group">
<label for="signup_username">사용자 이름:</label>
<input type="text" id="signup_username" name="username" required>
</div>
<div class="form-group">
<label for="signup_email">이메일:</label>
<input type="email" id="signup_email" name="email" required>
</div>
<div class="form-group">
<label for="signup_password">비밀번호:</label>
<input type="password" id="signup_password" name="password" required>
</div>
<div class="buttons">
<button type="submit">회원가입</button>
</div>
</form>
</div>
</body>
</html>
[전체 구조 요약]
구분 | 설명 |
HTML | 로그인 및 회원가입 입력 폼 구조 |
CSS | 디자인(폰트, 배경색, 입력창 스타일 등) |
JavaScript | 로그인/회원가입 버튼을 눌렀을 때 서버로 데이터를 보내는 기능 |
home.html 분석
1. HTML (화면에 보여지는 부분)
HTML: 웹 페이지에 어떤 요소를 보여줄지를 결정하는 "뼈대" 역할.
각 <form>은 onsubmit 속성을 통해 JavaScript 함수 (submitLoginForm, submitSignupForm)와 연결.
태그 | 역할 |
<h1> | 큰 제목. 앱 이름을 보여줌 |
<p> | 짧은 소개 문장 |
<form> | 입력 폼. 사용자 정보를 서버로 보낼 준비 |
<input> | 사용자 이름, 비밀번호 등을 입력하는 칸 |
<label> | 입력창에 대한 설명 (ex. "사용자 이름") |
<button> | 제출 버튼. 클릭하면 서버로 전송 |
onsubmit="함수명(event)" | 자바스크립트 함수 호출 (기본 제출 방지 포함) |
<head>
<meta charset="UTF-8">
<title>마이 메모 앱</title>
- 페이지의 문자 인코딩은 UTF-8 (한글 지원)
- 브라우저 탭 제목은 "마이 메모 앱"
<link> 폰트 추가
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet">
- 웹 폰트인 'Noto Sans KR'을 불러오는 코드
<body>
<div class="container">
<h1>...</h1>
<p>...</p>
<!-- 로그인 폼 -->
<form id="loginForm" onsubmit="submitLoginForm(event)">...</form>
<!-- 회원가입 폼 -->
<form id="signupForm" onsubmit="submitSignupForm(event)">...</form>
</div>
- 하나의 박스(.container) 안에 로그인과 회원가입 폼이 함께 들어 있음.
- 폼을 제출할 때 각각의 자바스크립트 함수가 실행됨
로그인 폼
<form id="loginForm" onsubmit="submitLoginForm(event)">
<div class="form-group">
<label for="username">사용자 이름:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">비밀번호:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="buttons">
<button type="submit">로그인</button>
</div>
</form>
- required 속성은 필수 입력 사항으로 입력 하지 않고 제출하면 브라우저가 경고창 띄움.
home.html 분석
2. CSS (화면 스타일 설정)
CSS는 웹페이지에 어떻게 보일지(모양, 색, 배치)를 정하는 부분
선택자 | 설명 |
body | 화면 전체를 중앙 정렬, 배경색, 글꼴 지정 |
.container | 폼이 담긴 박스. 최대 400px, 배경 흰색, 그림자 |
.form-group | 각 입력창을 감싸는 박스 |
.form-group input | 텍스트 입력창 스타일 |
.buttons button | 파란색 버튼 스타일. 마우스 올리면 색 진해짐 |
<주요 스타일>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
속성 | 설명 |
display: flex | 중앙 정렬을 위해 Flexbox 사용 |
justify-content: center | 가로축 중앙 정렬 |
align-items: center | 세로축 중앙 정렬 |
height: 100vh | 브라우저 화면 전체 높이를 사용 (vh = viewport height) |
<.container 박스 스타일>
.container {
max-width: 400px;
width: 100%;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
- max-width: 400px: 너무 커지지 않도록 최대 너비 제한
- width: 100%: 모바일에서 가로폭 채우기
- border-radius: 모서리를 둥글게
- box-shadow: 그림자 효과로 박스가 살짝 떠보이게
<입력창 스타일>
.form-group input {
padding: 0.75rem;
border: 1px solid #ced4da;
border-radius: 5px;
width: 100%;
}
속성 | 설명 |
padding | 입력창 안쪽 여백 |
border | 테두리 |
border-radius | 입력창도 살짝 둥글게 |
width: 100% | 폼 너비만큼 꽉 채움 |
.form-group input:focus {
border-color: #80bdff;
box-shadow: 0 0 0 2px rgba(0,123,255,.25);
}
- 입력창에 포커스(클릭)했을 때는 파란색 테두리로 바뀜.
<버튼 스타일>
.buttons button {
width: 100%;
padding: 0.75rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
- 파란색 배경의 버튼
- cursor: pointer: 마우스 올리면 손가락 모양
자바스크립트 기초
JavaScript: 사용자의 행동(버튼 클릭 등)에 따라 데이터 전송, 알림, 화면 이동 등을 처리하는 기능
[자바스크립트 기본 문법]
키워드 | 설명 |
let | 바뀔 수 있는 값 저장 |
const | 바뀌지 않는 값 저장 (상수) |
= | 값을 변수에 저장하는 기호 |
[함수 만들기]
function sayHello() {
alert("안녕하세요!");
}
- function: “이름이 있는 동작”을 만들 때 사용
- sayHello(): 나중에 실행시키는 이름
- { ... } 안에 실행할 코드를 넣음
- alert(): 경고창 띄우는 함수
[이벤트 처리]
<button onclick="sayHello()">인사하기</button>
- 이 버튼을 누르면 sayHello()라는 함수가 실행된다.
[조건문]
if (age > 18) {
alert("성인입니다.");
} else {
alert("미성년자입니다.");
}
- if: 조건이 참일 때 실행
- else: 그렇지 않을 때 실행
[fetch() – 서버와 통신]
fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
- fetch()는 서버와 데이터를 주고받을 때 사용
- POST: 데이터를 보낼 때 사용하는 방식
- JSON.stringify(data): 데이터를 JSON 형식 문자열로 바꿈
home.html 분석
3. JavaScript (동작 기능 처리)
<로그인 처리 함수>
function submitLoginForm(event) {
event.preventDefault(); // 기본 동작(페이지 새로고침) 막기
const formData = new FormData(event.target);
const data = {
username: formData.get('username'),
password: formData.get('password')
};
fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json().then(body => ({ status: response.status, body: body })))
.then(result => {
if (result.status == 200) {
alert(result.body.message); // 로그인 성공 시 팝업
window.location.href = '/memos'; // 메모 페이지로 이동
} else {
throw new Error(result.body.detail || '로그인이 실패하였습니다.');
}
})
.catch((error) => {
alert(error.message); // 오류 메시지 표시
});
}
[코드 설명]
function submitLoginForm(event) {
event.preventDefault();
- function submitLoginForm() → 함수 만들기
- event.preventDefault() → 폼이 기본 동작(새로고침) 못하게 막음
const formData = new FormData(event.target);
- FormData() → 입력한 값들을 쉽게 모아주는 도구
- event.target → 사용자가 입력한 <form> 요소 전체
const data = {
username: formData.get('username'),
password: formData.get('password')
};
- 객체({})는 여러 개의 값을 하나로 묶는 구조
- formData.get('username'): 입력한 사용자 이름을 꺼냄
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
- 서버 주소 /login으로 data를 보냄
- POST 방식으로 보내고
- 데이터를 JSON 문자열로 바꿈
.then(response => response.json().then(body => ({ status: response.status, body: body })))
- .then() → 서버 응답을 받은 후 실행할 일
- response.status → 응답 코드 (예: 200이면 성공)
- response.json() → 응답 데이터를 JSON으로 해석
.then(result => {
if (result.status == 200) {
alert(result.body.message);
window.location.href = '/memos';
} else {
throw new Error(result.body.detail || '로그인이 실패하였습니다.');
}
})
- 결과 상태가 200이면 성공 메시지 + 페이지 이동
- 실패하면 에러 메시지를 띄움
.catch((error) => {
alert(error.message);
});
}
- .catch()는 오류가 발생했을 때 실행됨
- alert()로 오류 메시지를 사용자에게 보여줌
<회원가입 처리 함수>
const data = {
username: formData.get('username'),
email: formData.get('email'),
password: formData.get('password')
};
회원가입 함수도 단지 email 입력 항목이 하나 추가됐을 뿐 로그인 함수와 비슷하다.
변경된 코드 실행
main.py를 실행하면 디자인적으로 개선된 홈페이지가 뜨며, 로그인을 하면 /memos 로 잘 이동한다.

[참고]
가장 빠른 풀스택을 위한 플라스크 & FastAPI
다음 내용
[파이썬] FastAPI - 메모 앱 프로젝트 7: 보완(회원가입 등)
이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 6: 프론트엔드 페이지 개선이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 5: 웹페이지 개선이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 4: 사용자별 메
puppy-foot-it.tistory.com
'[파이썬 Projects] > <파이썬 웹개발>' 카테고리의 다른 글
[파이썬] FastAPI - 메모 앱 프로젝트 8: MVC 패턴 (1) | 2025.05.10 |
---|---|
[파이썬] FastAPI - 메모 앱 프로젝트 7: 보완(회원가입 등) (0) | 2025.05.10 |
[파이썬] FastAPI - 메모 앱 프로젝트 5: 웹페이지 개선 (0) | 2025.05.09 |
[파이썬] FastAPI - 메모 앱 프로젝트 4: 사용자별 메모 관리(feat. Almebic) (0) | 2025.05.09 |
[파이썬] FastAPI - 메모 앱 프로젝트 3: 사용자 인증 (0) | 2025.05.09 |