본문 바로가기
Web Study/Next.js

Next.js로 SNS x.com 클론코딩하기 - 9

by 쿠리의일상 2024. 2. 9.

섹션4 돌입

드디어 섹션3 기획문서와 디자인은 있지만 백엔드 개발자의 API가 없는 상황에서 프론트엔드가 개발하는 경우를 다뤘습니다. 다음 섹션인4는 백엔드 개발자와 협업을 한다는 전제하에 이루어지므로 백엔드 세팅이 필요하다고 합니다.

 

백엔드 세팅

전제 조건

  1.  node.js
  2.  PostgreSQL
  3. Memurai (윈도우에선 Redis 대용으로 사용됨/로그인 시 필요) https://www.memurai.com/?gclid=CjwKCAiAq4KuBhA6EiwArMAw1PfXLOrv-z6d3mQLJ5Oh9moFqgbksHBbD_b5Q8brrc_pkZe65TpYeRoC9soQAvD_BwE
  4. 백엔드 서버 깃헙 코드 다운로드 https://github.com/zerocho/nest-prisma

뭔가 부산스럽게 준비할게 많아보이지만 node는 이미 있을테고, PostgreSQL도 저는 이미 있는터라 3, 4번을 세팅해주었습니다. 받아준 백엔드 서버 코드를 적당한 위치에 설정해두고, npm i 로 관련 라이브러리들을 다운 받아줍니다. 설치 여부는 node_modules 폴더가 생기면 확인이 가능합니다.

 

매번 DBeaver 를 사용해서 pgAdmin 은 사용할 일이 곧잘 없었는데 pgAdmin 을 틀어줍니다. 

백엔드 서버를 연결해줘야 하므로 Object > Register > Server 로 접근합니다. 그전에 혹시 비밀번호를 묻는 창이 뜨면 기존에 PostgreSQL을 다운로드 받으실 때 지정해주신 postgres의 비밀번호를 입력해주면 됩니다.(MySQL의 root 계정이라 생각하면 됩니다)

서버를 등록한 다음 zcom 이라는 DB를 생성해주면 됩니다. 그리고 백엔드 서버 상의 .env 파일에 존재하는 주소가 동일하게 맞춰줘야 합니다.

DATABASE_URL="postgresql://postgres:비번@localhost:5432/zcom?schema=public"

 

그런 다음 백엔드측 서버의 터미널 상에서 npx prisma migrate dev 를 실행해주면 됩니다. 이후 zcom db에 접근하여 Schemas > public > Tables 를 확인해보면 성공했다면 테이블들이 생겨남을 알 수 있습니다.

이제 이 테이블들에 직접 정보들을 넣어주면 담기는 것을 확인할 수 있습니다. 여기까지 백엔드 서버 세팅이었습니다. 백엔드 서버도 물론 틀어야겠죠? package.json 에 담겨있는 명령어 중, npm run start:dev 를 사용하여 서버를 틀어줍니다. 다만 주의할 점은 저희가 만든 임시 mock 서버가 틀어져 있다면 당연하게도 같은 포트를 사용하게 설정되어 있으므로 틀어지지 않습니다. MSW 서버는 다운시킨 다음 위 명령어를 실행하세요.

그럼 mock 서버가 아닌 백엔드 서버가 작동 중인지 확인하기 위해선? localhost:9090로 접근했을 때 Cannot Get 같은 문구가 보이지 않으면 됩니다. 강의에선 Hello World 라고 설정해두셨습니다.

 

swagger

https://swagger.io/tools/swagger-ui/

 

REST API Documentation Tool | Swagger UI

 

swagger.io

백엔드 서버내 swagger 를 사용하여 api 문서를 정리해놓으셨습니다. 저는 기존 api 문서를 노션을 통해서 정리하고 있었는데 이런 기능을 처음 알게 되었네요. 역시 개발의 길은 꾸준히 공부해야 하나 봅니다.

개발자가 api 문서를 직접 작성하지 않아도 되게끔 보조할 수 있다고 합니다. 여러 API 문서의 통합, 버전 관리가 용이하다고 합니다. yaml 파일을 사용하여 구성하거나 소스코드 내에서 swagger 를 설정하여 구성하는 두 가지의 방법이 있습니다. 

주로 springboot에서 사용되는지 검색해보니 관련 자료가 많았습니다만 저희는 nodejs 기반으로 작업하고 있으므로 그에 맞춰 사용법을 알아봅니다. 먼저 https://github.com/swagger-api/swagger-ui 깃헙으로 가서 클론을 받거나 다운로드 받아줍니다. dist 폴더만 필요하므로 해당 폴더를 제외한 나머지는 정리해줍니다.

npm http-server --cors
npm install swagger-jsdoc -D

추가로 express 서버를 사용하고 있다면 swagger-ui-express 모듈도 추가로 다운로드가 필요합니다만 현재 강의에선 nest 를 쓰고 있어서 자세한 사항은 모르겠습니다.

 

일단 express로 서버를 구성했다고 치고, swagger.js 파일을 생성하여 아래같이 설정을 해줄수 있습니다. 

const swaggerUi = require("swagger-ui-express")
const swaggereJsdoc = require("swagger-jsdoc")

const options = {
  swaggerDefinition: {
    openapi: "3.0.0",
    info: {
      version: "1.0.0",
      title: "이름",
      description:
        "프로젝트 설명",
    },
    servers: [
      {
        url: "http://localhost:3000", // 요청 URL
      },
    ],
  },
  apis: ["./routers/*.js", "./routers/user/*.js"], //Swagger 파일 연동
}

const specs = swaggereJsdoc(options)
module.exports = { swaggerUi, specs }

대략적인 사용법은 아래와 같습니다. 직접 사용하지 않는한 와닿지는 않지만 이런  것도 있다는 것을 알게 됐다는 점이 중요하니까요. 

const router = require("express").Router()
const user = require("./user")

/**
 * @swagger
 * tags:
 *   name: Users
 *   description: 유저 추가 수정 삭제 조회
 */
router.use("/user", user)
module.exports = router

 

일단 강의 내에서 중요한 것은 이런 api 문서를 작성하는 것보단 해당 swagger 문서를 볼 줄 아는 것이므로, 한번 살펴봅니다.

요청메서드 post, 요청 주소는 /api/login 으로 확인됩니다.

request body 안의 내용이 꼭 필요한 요소를 보여줍니다. application/json 형태로 id와 password를 body에 담아서 보내줘야 합니다.

그 아래론 요청의 응답 내용과 서버 응답 코드가 명세되어 있습니다. 위의 내용을 보면 404, 200, 401 이렇게 존재함을 볼 수 있습니다. 응답 body 는 success 여부와 code 번호, data가 담겨서 옵니다.

 

swagger 의 좋은 점 중 하나는 기존에는 postman 등을 사용하였지만 아래의 이미지처럼 Try it out 을 클릭해서 직접 테스팅을 해볼 수 있다는 점입니다. 

 

 

로그인/회원가입

로그인에서 기존의 방식은 Session 을 사용하고 있으므로 Session 쿠키가 브라우저에 등록이 된다는 것은 예전 포스팅에서 말씀드렸습니다. 이 쿠키 보내주기 위해선 credentials 를 꼭 'include' 처리해줘야 합니다.

API 문서를 확인했다시피 로그인 부분은 MSW를 사용했던 내용과 별다른 수정없이 정상적으로 아래처럼 로그인이 되는 것을 확인하실 수 있습니다. 

pgAdmin 에서도 테이블을 클릭하고 View data 로 확인하면 정상적으로 로그인 정보가 들어갔음을 알 수 있습니다.

로그인 화면으로 돌아가서, 동일한 아이디와 비밀번호로 회원가입 진행 시 아래와 같은 화면을 볼 수 있습니다. 

 

로그인을 해보면 PostForm 쪽의 프로필이 보이지 않음을 알 수 있습니다. 태그를 확인해보면 아래와 같이 src 부분에 /upload 폴더에 있다는 것을 확인할 수 있는데, 이 upload 폴더는 프론트엔드에만 존재하는 폴더입니다. 실제 이미지는 백엔드에 저장되어 있으니 당연하게도 이미지가 뜨지 않는 것입니다. 

이 주소를 백엔드 주소로 바꿔줄 필요가 있는데 이 때 넥스트에는 rewrite 라는 기능이 있다고 합니다. 해당 기능을 사용하여 바꿔줍니다. next.config.js 에 아래처럼 설정해줍니다.

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return [
      {
        source: "/upload/:slug",
        destination: "http://localhost:9090/upload/:slug",
      },
    ];
  },
};

module.exports = nextConfig;

말그대로 원래 프론트딴의 이미지 경로를 서버딴으로 변경해주는 것입니다. 그다음 넥스트 서버를 끈 뒤 재가동해보면..

넣어주었던 이미지가 잘 들어갔음을 알 수 있습니다!

 

rewrite / redirect

rewrite 와 유사한 redirect 둘다 모두 유저가 특정 path에 접근할 때 그 path를 변경시켜주는 기능을 합니다. 다만 rewrite 의 경우입력한 url 그대로 유저에게 보여지고, 화면 변경을 느낄 수 없지만 redirect 는 바뀐 path로 url 이 변하게 된다는 차이가 존재합니다.

module.exports = {
  basePath: '/api',

  async rewrites() {
    return [
      {
        source: '/about', // 유저 진입 path
        destination: '/', // 유저 이동 path
        has: [ 
        	{
            	type : 'header' | 'cookie' | 'host' | 'query',
            	key : string,
                value : string, undefined
            }
       	], // 헤더, 쿠키, 쿼리 값이 일치하는 경우에만 실행할 수 있게 제한
        missing: [ 
        	{
            	type : 'header' | 'cookie' | 'host' | 'query',
            	key : string,
                value : string, undefined
            }
       	], // 헤더, 쿠키, 쿼리 값이 일치하지 않는 경우에만 실행할 수 있게 제한 has <-> missing
        basePath: false, // 지정해준 basePath 가 있는 경우, 그 기본경로를 사용할지에 대한 여부
      },
    ]
  },

  async redirects() {
    return [
      {
        source: '/about', // 유저 진입 path
        destination: '/', // 유저 이동 path
        permanent: true, // 유저나 검색엔진에서 해당 값을 영구적으로 저장할 것인지의 여부
        basePath: true,
      },
    ]
  },
}

위의 틀처럼 source 에는 유저가 접근하는 주소를 의미하고, 해당 주소에 접근하면 destination 주소로 변경시켜줍니다. 특히나 동적 주소창의 경우를 받아오기 위해서 : 콜론을 사용하여 :변수명 형식으로 동적 경로를 받아줄 수 있습니다. 예를 들어 /api/:some 일 때 /api/aaa, /api/abc 등이 모두 가능합니다. 다만 /api/abc/def 같이 :some 의 내용에 /가 들어가게 되면 인식하지 못하기 때문에 이를 위해선 *을 사용해줍니다.

*은 기존의 모든 문자를 받아올 수 있다는 의미를 뜻하듯 /api/:some* 이라고 지정하면 /api/abc/def 나 /api/aaa/bbb 모두 가능해집니다.

 

추가적으로 rewrite의 경우 source에서 동적 경로를 받아줬음에도 destination 에 사용한 곳이 없다면 해당 슬러그(:변수명)는 destination 의 쿼리로 넘겨지게 됩니다. 

 

없는 유저로 로그인

현재 로그인 기능은 대부분 구현되어 있으나 없는 유저일 때 로그인이 안되게끔 안내메세지 및 처리가 필요합니다. 다만 이 경우 아직 강의에서 무슨 문제인지 모르는것 같습니다.

[auth][error] CredentialsSignin: Read more at https://errors.authjs.dev#credentialssignin

이런 문제가 발생하며 없는 유저로 로그인 시 

계속해서 status 가 200으로 뜨는데요... 기존에서 ok가 false 일때 아이디 비번이 일치하지 않는다고 해주고 싶은데 그 과정이 안되고 있습니다. 관련 게시글에서 저와 동일한 에러가 발생하는 분이 계시길래 공유합니다. https://www.inflearn.com/questions/1121093

  const onSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
    e.preventDefault();
    setMessage("");

    try {
      const response = await signIn("credentials", {
        username: id,
        password: password,
        redirect: false,
      });
      if (!response?.ok) {
        setMessage("아이디와 비밀번호가 일치하지 않습니다.");
      } else router.replace("/home");
    } catch (err) {
      console.log(err);
      setMessage("아이디와 비밀번호가 일치하지 않습니다.");
    }
  };

로그인 버튼 로직입니다. 현재 response 가 무조건 성공한다고 나오면서 에러가 발생합니다. 추정하기론 강의 내에선 next-auth 를 사용했고 현재는 auth.js 로 넘어가면서 생긴 에러로 추정됩니다. 위의 첨부한 게시글대로 임시 처치만 해둡니다.

 

회원가입 로직은 서버 액션을 사용한 서버 컴포넌트상에서, 로그인 로직은 클라이언트 컴포넌트 안에서 처리해주었습니다. 아직 auth.js 는 클라이언트 컴포넌트 안에서 쓰긴 불안정한가 봅니다.

 

이제 당장 바꿔줄 수 있는 api 주소를 수정해주고 확인해보면 얼추 그럴듯한 화면이 보이게 됩니다.

비어있는 부분들은 아직 DB에 없는 부분도 있고 서버와 연결이 안된 부분도 있을 것입니다.