React

 

Express 백엔드에서 Passport와 jsonwebtoken을 사용하여 인증 처리 시스템을 구현하는 방법을 설명합니다. 주요 개념, 설치 방법, 그리고 실용적인 코드를 포함한 안내서를 제공합니다.

 

토큰은 mysql 에 저장하는 방식을 취했습니다.

 

 

1.백엔드

1. 프로젝트에 필요한 라이브러리 설치

npm install express passport passport-local passport-kakao jsonwebtoken bcrypt dotenv sequelize mysql2 express-session cors cookie-parser morgan


 

위 명령어를 실행하면, Express와 관련된 주요 인증 및 데이터 처리 라이브러리가 설치됩니다.

 

 

2. 프로젝트 구조 설계 및 환경 설정

  • 프로젝트 구조

 

/project
├── /models        # Sequelize 모델 정의
├── /routes        # 라우터 파일
├── /passport      # Passport 전략 파일
├── /utils         # 유틸리티 함수
├── app.js         # Express 서버 메인 파일
├── .env           # 환경 변수

 

.env 예시

JWT_SECRET_KEY=your_secret_key
ACCESS_TOKEN_SECRET=your_access_secret
REFRESH_TOKEN_SECRET=your_refresh_secret
COOKIE_SECRET=your_cookie_secret
PORT=8001

 

 

models/user.js

const Sequelize = require('sequelize');
const { DataTypes } = require('sequelize'); 

class User extends Sequelize.Model {
  static initiate(sequelize) {
    User.init({
      id: { // 기본 키로 설정
        type: Sequelize.INTEGER,
        autoIncrement: true,
        primaryKey: true,
      },
      userId: {  
        type: Sequelize.STRING(40),
        allowNull: false,
        unique: true,
      },
      nick: {  
        type: Sequelize.STRING(15),
        allowNull: false,
      },
      password: {
        type: Sequelize.STRING(100),
        allowNull: true,
      },
      address: {
        type: Sequelize.TEXT,
        allowNull: false,
      },
      userType: {
        type: DataTypes.ENUM('guest', 'owner'), 
        allowNull: false,
        defaultValue: 'guest',
      },
      time: {  
        type: Sequelize.STRING(100),
        allowNull : true,
      },
    
    }, {
      sequelize,
      timestamps: true,
      underscored: false,
      modelName: 'User',
      tableName: 'users',
      paranoid: true,
      charset: 'utf8mb4',
      collate: 'utf8mb4_general_ci',
    });
  }

  static associate(db) {
    db.User.hasOne(db.RefreshToken, { // User와 RefreshToken 간 1:1 관계
      foreignKey: 'userId',
      sourceKey: 'userId',
      onDelete: 'CASCADE',
      onUpdate: 'CASCADE',
    });   
  }
};

module.exports = User;

 

models/refreshTokens.js

const Sequelize = require('sequelize');

class RefreshToken extends Sequelize.Model {
  static initiate(sequelize) {
    RefreshToken.init({
      id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        primaryKey: true,
      },
      userId: { // User의 기본 키 id와 연결
        type: Sequelize.INTEGER, // User.id의 데이터 타입과 일치시킴
        allowNull: false,
        unique: true, // 한 User는 하나의 RefreshToken만 가질 수 있음
      },
      refreshToken: {
        type: Sequelize.STRING(255),
        allowNull: false,
      },
      expiresAt: {
        type: Sequelize.DATE,
        allowNull: false,
      },
    }, {
      sequelize,
      timestamps: true,
      underscored: false,
      modelName: 'RefreshToken',
      tableName: 'refresh_tokens',
      paranoid: false,
      charset: 'utf8mb4',
      collate: 'utf8mb4_general_ci',
    });
  }

  static associate(db) {
    db.RefreshToken.belongsTo(db.User, {
      foreignKey: 'userId', // RefreshToken.userId -> User.id
      targetKey: 'id',      // User의 기본 키(id)에 연결
      onDelete: 'CASCADE',
      onUpdate: 'CASCADE',
    });
  }
}

module.exports = RefreshToken;

 

 

 

 

 

 

 

 

 

 

 

 

3. app.js 구현

app.js는 Express 애플리케이션의 메인 엔트리로, 불필요한 코드는 제거하고 중요한 기능에 집중합니다.

 

const express = require('express');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const dotenv = require('dotenv');
const passport = require('passport');
const cors = require('cors');
const { sequelize } = require('./models');
const passportConfig = require('./passport');
const authRouter = require('./routes/auth'); // 인증 관련 라우터

dotenv.config();
const app = express();
passportConfig(); // Passport 설정 초기화

// 기본 설정
app.set('port', process.env.PORT || 8001);

// DB 연결
sequelize.sync({ force: false })
  .then(() => console.log('데이터베이스 연결 성공'))
  .catch(err => console.error(err));

// 미들웨어 설정
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: { httpOnly: true, secure: false },
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(cors({
  origin: 'http://localhost:3000', // React 프론트엔드 도메인
  credentials: true,
}));

// 라우터 연결
app.use('/api/auth', authRouter);

// 에러 처리
app.use((req, res, next) => {
  const error = new Error('Not Found');
  error.status = 404;
  next(error);
});
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ message: err.message });
});

// 서버 시작
app.listen(app.get('port'), () => {
  console.log(`Server running on port ${app.get('port')}`);
});

 

 

4. Passport 설정 (passport/index.js)

Passport는 인증 요청 처리의 핵심 역할을 합니다. local과 kakao 전략을 통합 관리합니다

 

const passport = require('passport');
const local = require('./localStrategy');
const kakao = require('./kakaoStrategy');
const User = require('../models/user');

module.exports = () => {
  passport.serializeUser((user, done) => {
    console.log('serialize');
    done(null, user.id);
  });

  passport.deserializeUser((id, done) => {
    console.log('deserialize');
    User.findOne({
      where: { id },
      include: [{
        model: User,
        attributes: ['id', 'nick'],
        as: 'Followers',
      }, {
        model: User,
        attributes: ['id', 'nick'],
        as: 'Followings',
      }],
    })
      .then(user => {
        console.log('user', user);
        done(null, user);
       })
      .catch(err => done(err));
  });

  local();
  kakao();
};

 

 

localStrategy.js

로컬 로그인 전략 (passport/localStrategy.js)

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');

const User = require('../models/user');

module.exports = () => {  // 로그인
  passport.use(new LocalStrategy({
    usernameField: 'userId',   // req.body.userId
    passwordField: 'password',  // req.body.password
    passReqToCallback: false,
  }, async (userId, password, done) => {
    try {
      const exUser = await User.findOne({ where: { userId } });
      if (exUser) {
        const result = await bcrypt.compare(password, exUser.password);
        if (result) {
          done(null, exUser);
        } else {
          done(null, false, { message: '비밀번호가 일치하지 않습니다.' });
        }
      } else {
        done(null, false, { message: '가입되지 않은 회원입니다.' });
        
      }
    } catch (error) {
      console.error(error);
      done(error);
    }
  }));
};

 

 

kakaoStrategy.js

const passport = require('passport');
const KakaoStrategy = require('passport-kakao').Strategy;

const User = require('../models/user');

module.exports = () => {
  passport.use(new KakaoStrategy({
    clientID: process.env.KAKAO_ID,
    callbackURL: '/auth/kakao/callback',
  }, async (accessToken, refreshToken, profile, done) => {
    console.log('kakao profile', profile);
    try {
      const exUser = await User.findOne({
        where: { snsId: profile.id, provider: 'kakao' },
      });
      if (exUser) {
        done(null, exUser);
      } else {
        const newUser = await User.create({
          email: profile._json?.kakao_account?.email,
          nick: profile.displayName,
          snsId: profile.id,
          provider: 'kakao',
        });
        done(null, newUser);
      }
    } catch (error) {
      console.error(error);
      done(error);
    }
  }));
};

 

 

 

 

5. JWT 유틸리티 (utils/auth.js)

JWT 토큰 생성 및 검증을 담당하는 유틸리티 함수를 정의합니다.

 

const { sign, verify, decode } = require('jsonwebtoken');
const { compare } = require('bcrypt');
const RefreshToken = require('../models/refreshTokens'); // Sequelize 모델 임포트
const User = require('../models/user'); // User 모델 임포트 (User 모델이 있다고 가정)
const { NotAuthError } = require('./errors');

// 암호화 키를 설정합니다.
const KEY = process.env.JWT_SECRET_KEY;
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET;
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET;

const ACCESS_TOKEN_EXPIRATION = '1h'; // 1시간
const REFRESH_TOKEN_EXPIRATION = 14 * 24 * 60 * 60; // 14일 (초 단위)

// 1. 입력받은 비밀번호와 저장된 비밀번호를 비교하는 함수
function isValidPassword(password, storedPassword) {
  return compare(password, storedPassword);
}

// 2. 접근 토큰 생성
function createAccessToken(userId, userType) {
  console.log("2. 접근 토큰 생성 :", userId,userType, ACCESS_TOKEN_SECRET, ACCESS_TOKEN_EXPIRATION);

  const accessToken = sign({ userId,userType }, ACCESS_TOKEN_SECRET, { expiresIn: ACCESS_TOKEN_EXPIRATION });
  const decodedToken = decode(accessToken);

  return {
    accessToken,
    accessTokenExpires: decodedToken.exp,
  };
}

// 3. 갱신 토큰 생성
function createRefreshToken(userId,userType) {
  const refreshToken = sign({ userId ,userType}, REFRESH_TOKEN_SECRET, { expiresIn: REFRESH_TOKEN_EXPIRATION });
  const decodedToken = decode(refreshToken);

  return {
    refreshToken,
    refreshTokenExpires: decodedToken.exp,
  };
}

// 4. 접근 토큰 유효성 검사
function validateAccessToken(token) {
  return verify(token, ACCESS_TOKEN_SECRET);
}

// 5. 갱신 토큰 유효성 검사
function validateRefreshToken(token) {
  return verify(token, REFRESH_TOKEN_SECRET);
}

// 6. 갱신 토큰을 MySQL에 저장하는 함수
async function storeRefreshToken(refreshToken, userId) {
  try {
    const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRATION * 1000); // 만료 시간 계산
    const existingToken = await RefreshToken.findOne({ where: { userId } });

    if (existingToken) {
      // 기존 토큰 업데이트
      existingToken.refreshToken = refreshToken;
      existingToken.expiresAt = expiresAt;
      await existingToken.save();
    } else {
      // 새 토큰 생성
      const newToken = await RefreshToken.create({
        userId: userId,
        refreshToken,
        expiresAt,
      });
    }

    console.log("6. 갱신 토큰을 MySQL에 저장하는 함수 :", userId, refreshToken, expiresAt);
  } catch (error) {
    console.error('Failed to store refresh token:', error);
    throw new Error('Failed to store refresh token');
  }
}

// 7. 저장된 MySQL에서 갱신 토큰 가져오는 함수
async function getStoredRefreshToken(userId) {
  try {
    const tokenData = await RefreshToken.findOne({ where: { userId } });
    return tokenData ? tokenData.refreshToken : null;
  } catch (error) {
    console.error('Failed to retrieve refresh token:', error);
    throw new Error('Failed to retrieve refresh token');
  }
}

// 8. 인증 미들웨어 함수
async function checkAuthMiddleware(req, res, next) {
  console.log("미들웨어 인증 처리 :");

  if (req.method === 'OPTIONS') {
    return next();
  }
  if (!req.headers.authorization) {
    console.log("접근 권한이 없습니다.-1 :");
    return next(new NotAuthError('접근 권한이 없습니다.'));
  }

  const authFragments = req.headers.authorization.split(' ');

  if (authFragments.length !== 2) {
    console.log("not-authenticated-2 :");
    return next(new NotAuthError('접근 권한이 없습니다.'));
  }

  const authToken = authFragments[1];
  try {
    const validatedToken = validateAccessToken(authToken);
    req.token = validatedToken;
    req.userId = validatedToken.userId;
    req.user = {};  // req.user 객체 초기화
    req.user.userType = validatedToken.userType;
    req.userType = validatedToken.userType;
  } catch (error) {
    console.log("not-authenticated-3:", error.message);
    if (error.message === 'jwt expired') {
      console.log("접근 토큰 인증 만료");
      return res.status(403).json({ message: 'ACCESS_TOKEN_EXPIRED' });
    }
    return next(new NotAuthError('접근 권한이 없습니다.'));
  }
  next();
}

// 9. 로그아웃 시 갱신 토큰 삭제
async function deleteRefreshToken(userId) {
  try {
    await RefreshToken.destroy({ where: { userId } });
    console.log(`갱신 토큰 삭제 완료: ${userId}`);
  } catch (error) {
    console.error('Failed to delete refresh token:', error);
    throw new Error('Failed to delete refresh token');
  }
}

// 10. 사용자 등록 시 (회원가입)
async function registerUser(userData) {
  try {
    const { userId, password, nick } = userData;
    const userExists = await User.findOne({ where: { userId } });

    if (userExists) {
      throw new Error('이미 존재하는 아이디입니다.');
    }

    // 비밀번호 암호화 후 사용자 생성
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = await User.create({
      userId,
      password: hashedPassword,
      nick, // 추가된 닉네임
    });

    return newUser;
  } catch (error) {
    console.error('회원가입 실패:', error);
    throw error;
  }
}

module.exports = {
  createAccessToken,
  createRefreshToken,
  validateAccessToken,
  validateRefreshToken,
  isValidPassword,
  storeRefreshToken,
  getStoredRefreshToken,
  checkAuthMiddleware,
  deleteRefreshToken,
  registerUser, // 회원가입 처리 함수 추가
};

 

errors.js

class NotAuthError {
  constructor(message) {
    this.message = message;
    this.status = 401; // 인증 실패를 나타내는 HTTP 상태 코드
  }
}

exports.NotAuthError = NotAuthError;

 

 

 

6. 인증 컨트롤(routes/auth.js)

/api/auth에서 로그인 및 JWT 토큰 발급을 처리합니다

passport/localStrategy.js 와 연결처리 되어 로그인 인증처리 됩니다.

const bcrypt = require('bcrypt');
const passport = require('passport');
const User = require('../models/user');
const { createJSONToken, isValidPassword, createAccessToken, createRefreshToken, storeRefreshToken, deleteRefreshToken, validateRefreshToken } = require("../util/auth"); 


// 회원가입!
exports.join = async (req, res, next) => {
  //console.log("회원가입 요청 데이터: ", req.body);
  const { userId, nickname, password, userType, address } = req.body;

  if (!userId || !nickname || !password || !address || !userType) {
    return res.status(400).send('모든 필드를 입력해주세요.');
  }

  try {
    console.log(req.body);
    const exUser = await User.findOne({ where: { userId } });
    // 로그인 - 일단 이 아이디로 가입한 유저가 있는지 찾기
    if (exUser) {
      return res.status(400).json({ responseMessage: '이미 존재하는 사용자입니다.' });
    }

    let time=req.body.time;
    if(!time) {
      time=null;
    }


    const hash = await bcrypt.hash(password, 12); // bcrypt 비밀번호 암호화
    await User.create({
      userId,
      nick: nickname,
      password: hash,
      userType,
      address: JSON.stringify(address),
      time: time 
    });
    //return res.redirect('/');
    return res.status(201).json({ responseMessage: '회원가입 성공' });
  } catch (error) {
    console.error(error);
    return next(error);
  }
}

// 로그인! 
exports.login =async (req, res, next) => {
  passport.authenticate('local',async (authError, user, info) => {

    if (authError) return res.status(500).json({ responseMessage: '서버 에러가 발생했습니다.' });
    if (!user) return res.status(400).json({ responseMessage: info.message });

    try {
      const userId = user.userId;
      const { accessToken: newAccessToken } = createAccessToken(user.id,  user.userType);
      const { refreshToken: newRefreshToken } = createRefreshToken(user.id, user.userType);
      await storeRefreshToken(newRefreshToken, user.id);

      let parsedAddress = {};
      if (user.address) parsedAddress = JSON.parse(user.address);

      return res.status(200).json({
        id: user.id,
        userId,
        nickname: user.nick,
        address: parsedAddress,
        userType: user.userType,
        access_token: newAccessToken,
        refresh_token: newRefreshToken
      });
    } catch (error) {
      console.error("로그인 처리 에러:", error);
      return res.status(500).json({ responseMessage: '서버 에러가 발생했습니다.' });
    }
  })(req, res, next);
};






//갱신 토큰 발급처리
exports.refresh = async (req, res, next) => {
  const { refreshToken } = req.body;
  //console.log("refresh token: " + refreshToken);

  if (!refreshToken) {
    return res.status(400).json({
      responseMessage: 'refresh token 값이 존재하지 않습니다.',
    });
  }

  // Refresh Token 유효성 검사
  const refreshTokenValid = validateRefreshToken(refreshToken);
  if (!refreshTokenValid) {
    return res.status(400).json({
      responseMessage: '갱신 토큰값이 유효하지 않습니다.',
    });
  }

  try {
    console.log("갱신 토큰 유효합니다.", refreshTokenValid);

    // `validateRefreshToken`이 반환한 데이터 구조 확인
    const { userId, userType } = refreshTokenValid;

    // Access Token 생성
    const { accessToken: newAccessToken } = createAccessToken(userId, userType);

    // Refresh Token 생성
    const { refreshToken: newRefreshToken } = createRefreshToken(userId, userType);

    // 기존 Refresh Token 삭제
    await deleteRefreshToken(userId);

    // 새로운 Refresh Token 저장
    await storeRefreshToken(newRefreshToken, userId);

    // 성공 응답
    return res.status(200).json({
      id: userId,
      access_token: newAccessToken,
      refresh_token: newRefreshToken,
    });
  } catch (error) {
    console.error("갱신 토큰 처리 중 에러:", error);

    // 서버 에러 응답
    return res.status(500).json({
      responseMessage: '서버에서 에러가 발생했습니다. 잠시 후 다시 시도해주세요.',
    });
  }
};






exports.logout = async(req, res) => {
  req.logout(async () => {  
    const userId = req.query.userId;
    const exUser = await User.findOne({ where: { userId } });
    //console.log("로그아웃 시 갱신 토큰 삭제",exUser);    
    await deleteRefreshToken(exUser.id);
    return res.status(200).json({responseMessage: 'success'});

  });
};

 

 

 

 

 

 

 

2.프론트엔드

 

1. React 프로젝트 준비 및 필수 라이브러리 설치

 

1.1. 프로젝트 초기화

리액트 프로젝트를 생성하려면 create-react-app을 사용합니다.

 

npx create-react-app macaronics-clone
cd macaronics-clone

 

 

1.2. 의존성 설치

package.json에 나열된 라이브러리를 프로젝트에 설치합니다.

npm install axios react-daum-postcode react-icons react-query react-router-dom react-select recoil seamless-scroll-polyfill styled-components


 

 

1.3. package.json 파일 예시

다음은 package.json의 기본 구조입니다.

{
  "name": "macaronics-clone",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^1.4.0",
    "react": "^18.2.0",
    "react-daum-postcode": "^3.1.3",
    "react-dom": "^18.2.0",
    "react-icons": "^4.8.0",
    "react-query": "^3.39.3",
    "react-router-dom": "^6.11.1",
    "react-select": "^5.7.3",
    "recoil": "^0.7.7",
    "seamless-scroll-polyfill": "^2.3.4",
    "styled-components": "^6.0.0-rc.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test"
  }
}

 

 

 

2. Axios 인스턴스 구성

2.1. Axios 인스턴스 설정

백엔드와의 HTTP 통신을 쉽게 하기 위해 Axios 인스턴스를 생성합니다.

 

import axios from "axios";

// Axios 인스턴스 생성
export const instance = axios.create({
  baseURL: process.env.REACT_APP_SERVER_URL || "http://localhost:8001", // 백엔드 주소
  withCredentials: true,
  headers: {
    "Content-Type": "application/json",
  },
});

// Request 인터셉터
instance.interceptors.request.use(
  (config) => {
    const accessToken = localStorage.getItem("access_token");
    const refreshToken = localStorage.getItem("refresh_token");

    if (accessToken && refreshToken) {
      config.headers["Access_token"] = `${accessToken}`;
      config.headers["Refresh_token"] = `${refreshToken}`;
    }

    if (accessToken) {
      config.headers.Authorization = `Bearer ${accessToken}`;
    }

    return config;
  },
  (error) => Promise.reject(error)
);

// Response 인터셉터
instance.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    if (error.response?.status === 403 && !originalRequest._retry) {
      originalRequest._retry = true;

      const refreshToken = localStorage.getItem("refresh_token");
      if (!refreshToken) {
        localStorage.clear();
        window.location.href = "/Intro";
        return Promise.reject(error);
      }

      try {
        const response = await fetch(
          `${process.env.REACT_APP_SERVER_URL}/api/auth/refresh`,
          {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ refreshToken }),
          }
        );

        if (response.ok) {
          const data = await response.json();
          localStorage.setItem("access_token", data.access_token);
          localStorage.setItem("refresh_token", data.refresh_token);

          return instance(originalRequest);
        } else {
          localStorage.clear();
          window.location.href = "/Intro";
          return Promise.reject(error);
        }
      } catch (err) {
        return Promise.reject(err);
      }
    }

    return Promise.reject(error);
  }
);

 

 

3. 로그인 및 로그아웃 처리

3.1. 로그인 처리

 

export const login = async (email, password) => {
  try {
    const response = await instance.post("/auth/login", { email, password });

    localStorage.setItem("access_token", response.data.access_token);
    localStorage.setItem("refresh_token", response.data.refresh_token);
    localStorage.setItem("user", response.data.refresh_token);

    return response.data;
  } catch (error) {
    console.error("로그인 실패:", error);
    throw error;
  }
};

 

 

 

3.2. 로그아웃 처리

 

export const userLogout = () => {
  const userId=localStorage.getItem("userId");
  
  if(!userId || userId===null){
    localStorage.clear();
    //alert('로그아웃 성공');
    return  null;
  }

  return instance.get(`/api/auth/logout?userId=${userId}`)
  .then((response) => {  
    localStorage.clear();    
    //alert('로그아웃 성공');
    return response;
  }).catch((error) => {
    
      localStorage.clear();    
    if (error.response && error.response.data) {
      alert(error.response.data.responseMessage);
    } else {
      alert('로그아웃 중 에러가 발생했습니다.');
    }
    return Promise.reject(error);
  });
};

 

 

 

 

4. CRUD 처리 예시

4.1. 데이터 조회

 

export const getData = async (endpoint) => {
  try {
    const response = await instance.get(endpoint);
    return response.data;
  } catch (error) {
    console.error("데이터 조회 실패:", error);
    throw error;
  }
};

 

 

4.2. 데이터 생성

export const createData = async (endpoint, payload) => {
  try {
    const response = await instance.post(endpoint, payload);
    return response.data;
  } catch (error) {
    console.error("데이터 생성 실패:", error);
    throw error;
  }
};

 

 

4.3. 데이터 수정

export const updateData = async (endpoint, payload) => {
  try {
    const response = await instance.put(endpoint, payload);
    return response.data;
  } catch (error) {
    console.error("데이터 수정 실패:", error);
    throw error;
  }
};

 

 

4.4. 데이터 삭제

export const deleteData = async (endpoint) => {
  try {
    await instance.delete(endpoint);
    console.log("데이터 삭제 성공");
  } catch (error) {
    console.error("데이터 삭제 실패:", error);
    throw error;
  }
};

 

 

 

5. 실제 예제: React 컴포넌트와 Axios 통합

 

5.1. 사용자 리스트 조회

import React, { useEffect, useState } from "react";
import { getData } from "../api/axiosInstance";

const UserList = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const data = await getData("/users");
        setUsers(data);
      } catch (error) {
        console.error("사용자 목록 조회 오류:", error);
      }
    };

    fetchUsers();
  }, []);

  return (
    <div>
      <h1>사용자 목록</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};

export default UserList;

 

위와 같은 방식으로 React에서 Axios를 사용하여 백엔드 API와 통신하는 방법을 구현할 수 있습니다.

interceptors를 활용하면 반복적인 요청/응답 처리를 효율적으로 관리할 수 있습니다.

 

 

 

 

 

리액트

 

about author

PHRASE

Level 60  라이트

왕후 귀족을 섬기지 않고 자기의 지조를 고결하게 지닌다. 정물에 시달리지 않고 깨끗이 자기 뜻대로 살아가니 그 뜻을 모범으로 할 만하다. -역경

댓글 ( 0)

댓글 남기기

작성