Krafton Jungle/6. Frameworks

[WEEK14] Day 2

munsik22 2025. 6. 13. 20:10

Javascript에서의 비동기 처리

async는 "asynchronous"의 줄임말로, "비동기"를 의미한다. 프로그래밍에서 async는 비동기 작업을 처리하는 데 사용되는 키워드다. 즉, 어떤 작업을 실행할 때 그 작업이 완료될 때까지 기다리지 않고 다른 작업을 실행할 수 있도록 하는 것을 의미한다.

자바스크립트는 기본적으로 단일 스레드 기반의 언어, 즉 동기식 언어이기 때문에 한 번에 하나의 작업만 처리할 수 있다. JS에서 동기 방식으로 작동하면 이전 작업이 완료될 때까지는 다음 작업을 수행할 수 없다. 병렬 처리와 네트워크 등의 작업을 위해서는 비동기 방식이 필요하다.

  • .then() 방식: Promise 기반
  • async/await 방식: Promise를 좀 더 읽기 쉽게 만든 sugar syntax
항목 .then() async/await
구조 체이닝 (중첩될 수 있음) 동기 코드처럼 읽힘
에러 처리 .catch()로 따로 처리 try/catch 블록 사용
가독성 복잡한 흐름일수록 가독성 나빠짐 간결하고 명확한 흐름
중첩 처리 .then().then()으로 체인 중첩 await로 순차적 흐름 표현
동시 실행 .then()으로 명시적 분기 가능 Promise.all() 같이 별도로 병렬 처리 작성 필요

아래 두 코드는 다른 방식을 사용하지만 같은 기능을 한다.

/* then 방식 */
fetch("/api/data")
  .then(res => res.json())
  .then(data => console.log(data));
/* async/await 방식 */
async function getData() {
  const res = await fetch("/api/data");
  const data = await res.json();
  console.log(data);
}

Node.js + MySQL 연동하기

  • MySQL을 설치한다.
  • MySQL Workbench (또는 MySQL Shell)에서 다음 쿼리를 입력해 DB와 테이블을 생성한다.
CREATE DATABASE testdb;
USE testdb;
CREATE TABLE USERINFO (
  id INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(45) NOT NULL,
  username VARCHAR(45) NOT NULL,
  password VARCHAR(100) NOT NULL,
  PRIMARY KEY (id)
);

번개 모양 아이콘을 클릭해서 쿼리를 실행한다.

  • 다음 명령어를 입력해 mysql2 모듈을 설치한다: npm install mysql2
  • 서버단에서 db.js 파일을 생성한다.
/* server/db.js */
const mysql = require("mysql2");
const connection = mysql.createConnection({
    host: "localhost",
    user: "host", // 실제 사용자 이름
    password: "password", // 실제 비밀번호
    database: "database", // 실제 DB 이름
});
const db = connection.promise();
module.exports = db;
  • index.js 상단에 다음 코드를 추가한다: const db = require("./db.js");
  • 비밀번호 암호화를 위해 bcrypt 모듈을 설치한다: npm install bcryptjs
  • index.js 상단에 다음 코드를 추가한다: import bcrypt from "bcryptjs";

회원가입 기능 구현하기

🔹 클라이언트 사이드

<form class="signup_form">
    <h1 class="signup_title">회원가입</h1>
    <div class="signup_inputs">
        <div class="signup_box">
            <input
                placeholder="이름"
                class="signup_input"
                required=""
                type="text"
            /><i class="ri-account-circle-line"></i>
        </div>
        <div class="signup_box">
            <input
                placeholder="ID"
                class="signup_input"
                required=""
                type="text"
            /><i class="ri-user-fill"></i>
        </div>
        <div class="signup_box">
            <input
                placeholder="비밀번호"
                class="signup_input"
                required=""
                type="password"
            /><i class="ri-lock-2-fill"></i>
        </div>
        <div class="signup_box">
            <input
                placeholder="비밀번호 재입력"
                class="signup_input"
                required=""
                type="password"
            /><i class="ri-lock-2-fill"></i>
        </div>
    </div>
    <button type="submit" class="signup_button">회원가입</button>
    <div class="signup_register">
        이미 계정이 있으신가요? <a href="/signup">로그인</a>
    </div>
</form>
fetch("/api/sign-up", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
    },
    body: JSON.stringify({
        name: name,
        username: username,
        password: password,
        confirmPw: confirmPw,
    }),
})
    .then((response) =>
        response.json().then((data) => ({ status: response.status, data }))
    )
    .then(({ status, data }) => {
        if (status === 201) {
            // 회원가입 성공
            setUsernameCheck(false);
            setPwCheck(false);
            alert("회원가입이 완료되었습니다. 로그인 해주세요.");
            navigate("/login");
        } else if (status == 400) {
            // 비밀번호 불일치
            setPwCheck(true);
            setUsernameCheck(false);
        } else if (status == 409) {
            // ID 중복
            setUsernameCheck(true);
            setPwCheck(false);
        } else {
            setPwCheck(false);
            setUsernameCheck(false);
            alert("회원가입 중 오류가 발생했습니다. 다시 시도해주세요.");
        }
    });

🔹 서버 사이드

/* 회원가입 */
app.post("/api/sign-up", (req, res) => {
    const { name, username, password, confirmPw } = req.body;

    /* 비밀번호 확인 */
    if (password !== confirmPw) {
        return res.status(400).json({ error: "Invalid password" });
    }

    /* 중복 아이디 체크 */
    db.query("SELECT * FROM USERINFO WHERE username = ?", [username])
        .then(([rows]) => {
            if (rows.length > 0) {
                return res.status(409).json({ error: "Conflict username" });
            }

            /* 비밀번호 암호화 */
            const salt = bcrypt.genSaltSync(10);
            const hash = bcrypt.hashSync(password, salt);

            /* DB에 회원 정보 추가 */
            return db.query(
                "INSERT INTO USERINFO (name, username, password) VALUES (?, ?, ?)",
                [name, username, hash]
            );
        })
        .then(() => {
            if (!res.headersSent) {
                res.status(201).json({ message: "User created" });
            }
        })
        .catch((err) => {
            console.error("Error occurred on sign-up:", err);
            if (!res.headersSent) {
                res.status(500).json({ error: "Server error" });
            }
        });
});

구현 예시


로그인 기능 구현하기

  • 로그인 구현을 위해 jwt 모듈을 설치한다: npm install jsonwebtoken
  • index.js 상단에 다음 코드를 추가한다.
const jwt = require("jsonwebtoken");
const SECRET_KEY = "비밀키";

🔹 클라이언트 사이드

<form class="login_form">
    <h1 class="login_title">로그인</h1>
    <div class="login_inputs">
        <div class="login_box">
            <input
                placeholder="ID"
                class="login_input"
                required=""
                type="text"
            /><i class="ri-user-fill"></i>
        </div>
        <div class="login_box">
            <input
                placeholder="비밀번호"
                class="login_input"
                required=""
                type="password"
            /><i class="ri-lock-2-fill"></i>
        </div>
    </div>
    <button type="submit" class="login_button">로그인</button>
    <div class="login_register">
        아직 회원이 아니신가요?
        <a href="/signup">회원가입</a>
    </div>
</form>
fetch("/api/log-in", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
    },
    body: JSON.stringify({
        username: username,
        password: password,
    }),
})
    .then((response) =>
        response.json().then((data) => ({ status: response.status, data }))
    )
    .then(({ status, data }) => {
        if (status === 200) {
            setLoginCheck(false);
            sessionStorage.setItem("username", data.username);
            sessionStorage.setItem("token", data.token);
            navigate("/");
        } else {
            setLoginCheck(true);
        }
    })
    .catch((error) => {
        console.error("Error occurred on login:", error);
        setLoginCheck(true);
    });

🔹 서버 사이드

app.post("/api/log-in", (req, res) => {
    const { username, password } = req.body;
    db.query("SELECT * FROM USERINFO WHERE username = ?", [username])
        .then(([rows]) => {
			/* 유효하지 않은 ID */
            if (rows.length === 0) {
                return res.status(401).json({ error: "Invalid ID" });
            }
            const user = rows[0];
            const isPwCorrect = bcrypt.compareSync(password, user.password);
            /* 비밀번호가 틀린 경우 */
			if (!isPwCorrect) {
                return res.status(401).json({ error: "Invalid password" });
            }
			/* JWT 토큰 생성 */
			const payload = { username: username };
			const token = generateToken(payload);
			return res.status(200)
				.json({ message: "Login success", username, token });
        })
        .catch((err) => {
            console.error(err);
            res.status(500).json({ error: "Server error" });
        });
});

구현 예시


TODO

  • 로그인 여부(토큰을 가지고 있는가?)를 전역으로 관리하기
  • 로그아웃 구현하기
  • 토큰 유효성 검증 구현하기

[참고자료]

 

async / await 개념 정리 (Feat. 동기, 비동기)

📡 동기와 비동기 동기(synchronous)란, 어떤 작업을 실행할 때 그 작업이 끝나기를 기다리는 방식을 의미한다. 즉, 작업이 완료될 때까지 다음 코드의 실행을 멈추고 기다리는 것이다. 이러한 방식

trustmitt.tistory.com

 

[MySQL] MySQL 설치하기 (윈도우 / windows)

MySQLMySQL은 가장 많이 사용되는 데이터베이스 중 하나이다.무료이기에 간단히 설치해 바로 사용할 수 있다. 윈도우와 리눅스 등 다양한 운영체제에서 사용 가능해 확장성이 뛰어나다. 표준 SQL

code-angie.tistory.com

 

MySQL :: 쿼리문, 명령어 모음

MySQL command Cheat Sheet 메모입니다🤓

velog.io

 

[Node.js] bcrypt 비밀번호 암호화 하기

회원가입을 구현할 때 회원 정보를 데이터베이스에 저장해야하는데 회원의 비밀번호를 평문 그대로 저장하게 된다면? 관리자가 모든 회원의 비밀번호를 알 수 있음 -> 보안 취약점 발생 비밀번

jisilver-k.tistory.com

 

[Node.js] express 프레임워크와 MySQL을 연동하여 CRUD 구현하기

※ express에 대한 설명은 아래의 링크를 참고 바란다.express 프레임워크로 웹 서버 구축하기간단하게 express 사용하여 웹 서버를 구축해보았다. 하지만 사용자의 데이터를 받어서 데이터베이스에

velog.io

 

NodeJS (JWT를 이용한 로그인 구현하기)

첫번째 프로젝트를 진행했을 당시, 백엔드를 담당하였고 그 중 User와 Admin 관련 기능을 구현하게 되었다. User 기능 중 가장 기본이 되는 로그인과 로그아웃 방식을 JWT 토큰을 활용한 방식으로 구

velog.io

'Krafton Jungle > 6. Frameworks' 카테고리의 다른 글

[WEEK14] Day 6  (0) 2025.06.17
[WEEK14] Day 5  (0) 2025.06.16
[WEEK14] Day 3  (0) 2025.06.14
[WEEK14] Day 1  (0) 2025.06.12