궁금한게 많은 열아홉
article thumbnail
🚨 본 글에서 프론트엔드는 Nex.js + Typescript, 백엔드는 Node.js + Express를 나타냅니다. 해결 방법은 맨 아래 '내가 해결한 과정' 부분 부터 봐주세요. :)

 

사용자 API를 구축한 후 테스트를 하기 위해 임시로 코드를 작성하고 로그인이 되는 지 확인하기 위해 API 연결을 해줬다.

하지만 에러가 발생하는데 ... 

 

Uncaught (in promise) SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON

어떤 에러인지 확인해보니 반환되는 값이 JSON이어야 하지만, HTML로 받아들여 생긴 오류였다.

 

프론트엔드 코드

import { useRouter } from "next/router";
import React, { FormEvent, useState } from "react";

export default function LoginWrap() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const router = useRouter();

  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
  
    e.preventDefault();
    
    const response = await fetch("http://127.0.0.1:5000/login", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email, password }),
    });
    // console.log(response);
    
    const data = await response.json();
    console.log(data);
    
    if (data.success) {
      // JWT 토큰을 클라이언트에 저장하는 코드 작성
      console.log("success");
      router.push("/");
    } else {
      // 로그인 실패 처리
      console.log("Sorry");
    }
  };

  return (
   <div>
          <form onSubmit={handleSubmit}>
            <input
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
            />
            <br />
            <input
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              required
            />
            <br />
            <button type="submit">login</button>
          </form>
    </div>
  );
}

 

백엔드 코드 

나 같은 경우 코드를 효율적으로 관리하기 위해 로그인 코드를 라우터로 따로 분리했다.

// login.js
const express = require("express");
const router = express.Router();

const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");

const dotenv = require("dotenv");
dotenv.config();

const mysql = require("mysql");
const dbconfig = require("../config/db.js");
const connection = mysql.createConnection(dbconfig);

// jwt 시크릿 키 설정
const JWT_SECRET = process.env.JWT_SECRET;

router.post("/", (req, res) => {
  const { email, password } = req.body;
  
  if (!email) {
    return res.status(400).json({ error: "이메일을 입력해주세요." });
  }

  connection.query(
    "SELECT * FROM users WHERE email = ?",
    [email],
    (error, results) => {
      if (error) {
        return res.status(500).json({
          error: "서버 오류 입니다.",
        });
      }
      if (results.length === 0) {
        return res.status(401).json({
          error: "이메일 혹은 비밀번호가 올바르지 않습니다.",
        });
      }

      const user = results[0];

      bcrypt.compare(password, user.password, (error, isMatch) => {
        if (error) {
          return res.status(500).json({
            error: "서버 오류 입니다.",
          });
        }

        if (!isMatch) {
          // 비밀번호가 일치하지 않을 경우
          return res.status(401).json({
            error: "이메일 또는 비밀번호가 올바르지 않습니다.",
          });
        }

        // JWT 발급
        const token = jwt.sign({ email }, JWT_SECRET);

        res.json({
          success: true,
          token,
        });
      });
    }
  );
});

module.exports = router;

 

// index.js
const express = require("express");
const app = express();

const port = 5001;

const loginRouter = require("./routes/login.js");

app.use(express.json());

// login.js
app.use('/login', loginRouter);


app.get("/", (req, res) => {
  res.send("This is node js server");
});

app.listen(port, () => {
  console.log(`🚀 Server start : http://localhost:${port}`);
});

정말 감사하게도 때마침 JS스터디가 일찍 끝나서 스터디원 분들도 관련된 문제에 대해 같이 고민해주셨다.

내가 해결한 과정

1. postman에서 정상 동작하는지 확인

내가 만든 백엔드 코드에 문제가 있을 수도 있다고 생각해서 postman으로 테스트를 해보았다.

하지만 요청을 보냈을 때 정상적으로 성공 여부와 토큰이 반환되는 것을 보고 백엔드 코드의 문제가 아니라는 것을 알았다.

 

2. 요청을 정상적으로 하는지 확인

올바른 주소로  요청을 하고 있었다. 하지만 에러는 그대로...

 

3. 해결하기 - Proxy 설정

이외에도 여러 시도를 해보다가 한 스터디원 분께서 Proxy 설정이 안되어있어서 그런것 같다고 얘기해주시면서 이 글을 보여주셨다.

글을 요약하면, /api로 시작되는 경로에 접근을 했을 때 내가 만든 API로 접근 할 수 있도록 해주는 방법이었다.

 

<과정>

1. 우선 프론트파일의 package.json에 Proxy 코드를 넣어준다.

"proxy": "http://localhost:5001", // port번호는 자신이 설정한 번호에 맞게 넣어줘야 한다.

위치 참고

2. CORS 허용 설정을 해준다.

백엔드 파일의 index.js 파일에 CORS설정 허용 코드를 넣어준다

// CORS 하용 설정하기.
app.use((req, res, next) => {
  res.setHeader("Content-Type", "application/json");
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

 3. Next.js를 사용하기 때문에 프론트에 next.config.json파일 생성(혹은 있다면 수정) 설정을 해준다.

/** @type {import('next').NextConfig} */
const { createProxyMiddleware } = require('http-proxy-middleware');

const nextConfig = {
  reactStrictMode: true,
  async rewrites() {
    return [
      {
      	// api 경로로 오는 것들을 허용해주기!
        source: '/api/:path*',
        destination: 'http://localhost:5001/api/:path*',
      },
    ];
  },
  async middleware() {
    const middleware = [];

    middleware.push(
      createProxyMiddleware('/api', {
        target: 'http://localhost:5000',
        changeOrigin: true,
      })
    );

    return middleware;
  },
};


module.exports = nextConfig

4. 프론트의 fetch부분에 경로를 '/api/login'으로, 백엔드의 index.js를 '/api/login'으로 바꿔준다.

프론트엔드 코드

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    // 경로를 바꿔준다! /api/login
    const response = await fetch("/api/login", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email, password }),
    });
    // console.log(response);

    const data = await response.json();
    console.log(data);

    if (data.success) {
      // JWT 토큰을 클라이언트에 저장하는 코드 작성
      localStorage.setItem("token", data.token);
      console.log("success");
      router.push("/");
    } else {
      // 로그인 실패 처리
      console.log("Sorry");
    }
  };

백엔드 코드 - index.js만 수정하면 된다!

const express = require("express");
const app = express();

const port = 5001;

const loginRouter = require("./routes/login.js");

app.use(express.json());

// 경로를 바꿔준다! /api/login
app.use('/api/login', loginRouter);

app.get("/", (req, res) => {
  res.send("This is node js server");
});


// CORS 하용 설정하기.
app.use((req, res, next) => {
  res.setHeader("Content-Type", "application/json");
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

app.listen(port,'0.0.0.0', () => {
  console.log(`🚀 Server start : http://localhost:${port}`);
});

결과

이제 다시 실행해보면 정상적으로 동작하는 것을 확인 할 수 있을 것이다 :)

로그인 화면
메인 화면의 프로필 일부분

 

성공-

 

ps. 전체 코드를 확인하고 싶으시다면 참고해 주세요. 참고로 프론트엔드가 백엔드 코스프레 하는 중이라 백엔드 하신분들을 따라가지는 못한다는 점 ... ^^ 고수분들 시간 되실때 오셔서 훈수 좀...

 

GitHub - Stay-Mate/Stay-Mate-Server: 프론트가 백엔드 코스프레 하는 즁

프론트가 백엔드 코스프레 하는 즁. Contribute to Stay-Mate/Stay-Mate-Server development by creating an account on GitHub.

github.com

 

profile

궁금한게 많은 열아홉

@jjin502

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!