회원가입 만들기: HTML + JavaScript 코드를 TypeScript로 바꾸는 과정

이번에는 간단한 회원가입 화면을 만들고, 브라우저에서 API를 호출하는 코드를 TypeScript 방식으로 바꾸는 과정을 정리해보겠습니다.
초보자 입장에서는 먼저 HTML로 화면을 만들고, 그 다음 JavaScript로 동작을 붙인 뒤, 마지막에 TypeScript로 안전하게 개선하는 흐름이 이해하기 쉽습니다.
1. 회원가입 화면 만들기
먼저 user_reg.html 파일을 만듭니다.
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>회원가입</title> </head> <body> <h1>회원가입</h1> <input type="text" id="username" placeholder="username" /><br> <input type="password" id="password" placeholder="password" /><br> <input type="text" id="nickname" placeholder="nickname" /><br> <button id="signupBtn">회원가입</button> </body> </html>
이 코드는 사용자에게 다음 3가지 정보를 입력받습니다.
usernamepasswordnickname
그리고 회원가입 버튼을 누르면 API를 호출하도록 만들 예정입니다.
2. form 태그로 구조 개선하기
단순히 input과 button만 두는 것도 가능하지만, 회원가입처럼 사용자 입력을 모으는 화면은 form 태그로 묶는 것이 더 자연스럽습니다.
다음과 같이 수정해보겠습니다.
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>회원가입</title> </head> <body> <h1>회원가입</h1> <form id="signupForm"> <input type="text" id="username" placeholder="username" /><br> <input type="password" id="password" placeholder="password" /><br> <input type="text" id="nickname" placeholder="nickname" /><br> <button type="submit" id="signupBtn">회원가입</button> </form> <script type="module" src="/src/user_reg.ts"></script> </body> </html>
여기서 중요한 점은 다음과 같습니다.
form태그를 추가했다.- 버튼의 타입을
submit으로 지정했다. - TypeScript 파일인
user_reg.ts를 연결했다.
3. JavaScript 방식으로 먼저 생각해보기
보통 처음에는 JavaScript로 이렇게 작성합니다.
const signupForm = document.getElementById('signupForm'); const usernameInput = document.getElementById('username'); const passwordInput = document.getElementById('password'); const nicknameInput = document.getElementById('nickname'); signupForm.addEventListener('submit', async (e) => { e.preventDefault(); const username = usernameInput.value; const password = passwordInput.value; const nickname = nicknameInput.value; const response = await fetch('/api/users/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, nickname }) }); const data = await response.json(); console.log(data); });
이 방식도 동작은 할 수 있지만, 문제는 TypeScript가 아니기 때문에 실수를 잡아주지 못한다는 점입니다.
예를 들어:
getElementById()가null일 수도 있음value속성이 없는 요소를 잘못 가져올 수도 있음- API 요청 데이터 구조를 명확히 관리하기 어려움
그래서 이제 이 코드를 TypeScript로 바꿔보겠습니다.
4. TypeScript 파일 만들기
src/user_reg.ts 파일을 생성합니다.
interface SignupRequest { username: string; password: string; nickname: string; } const signupForm = document.getElementById('signupForm') as HTMLFormElement | null; const usernameInput = document.getElementById('username') as HTMLInputElement | null; const passwordInput = document.getElementById('password') as HTMLInputElement | null; const nicknameInput = document.getElementById('nickname') as HTMLInputElement | null; if (!signupForm || !usernameInput || !passwordInput || !nicknameInput) { throw new Error('회원가입 폼 요소를 찾을 수 없습니다.'); } signupForm.addEventListener('submit', async (event: SubmitEvent) => { event.preventDefault(); const signupData: SignupRequest = { username: usernameInput.value, password: passwordInput.value, nickname: nicknameInput.value, }; try { const response = await fetch('/api/users/register', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(signupData), }); if (!response.ok) { throw new Error(`회원가입 실패: ${response.status}`); } const result = await response.json(); console.log('회원가입 성공:', result); alert('회원가입이 완료되었습니다.'); } catch (error) { console.error('에러 발생:', error); alert('회원가입 중 오류가 발생했습니다.'); } });
5. TypeScript로 바꾸면서 좋아진 점
5-1. 요청 데이터 구조를 명확히 정의할 수 있다
interface SignupRequest { username: string; password: string; nickname: string; }
이렇게 interface를 사용하면, 회원가입 요청에 어떤 데이터가 들어가는지 한눈에 알 수 있습니다.
즉,
username은 문자열password는 문자열nickname은 문자열
이라는 사실이 분명해집니다.
5-2. HTML 요소 타입을 명확하게 지정할 수 있다
const usernameInput = document.getElementById('username') as HTMLInputElement | null;
브라우저는 getElementById()로 가져온 요소가 정확히 어떤 태그인지 자동으로 완벽하게 알지 못합니다.
그래서 TypeScript에서는 HTMLInputElement라고 알려주는 것이 좋습니다.
이렇게 하면 .value 같은 속성을 안전하게 사용할 수 있습니다.
5-3. null 체크를 통해 오류를 줄일 수 있다
if (!signupForm || !usernameInput || !passwordInput || !nicknameInput) { throw new Error('회원가입 폼 요소를 찾을 수 없습니다.'); }
JavaScript에서는 종종 이런 체크 없이 바로 코드를 작성하다가 런타임 오류가 발생합니다.
TypeScript는 이런 부분을 더 조심하게 만들어줍니다.
5-4. API 호출 코드도 더 안정적으로 관리할 수 있다
const signupData: SignupRequest = { username: usernameInput.value, password: passwordInput.value, nickname: nicknameInput.value, };
signupData가 SignupRequest 구조를 반드시 따라야 하므로,
속성 이름을 틀리거나 빠뜨리는 실수를 줄일 수 있습니다.
6. 파일 구조 예시
프로젝트 구조는 대략 이렇게 둘 수 있습니다.
myboard/ ├─ index.html ├─ user_reg.html ├─ package.json ├─ package-lock.json ├─ node_modules/ └─ src/ └─ user_reg.ts
만약 Vite 기반으로 작업 중이라면 user_reg.html에서 src/user_reg.ts를 바로 불러와 테스트할 수 있습니다.
7. 정리
회원가입 화면은 단순한 예제처럼 보여도, 실제로는 다음 개념이 다 들어 있습니다.
- 사용자 입력 받기
- form 제출 이벤트 처리
- API 요청 보내기
- 서버 응답 처리
- 예외 처리
이 과정을 JavaScript로 먼저 만들고, TypeScript로 바꾸면 다음 장점이 생깁니다.
- 입력 요소 타입을 명확하게 다룰 수 있다
- null 가능성을 미리 체크할 수 있다
- API 요청 데이터 구조를 인터페이스로 정리할 수 있다
- 유지보수가 쉬워진다
즉, TypeScript는 코드를 더 어렵게 만드는 도구가 아니라, 실수를 줄이고 구조를 분명하게 만드는 도구라고 볼 수 있습니다.
마무리
다음 단계에서는 여기에 이어서 다음 내용도 확장할 수 있습니다.
- 회원가입 성공 후 로그인 페이지로 이동하기
- 입력값 검증 추가하기
- 응답 타입까지 TypeScript로 정의하기
- 공통 API 함수로 분리하기
초보자일수록 작은 예제를 이렇게 하나씩 TypeScript로 바꿔보는 연습이 정말 중요합니다.

댓글
댓글을 작성하려면 이 필요합니다.