로그인 체크 Todo SPA: HTML + JavaScript 코드를 TypeScript로 바꾸는 과정

우리가 만들려는 TODO 웹은 SPA(Single Page Application) 형태입니다. 즉, 페이지를 여러 장 넘기기보다 하나의 HTML 문서 안에서 로그인 상태 확인, 목록 보기, 등록 기능 등을 자바스크립트로 제어하는 방식입니다.
이번 글에서는 먼저 로그인한 사용자만 TODO 페이지에 접근할 수 있도록 만드는 코드를 TypeScript로 바꾸는 과정을 정리해보겠습니다.
1. 요구사항 정리
우리가 만들려는 TODO 프로그램은 다음과 같은 흐름으로 동작합니다.
- 이 프로그램은 로그인한 이후에만 사용할 수 있습니다.
- 1개의 HTML 문서에서 동작하는 SPA 구조로 구현합니다.
- 페이지에 진입하면 먼저 로그인 상태를 확인해야 합니다.
- 로그인하지 않았다면
login.html로 이동합니다.
여기서 로그인 여부를 판단하는 기준은 2단계입니다.
- 로컬스토리지에 token이 있는지 확인한다
- 없으면 로그인되지 않은 상태이므로login.html로 이동 - 내 정보 조회 API를 호출한다
-/auth/me응답이 정상이라면 로그인 상태
- 실패하면 유효하지 않은 토큰일 수 있으므로login.html로 이동
즉, 단순히 token이 저장되어 있다는 이유만으로 로그인 상태라고 판단하면 안 되고, 서버에 실제로 유효한 사용자인지 다시 확인해야 합니다.
2. 기존 JavaScript 코드의 특징
기존 코드는 대략 다음과 같은 흐름입니다.
localStorage.getItem("token")으로 토큰을 읽어온다.- 토큰이 없으면
login.html로 이동한다. - 토큰이 있으면
/auth/meAPI를 호출한다. - 응답이 실패하면 다시
login.html로 이동한다. - 성공하면 사용자 정보를 콘솔에 출력한다.
이 방식 자체는 맞지만, JavaScript로만 작성하면 다음과 같은 문제가 생기기 쉽습니다.
token이null일 수 있는데 문자열처럼 다룰 수 있다.- 응답 데이터 구조를 정확히 모른 채 사용할 수 있다.
userInfo와loginInfo같은 변수명이 섞이면 실수가 생긴다.- DOM, API 응답, 상태값에 대한 타입이 없어서 유지보수가 어려워진다.
이럴 때 TypeScript를 사용하면 코드가 훨씬 안전해집니다.
3. HTML 파일 구성
우선 HTML은 아주 단순하게 시작할 수 있습니다.
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Todo Web</title> </head> <body> <h1>나만의 Todo Web</h1> <script type="module" src="./todo.ts"></script> </body> </html>
기존에는 <script> 태그 안에 직접 JavaScript를 넣었지만, TypeScript로 바꿀 때는 보통 별도의 .ts 파일로 분리하는 것이 좋습니다.
여기서는 todo.ts 파일에서 로그인 체크를 수행하도록 하겠습니다.
4. 먼저 타입을 설계하자
API를 호출할 때 가장 먼저 해야 할 일은 응답 구조를 예상하고 타입으로 정의하는 것입니다.
예를 들어 /auth/me API가 다음과 같은 형태로 응답한다고 가정해봅시다.
{ "data": { "id": 1, "username": "hong", "nickname": "홍길동" } }
그럼 TypeScript에서는 이렇게 인터페이스를 만들 수 있습니다.
interface User { id: number; username: string; nickname: string; } interface MeResponse { data: User; }
이렇게 하면 response.json()으로 받은 값이 어떤 구조인지 좀 더 명확하게 다룰 수 있습니다.
5. JavaScript 코드를 TypeScript로 변환하기
이제 핵심 코드를 TypeScript로 작성해보겠습니다.
interface User { id: number; username: string; nickname: string; } interface MeResponse { data: User; } let userInfo: User | null = null; async function checkLogin(): Promise<void> { console.log('함수호출시작'); const token: string | null = localStorage.getItem('token'); console.log(`token : ${token}`); if (!token) { location.href = 'login.html'; return; } const response: Response = await fetch( 'https://api.fullstackfamily.com/api/edu/ws-283fc1/auth/me', { method: 'GET', headers: { Authorization: `Bearer ${token}`, }, } ); console.log(`response.ok : ${response.ok}`); if (!response.ok) { location.href = 'login.html'; return; } const result: MeResponse = await response.json(); userInfo = result.data; console.log(userInfo); console.log('함수호출끝'); } checkLogin();
6. 무엇이 좋아졌을까?
6-1. token의 타입이 명확해졌다
const token: string | null = localStorage.getItem('token');
JavaScript에서는 localStorage.getItem()이 null을 반환할 수 있다는 점을 놓치기 쉽습니다.
하지만 TypeScript에서는 string | null로 타입이 잡히기 때문에, 반드시 if (!token) 같은 검사를 하게 됩니다.
즉, null 체크를 강제해줘서 더 안전합니다.
6-2. 응답 데이터 구조를 명확히 알 수 있다
const result: MeResponse = await response.json(); userInfo = result.data;
그냥 any처럼 다루는 것이 아니라, /auth/me가 어떤 데이터를 반환하는지 코드 수준에서 설명할 수 있습니다.
이렇게 해두면 나중에
result.data.usernameresult.data.nicknameresult.data.id
같은 값을 사용할 때 자동완성과 타입 체크를 받을 수 있습니다.
6-3. 변수명 실수를 줄일 수 있다
기존 코드에는 let userInfo;를 선언해놓고 실제로는 loginInfo = await response.json();처럼 다른 이름을 쓰는 부분이 있었습니다.
이런 실수는 JavaScript에서는 런타임까지 가야 발견되지만, TypeScript에서는 훨씬 빨리 찾을 수 있습니다.
즉, TypeScript는 문법을 더 엄격하게 잡아줘서 실수를 줄여주는 도구입니다.
7. 리다이렉트 로직도 함수로 분리할 수 있다
조금 더 깔끔하게 작성하고 싶다면 로그인 페이지 이동 코드를 함수로 분리할 수 있습니다.
function moveToLogin(): void { location.href = 'login.html'; }
그 다음에는 이렇게 바꿀 수 있습니다.
if (!token) { moveToLogin(); return; } if (!response.ok) { moveToLogin(); return; }
중복이 줄어들고 코드 읽기도 더 좋아집니다.
8. 예외 처리까지 추가하면 더 좋다
실제 서비스에서는 네트워크 오류도 고려해야 합니다. 그래서 try...catch를 넣는 것이 좋습니다.
interface User { id: number; username: string; nickname: string; } interface MeResponse { data: User; } let userInfo: User | null = null; function moveToLogin(): void { location.href = 'login.html'; } async function checkLogin(): Promise<void> { try { const token: string | null = localStorage.getItem('token'); if (!token) { moveToLogin(); return; } const response: Response = await fetch( 'https://api.fullstackfamily.com/api/edu/ws-283fc1/auth/me', { method: 'GET', headers: { Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { moveToLogin(); return; } const result: MeResponse = await response.json(); userInfo = result.data; console.log('로그인 사용자 정보:', userInfo); } catch (error) { console.error('로그인 확인 중 오류 발생:', error); moveToLogin(); } } checkLogin();
이렇게 하면 서버가 잠시 응답하지 않거나 네트워크 문제가 생겨도 안전하게 처리할 수 있습니다.
9. 정리
이번 예제에서 핵심은 단순히 문법을 바꾸는 것이 아닙니다.
TypeScript로 바꾸면서 얻는 장점은 다음과 같습니다.
token이null일 수 있음을 명확하게 처리한다.- API 응답 구조를 인터페이스로 정의한다.
- 사용자 정보 타입을 안전하게 관리한다.
- 변수명 실수와 구조 실수를 줄인다.
- 유지보수하기 좋은 코드로 발전시킬 수 있다.
즉, TypeScript는 자바스크립트를 완전히 다른 언어로 바꾸는 것이 아니라, 기존 코드에 더 많은 안정성과 설명력을 붙여주는 도구라고 볼 수 있습니다.
10. 다음 단계
이제 로그인 여부를 확인할 수 있게 되었으니, 다음 단계에서는 이런 기능을 추가할 수 있습니다.
- 로그인한 사용자 이름 화면에 출력하기
- TODO 등록 폼 만들기
- TODO 목록 조회 API 붙이기
- TODO 완료/삭제 기능 만들기
- 상태 관리까지 고려한 구조로 개선하기
다음 글에서는 이 로그인 체크 이후에 TODO 등록과 목록 보기 기능을 TypeScript로 확장하는 과정도 이어서 정리해보겠습니다.

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