패스트캠퍼스 환급챌린지 30일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기

2025. 7. 30. 23:40·패스트캠퍼스/김민태의 프론트엔드 아카데미: 제1강

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다

 

패스트캠퍼스 환급챌린지란?

 

(~6/20) 50일의 기적 AI 환급반💫 | 패스트캠퍼스

초간단 미션! 하루 20분 공부하고 수강료 전액 환급에 AI 스킬 장착까지!

fastcampus.co.kr

https://fastcampus.info/4n8ztzq

 

야근으로 엄청 피곤하지만 재미있게 강의를 들을 수 있게 해주는 환급챌린지 최공!

 

30일차 공부 시작!

오늘 공부 시작 시간 (07.30 23:05)

 

오늘의 강의 주제는 '로그인-병렬, 연속적 비동기 API 처리의 이해'이다.

 

학습 인증샷 - 1

 

(오늘도 마찬가지로 모자이크 처리..)

 

오늘의 학습 내용

* 로그인과 토큰 인증 흐름 이해

 

우선 로그인 API는 사용자의 아이디와 비밀번호를 입력받아 인증 결과를 반환한다. 이때 실제로는 데이터베이스를 통한 사용자의 정보 검사 과정이 필요하지만, 이번 실습에서는 무작위로 로그인 성공 여부를 결정하는 방식으로 간단하게 처리한다.


성공적인 로그인 이후 클라이언트에게는 사용자 ID와 함께 고유한 토큰 값이 전달된다. 이때 전달되는 토큰은 이후의 모든 API 요청에서 인증을 대신하는 키 역할을 한다. HTTP는 상태가 유지되지 않는 프로토콜이기 때문에 클라이언트가 서버에 인증 상태를 유지하려면, 모든 요청마다 토큰 값을 함께 보내야 한다. 이러한 방식이 바로 토큰 기반 인증이다.


* axios와 비동기 API 호출 방식


API 호출에는 JavaScript의 fetch API가 아닌 axios 라이브러리를 사용했다. 실제 현업에서는 axios의 사용성이 더 우수하다는 이유로 많이 활용된다. 설정이 간단하며 헤더 삽입이나 응답 처리 등에서 코드가 간결하게 작성되기 때문이다.

로그인 성공 후, 서버로부터 받은 사용자 ID와 토큰을 활용해 두 가지 API(`/api/user/:id`, `/api/user/:id/posts`)를 호출해야 했는데, 이 과정은 병렬적으로 처리하는 것이 좋다. 프로필 정보와 게시물 목록을 순차적으로 받아올 경우 전체 응답 시간이 길어질 수 있기 때문이다.

이를 병렬적으로 처리하기 위해 axios.all()을 사용했다. 이는 Promise.all과 동일한 개념으로, 여러 요청을 동시에 보내고 결과를 묶어서 받는다. 이 방식은 동시에 실행 가능한 비동기 요청에 적합하며, 두 요청 중 하나라도 실패하면 오류 처리를 통해 적절한 피드백을 줄 수 있다.


* 토큰 검사와 라우팅 처리


서버의 각 API에서는 요청 헤더에 토큰이 존재하는지를 검사하는 로직이 포함되어 있다. 토큰이 없을 경우 서버는 403 에러를 반환하여 인증되지 않은 요청임을 알린다. 이렇게 헤더를 통해 인증 정보를 검증하는 방식은 보안상 중요한 역할을 하며, 사용자가 로그인한 상태여야만 특정 API를 사용할 수 있도록 하는 제한을 구현한다.

응답을 받은 후에는 클라이언트 스토어(예: `this.#data.store`)에 사용자 프로필과 글 목록을 저장하고, 클라이언트 사이드 라우팅을 이용해 사용자 프로필 페이지로 이동한다.


* 공통 로직 분리와 컴포넌트 구조


반복적으로 사용되는 컴포넌트를 별도의 클래스(TextField)로 추상화하고, 로그인 뷰에서는 이를 조합하여 구성했다. 또한 중복되는 로직이나 데이터 초기화 과정은 초기화 메서드로 분리되어 코드의 재사용성과 유지보수성이 높아졌다. 데이터도 외부에서 주입받는 구조로 설계되어 있어 상태 의존도를 낮추고 데이터 관리가 명확하게 이루어진다.

공통 요소가 많은 페이지는 핵심 로직을 공유할 수 있도록 베이스 클래스 형태로 만들고, 페이지별 특화된 기능만 독립적으로 작성하는 구조가 유지된다면, 전체 코드의 품질과 생산성이 올라갈 수 있다.

 

* 실습 코드 - 서버

const path = require('path');
const express = require('express');
const morgan = require('morgan');
const { urlencoded, json } = require('body-parser');
const { v4 } = require('uuid');
const axios = require('axios');
const app = express();

app.use(express.static('dist'));
app.use(morgan('dev'));
app.use(urlencoded({ extended: false }))
app.use(json())

app.get('/api/user/:id', (req, res) => {
  if (!req.headers['token']) {
    return res.status(403).send({
      status: 'Error'
    });
  }

  axios.get('https://randomuser.me/api')
    .then(result => {
      const [userProfile] =  result.data.results;
      const { name, picture, phone, email, country } = userProfile;

      res.status(200).send({
        status: 'OK',
        result: {
          name, picture, phone, email, country
        }
      })
    })
    .catch(e => {
      res.status(400).send({
        status: 'Error'
      });
    });
});

app.get('/api/user/:id/posts', (req, res) => {
  if (!req.headers['token']) {
    return res.status(403).send({
      status: 'Error'
    });
  }

  axios.get(`https://jsonplaceholder.typicode.com/posts?userId=${req.params.id}`)
    .then(result => {
      res.status(200).send({
        status: 'OK',
        results: result.data
      })
    })
    .catch(e => {
      res.status(400).send({
        status: 'Error'
      });
    });
});

app.post('/api/authentication', (req, res) => {
  if (Math.floor(Math.random() * 10) % 2 === 0) {
    res.status(200).send({
      status: 'OK',
      result: {
        id: Math.floor(Math.random()*10),
        token: v4(),
      }
    });
  } else {
    res.status(400).send({
      status: 'Error'
    });
  }
});

app.listen(8080, () => {
  console.log('ready to dumy signup server');
});

 

* 실습 코드 - 클라이언트 axios

 

import template from './login.template';
import TextField from '../views/text-field';
import axios  from 'axios';

export default class Login {
  #template = template;
  #data;
  #container;
  #loginFail = false;
  #fields = [];

  constructor(container, data) {
    this.#container = document.querySelector(container);
    this.#data = data;

    this.#initialize();
  }

  #initialize = () => {
    const idField = new TextField('#login-fields', {
      id: 'userid', label: '아이디', type: 'text', placeholder: '아이디를 입력해주세요', require: true,
    });
    const passwordField = new TextField('#login-fields', {
      id: 'password', label: '비밀번호', type: 'password', placeholder: '**********', require: true,
    });

    this.#fields.push(idField);
    this.#fields.push(passwordField);
  }

  #onSubmit = e => {
    e.preventDefault();

    const loginData = this.#fields
      .map(field => ({ [field.name]: field.value }))
      .reduce((a, b) => ({ ...a, ...b }), {});

    axios.post('/api/authentication', loginData)
      .then(result => {
        return result.data.result;
      })
      .then(({ id, token }) => {
        const options = { headers: { token } };
        this.#data.store.token = token;

        return axios.all([
          axios.get(`/api/user/${id}`, options),
          axios.get(`/api/user/${id}/posts`, options),
        ]);
      })
      .then(([profile, posts]) => {
        this.#data.store.userProfile = profile.data.result;
        this.#data.store.userPosts = posts.data.results;

        location.href = '/#/profile';
      })
      .catch(error => {      
        this.#loginFail = true;
        this.render();
      });
  }

  render = () => {
    this.#container.innerHTML = this.#template({ ...this.#data, loginFail: this.#loginFail });
    this.#fields.forEach(field => {
      field.render(true);
    });

    this.#container.addEventListener('submit', this.#onSubmit);
  }
}

 

32강 수강 완료

 

오늘 공부 종료 시간 (07.30 23:25)
32강 강의 내용 정리 필기

 

학습 인증샷 - 2

마치며

 

오늘도 환급 챌린지 32강 공부 완료!!

 

이번 강의에서는 로그인 처리를 중심으로, 연속적이거나 병렬적으로 동작하는 비동기 API 호출 방식에 대해 실습을 통해 익혀보았다. Node.js를 이용한 간단한 서버와 프론트엔드 코드 작성이 주요했다.

'패스트캠퍼스 > 김민태의 프론트엔드 아카데미: 제1강' 카테고리의 다른 글

패스트캠퍼스 환급챌린지 32일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (0) 2025.08.01
패스트캠퍼스 환급챌린지 31일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (3) 2025.07.31
패스트캠퍼스 환급챌린지 29일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (2) 2025.07.29
패스트캠퍼스 환급챌린지 28일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (3) 2025.07.28
패스트캠퍼스 환급챌린지 27일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (2) 2025.07.27
'패스트캠퍼스/김민태의 프론트엔드 아카데미: 제1강' 카테고리의 다른 글
  • 패스트캠퍼스 환급챌린지 32일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
  • 패스트캠퍼스 환급챌린지 31일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
  • 패스트캠퍼스 환급챌린지 29일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
  • 패스트캠퍼스 환급챌린지 28일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
hangyeoldora
hangyeoldora
hangyeoldora 님의 블로그 입니다.
  • hangyeoldora
    한결의 개발로그
    hangyeoldora
  • 글쓰기 관리자
  • 전체
    오늘
    어제
    • 분류 전체보기 (65)
      • Azure (2)
      • NestJS (1)
      • Python (1)
        • 자연어 처리 (0)
        • 머신러닝 (0)
      • Java (1)
      • Javascript (1)
      • LLM (1)
        • 벡터 DB (1)
      • Electron (0)
      • Nsis (0)
      • 자격증 (0)
      • 패스트캠퍼스 (51)
        • 김민태의 프론트엔드 아카데미: 제1강 (51)
      • 혼공단 (7)
        • 혼자 공부하는 머신러닝 (7)
      • 목표 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 최근 댓글

  • 최근 글

  • 태그

    환급챌린지
    오공완
    습관형성
    직장인자기계발
    혼공학습단
    패스트캠퍼스후기
    딥러닝
    패스트캠퍼스
    혼공단
    혼공머신
  • hELLO· Designed By정상우.v4.10.3
hangyeoldora
패스트캠퍼스 환급챌린지 30일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
상단으로

티스토리툴바