강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

hib4888님의 프로필 이미지
hib4888

작성한 질문수

[리뉴얼] React로 NodeBird SNS 만들기

CORS 문제 해결하기

user router가 실행되지 않는 문제 질문드리겠습니다.

해결된 질문

작성

·

397

0

 

아래와 같이 saga도 정상적으로 동작하며, 회원가입 기능, DB에 user 정보 저장, 중복ID체크 오류 모두 정상적으로 동작합니다.

Recipe.io - Chrome 2022-09-10 오후 3_03_00 (3).pngRecipe.io - Chrome 2022-09-10 오후 3_03_27.pngRecipe.io - Chrome 2022-09-10 오후 3_03_32.pngMySQL Workbench 2022-09-10 오후 3_04_50 (2).png회원가입 _ Recipe.io - Chrome 2022-09-10 오후 3_16_33.png근데 network탭을 확인해보니 아래와 같이 user router가 실행되지 않습니다.

또한 cors문제도 해결했는데 Resopnse Headers탭에는 Access-Control-Allow-Origin이 적용되지 않아 질문드립니다.

Recipe.io - Chrome 2022-09-10 오후 3_06_14.pngRecipe.io - Chrome 2022-09-10 오후 3_06_22.png

참고할 수 있는 코드 첨부하겠습니다.

바쁘시겠지만 답변해주시면 정말 감사하겠습니다.

 

<참고 코드>

signupform

import React, { useCallback, useEffect } from 'react';
import { Button, Checkbox, Form, Input, message } from 'antd';
import { useSelector, useDispatch } from 'react-redux';
import Router from 'next/router';

import { formItemLayout, tailFormItemLayout } from './styles';
import { signupRequestAction } from '../../reducers/user';

const SignupForm = () => {
  const { signUpLoading, signUpDone, signUpError } = useSelector((state => state.user));
  const dispatch = useDispatch();
  const [form] = Form.useForm();    

  useEffect(() => {
    if (signUpDone) {
      Router.push('/');
      message.success('정상적으로 회원가입이 완료됬습니다.');
    }
  }, [signUpDone]);

  useEffect(() => {
    if (signUpError) {
      message.error(signUpError);
    }
  }, [signUpError]);

  const onSubmitForm = useCallback((value) => {
    console.log(value);
    dispatch(signupRequestAction(value));
  }, []);

  return (
    <section>
      <Form      
        {...formItemLayout}
        form={form}
        name="signup"
        onFinish={onSubmitForm}      
        scrollToFirstError
      >
        <Form.Item
          name="email"
          label="E-MAIL"
          rules={[
            {
              type: 'email',
              message: 'E-mail형식이 올바르지 않습니다.',
            },
            {
              required: true,
              message: 'E-mail을 입력하세요.',
            },
          ]}
        >
          <Input />
        </Form.Item>

        <Form.Item
          name="password"
          label="Password"
          rules={[
            {
              required: true,
              message: '비밀번호를 입력하세요.',
            },
          ]}
          hasFeedback
        >
          <Input.Password />
        </Form.Item>

        <Form.Item
          name="confirm-password"
          label="Confirm Password"
          dependencies={['password']}
          hasFeedback
          rules={[
            {
              required: true,
              message: '비밀번호를 입력하세요.',
            },
            ({ getFieldValue }) => ({
              validator(_, value) {
                if (!value || getFieldValue('password') === value) {
                  return Promise.resolve();
                }

                return Promise.reject(new Error('비밀번호가 일치하지 않습니다.'));
              },
            }),
          ]}
        >
          <Input.Password />
        </Form.Item>

        <Form.Item
          name="nickname"
          label="Nickname"        
          rules={[
            {
              type: 'text',
            },
            {
              required: true,
              message: '닉네임을 입력하세요.',            
            },
          ]}
        >
          <Input />
        </Form.Item>    
        
        <Form.Item
          name="agreement"
          valuePropName="checked"
          rules={[
            {
              validator: (_, value) =>
                value ? Promise.resolve() : Promise.reject(new Error('약관에 동의해 주세요.')),
            },
          ]}
          {...tailFormItemLayout}
        >
          <Checkbox>
            이용약관에 모두 동의합니다.
          </Checkbox>
        </Form.Item>
        <Form.Item {...tailFormItemLayout}>
          <Button type="primary" htmlType="submit" loading={signUpLoading} >회원가입</Button>
        </Form.Item>
      </Form>
    </section>
  );
};

export default SignupForm;

reducers/user.js

import produce from 'immer';
import { dummyPost } from './post';

export const initialState = {
  me: null,
  signUpData: {},
  loginData: {},  
  logInLoading: false,
  logInDone: false,
  logInError: null,
  logOutLoading: false,
  logOutDone: false,
  logOutError: null,  
  signUpLoading: false,
  signUpDone: false,
  signUpError: null,
  nicknameEditLoading: false,
  nicknameEditDone: false,
  nicknameEditError: null,
};

const dummyUser = (data) => ({
  ...data, // email, password
  nickname: 'Mirrer',
  id: 2,
  Posts: [],
  Scrap: [],
  Board: [],
});

export const LOG_IN_REQUEST = 'LOG_IN_REQUEST';
export const LOG_IN_SUCCESS = 'LOG_IN_SUCCESS';
export const LOG_IN_FAILURE = 'LOG_IN_FAILURE';

export const LOG_OUT_REQUEST = 'LOG_OUT_REQUEST';
export const LOG_OUT_SUCCESS = 'LOG_OUT_SUCCESS';
export const LOG_OUT_FAILURE = 'LOG_OUT_FAILURE';

export const SIGN_UP_REQUEST = 'SIGN_UP_REQUEST';
export const SIGN_UP_SUCCESS = 'SIGN_UP_SUCCESS';
export const SIGN_UP_FAILURE = 'SIGN_UP_FAILURE';

export const NICKNAME_EDIT_REQUEST = 'NICKNAME_EDIT_REQUEST';
export const NICKNAME_EDIT_SUCCESS = 'NICKNAME_EDIT_SUCCESS';
export const NICKNAME_EDIT_FAILURE = 'NICKNAME_EDIT_FAILURE';

export const BOARD_ADD_POST_TO_ME = 'BOARD_ADD_POST_TO_ME';
export const BOARD_REMOVE_POST_OF_ME = 'BOARD_REMOVE_POST_OF_ME';

export const SCRAP_ADD_POST_TO_ME = 'SCRAP_ADD_POST_TO_ME';
export const SCRAP_REMOVE_POST_OF_ME = 'SCRAP_REMOVE_POST_OF_ME';

export const loginRequestAction = (data) => {
  return {
    type: LOG_IN_REQUEST,
    data
  }
};

export const signupRequestAction = (data) => {  
  return {
    type: SIGN_UP_REQUEST,    
    data
  }
};

export const nicknameEditRequestAction = (data) => {  
  return {
    type: NICKNAME_EDIT_REQUEST,    
    data
  }
};

const reducer = (state = initialState, action) => {
  return produce(state, (draft) => {
    switch (action.type) {
      case LOG_IN_REQUEST:
        draft.logInLoading = true;
        draft.logInDone = false;
        draft.logInError = null;
        break;
      case LOG_IN_SUCCESS:
        draft.logInLoading = false;
        draft.logInDone = true;
        draft.me = dummyUser(action.data);
        break;
      case LOG_IN_FAILURE:
        draft.logInLoading = false;
        draft.logInError = action.error;
        break;
      case LOG_OUT_REQUEST:
        draft.logOutLoading = true;
        draft.logOutDone = false;
        draft.logOutError = null;
        break;
      case LOG_OUT_SUCCESS:
        draft.logOutLoading = false;
        draft.logOutDone = true;
        draft.me = null;
        break;
      case LOG_OUT_FAILURE:
        draft.logOutLoading = false;
        draft.logOutError = action.error;
        break;
      case SIGN_UP_REQUEST:
        draft.signUpLoading = true;
        draft.signUpDone = false;
        draft.signUpError = null;
        break;        
      case SIGN_UP_SUCCESS:
        draft.signUpLoading = false;
        draft.signUpDone = true;
        break;        
      case SIGN_UP_FAILURE:
        draft.signUpLoading = false;
        draft.signUpError = action.error;
        break;
      case NICKNAME_EDIT_REQUEST:
        draft.nicknameEditLoading = true;
        draft.nicknameEditDone = false;
        draft.nicknameEditError = null;
        break;      
      case NICKNAME_EDIT_SUCCESS:
        draft.me.nickname = action.data.nicknameEdit;
        draft.nicknameEditLoading = false;
        draft.nicknameEditDone = true;
        break;        
      case NICKNAME_EDIT_FAILURE:
        draft.nicknameEditLoading = false;
        draft.nicknameEditError = action.error;
        break;      
      case SCRAP_ADD_POST_TO_ME:
        draft.me.Scrap.unshift(dummyPost(action.data));
        break;
      case SCRAP_REMOVE_POST_OF_ME:
        draft.me.Scrap = draft.me.Scrap.filter((v) => v.id !== action.data);
        break;
      case BOARD_ADD_POST_TO_ME:
        draft.me.Board.unshift(dummyPost(action.data));
        break;
      case BOARD_REMOVE_POST_OF_ME:
        draft.me.Board = draft.me.Board.filter((v) => v.id !== action.data);
        break;
      default:
        break;
    }
  });
};

export default reducer;

 

sagas/user.js

import { all, fork, delay, put, takeLatest, call } from 'redux-saga/effects';
import axios from 'axios';
import { 
  LOG_IN_REQUEST, LOG_IN_SUCCESS, LOG_IN_FAILURE, 
  LOG_OUT_REQUEST, LOG_OUT_SUCCESS, LOG_OUT_FAILURE, 
  SIGN_UP_REQUEST, SIGN_UP_SUCCESS, SIGN_UP_FAILURE,
  NICKNAME_EDIT_REQUEST, NICKNAME_EDIT_SUCCESS, NICKNAME_EDIT_FAILURE,
} from '../reducers/user';

// function logInAPI(data) {
//   return axios.post('/api/login', data);
// }

function* logIn(action) {
  try {    
    // const result = yield call(logInAPI, action.data);        
    yield delay(1000);
    yield put({
      type: LOG_IN_SUCCESS,       
      data: action.data,
    })
  } catch(err) {
    yield put({
      type: LOG_IN_FAILURE,
      error: err.response.data
    })
  }  
}

// function logOutAPI() {
//   return axios.post('/api/logout');
// }

function* logOut() {
  try {  
    // const result = yield call(logOutAPI);
    yield delay(1000);
    yield put({
      type: LOG_OUT_SUCCESS,      
    })
  } catch(err) {
    yield put({ 
      type: LOG_OUT_FAILURE,
      error: err.response.data
    })
  }  
}

function signUpAPI(data) {
  console.log('signUpAPI 실행');
  return axios.post('/user', data);
}

function* signUp(action) {
  console.log('signUp 실행');
  try {  
    const result = yield call(signUpAPI, action.data);
    console.log(result);
    yield put({
      type: SIGN_UP_SUCCESS,      
    })
  } catch(err) {
    console.log(err);
    yield put({ 
      type: SIGN_UP_FAILURE,
      error: err.response.data
    })
  }  
}

// function NicknameEditAPI() {
//   return axios.post('/api/NicknameEdit');
// }

function* nicknameEdit(action) {
  try {  
    // const result = yield call(NicknameEditAPI);
    console.log(action.data);
    yield delay(1000);
    yield put({
      type: NICKNAME_EDIT_SUCCESS,
      data: action.data,
    })
  } catch(err) {
    yield put({ 
      type: NICKNAME_EDIT_FAILURE,
      error: err.response.data
    })
  }  
}

function* watchLogIn() {   
  yield takeLatest(LOG_IN_REQUEST, logIn);
}

function* watchLogOut() {  
  yield takeLatest(LOG_OUT_REQUEST, logOut);
}

function* watchSignUp() {  
  console.log('watchSignUp 실행');
  yield takeLatest(SIGN_UP_REQUEST, signUp);
}

function* watchNicknameEdit() {  
  yield takeLatest(NICKNAME_EDIT_REQUEST, nicknameEdit);
}

export default function* userSaga() {
  yield all([
    fork(watchLogIn),
    fork(watchLogOut),
    fork(watchSignUp),
    fork(watchNicknameEdit),
  ]);
}

 

back/app.js

const express = require('express');
const cors = require('cors');

const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const db = require('./models')
const app = express();

db.sequelize.sync()
  .then(() => {
    console.log('db 연결 성공');
  })
  .catch(console.error);

app.use(cors({
  origin: true,
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get('/', (req, res) => {
  res.send('Hello Express');
});

app.get('/api', (req, res) => {
  res.send('Hello API');
});

app.get('/api/posts', (req, res) => {
  res.json([
    { id: 1, content: 'Hello1'},
    { id: 2, content: 'Hello2'},
    { id: 3, content: 'Hello3'},
  ])
});

app.use('/post', postRouter);
app.use('/user', userRouter);

app.listen(3065, () => {
  console.log('서버 실행 중');
});

 

back/routes/user.js

const express = require('express');
const bcrypt = require('bcrypt');
const { User } = require('../models');

const router = express.Router();

router.post('/', async(req, res, next) => {  
  try {
    const exUser = await User.findOne({
      where: {
        email: req.body.email,
      }
    });
    if (exUser) {
      return res.status(403).send('이미 사용중인 아이디입니다.');
    }

    const hashedPassword = await bcrypt.hash(req.body.password, 10);
    await User.create({
      email: req.body.email,
      password: hashedPassword,
      nickname: req.body.nickname,
    });
    res.status(200).send('OK');
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

 

back/models/user.js

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

module.exports = class User extends Model {
  static init(sequelize) {
    return super.init({
      email: {
        type: DataTypes.STRING(30),
        allowNull: false,
        unique: true,
      },
      nickname: {
        type: DataTypes.STRING(30),
        allowNull: false,
      },
      password: {
        type: DataTypes.STRING(100),
        allowNull: false,
      },
    }, {
      modelName: 'User',
      tableName: 'users',
      charset: 'utf8',
      collate: 'utf8_general_ci',
      sequelize,
    });
  }
  static associate(db) {
    db.User.hasMany(db.Post);
    db.User.hasMany(db.Comment);
    db.User.belongsToMany(db.Post, { through: 'Like', as: 'Liked' })    
  }
};

 

 

답변 1

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

그러니까 에러가 뭔가요?? 콘솔에 있는 에러를 해결하셔야할것같습니다.

hib4888님의 프로필 이미지
hib4888
질문자

명절인데도 빠른 답변 정말 감사합니다!!

첫 빌드시 콘솔에는 다음과 같은 에러가뜨는데 해당 에러가

user router실행되지 않는 문제랑 연관이 있는걸까요?

image

hib4888님의 프로필 이미지
hib4888
질문자

회원가입 이후에는 해당 콘솔에러가 출력되지 않고 정상적으로 기능이 동작합니다

다만 질문글의 사진처럼 network탭에서만 user router가 실행되지 않습니다

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

이해가 안 가는 것이 user router라는 게 뭔가요? 왜 user router가 실행되야한다고 생각하시는 건가요...??

hib4888님의 프로필 이미지
hib4888
질문자

해당 강의에서 회원가입을 구현하신 뒤 회원가입을 수행하면 네트워크탭에 name: user, method: post가 표시되는데, 제 네트워크탭에서는 아무런 표시가안되고 있습니다.

표시안되는이유가 routes/user에서 작성한 post요청이 정상적으로 안되서 그런건지 그 이유에 대해 궁금해서 질문드렸습니다.

router.post('/', async(req, res, next) => {  
  try {
    const exUser = await User.findOne({
      where: {
        email: req.body.email,
      }
    });
    if (exUser) {
      return res.status(403).send('이미 사용중인 아이디입니다.');
    }

    const hashedPassword = await bcrypt.hash(req.body.password, 10);
    await User.create({
      email: req.body.email,
      password: hashedPassword,
      nickname: req.body.nickname,
    });
    res.status(200).send('OK');
  } catch (error) {
    console.error(error);
    next(error);
  }
});

 

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

지금 개발자도구 네트워크탭에서 필터가 걸려있는것 아닌가요? 잘 보시면 56개 요청 중 10개만 필터로 보고 계십니다.

hib4888님의 프로필 이미지
hib4888
질문자

정상적으로 실행되고 있었네요.. 진짜 바보같은 질문을 드렸습니다 바쁘신데 죄송합니다 ㅜㅜ

추가로 passport login을 구현해서 실행했는데 로그인, 회원가입시 둘다 아래와 같은 서버 에러가 발생하는데 어떤 이유때문에 발생했는지 알 수 있을까요?

구글링으로 계속 찾고있는데 답을 찾기힘들어서 질문드립니다 ㅜㅜ

image

image

image

image

<관련코드>

back/app.js

const express = require('express');
const cors = require('cors');
const passport = require('passport');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const dotenv = require('dotenv');

const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const db = require('./models')
const app = express();
const passportConfig = require('./passport');

dotenv.config();
db.sequelize.sync()
  .then(() => {
    console.log('db 연결 성공');
  })
  .catch(console.error);

passportConfig();

app.use(cors({
  origin: true,
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(passport.initialize());
app.use(passport.session({
  saveUninitialized: false,
  resave: false,
  secret: process.env.COOKIE_SECRET,  
}));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session());

app.use('/post', postRouter);
app.use('/user', userRouter);

app.listen(3065, () => {
  console.log('서버 실행 중');
});

 

routes/user.js

const express = require('express');
const bcrypt = require('bcrypt');
const passport = require('passport');

const { User } = require('../models');

const router = express.Router();

router.post('/login', (req, res, next) => {
  passport.authenticate('local', (err, user, info) => {
    if (err) {
      console.error(err);
      return next(err);
    }

    if (info) {
      return res.status(401).send(info.reason);
    }
    return req.logIn(user, async (loginErr) => {
      if (loginErr) {
        console.error(loginErr);
        return next(loginErr);
      }
    return res.status(200).json(user);
    });
  })(req, res, next);
}); 

module.exports = router;

 

passport/index.js

const passport = require('passport');
const local = require('./local');
const { User } = require('../models');

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

  passport.deserializeUser(async (id, done) => {
    try {
      const user = await User.findOne({
        where: { id }
      })
      done(null, user);
    } catch (error) {
      console.error(error);
      done(error);
    }
  });

  local();
};

 

passport/local.js

const passport = require('passport');
const { Strategy: LocalStrategy } = require('passport-local');
const bcrypt = require('bcrypt');
const { User } = require('../models');

module.exports = () => {
  passport.use(new LocalStrategy({
    usernameField: 'email',
    passwordField: 'password',
  }, async (email, password, done) => {
    try {
      const user = await User.findOne({
        where: { email }
      });

      if (!user) {
        return done(null, false, { reason: '존재하지 않는 이메일입니다!!' });
      }

      const result = await bcrypt.compare(password, user.password);
      if (result) {
        return done(null, user);
      }      
      return done(null, false, { reason: '비밀번호가 틀렸습니다.' })      
    } catch (error) {
      console.error(error);
      return done(error);
    }
  }));
};
hib4888님의 프로필 이미지
hib4888
질문자

back/app.js에서 오타가 발생해서 session이 미적용됬었네요

해결되었습니다 다른 답변들 정말 너무 감사합니다!!

hib4888님의 프로필 이미지
hib4888

작성한 질문수

질문하기