본문 바로가기
여러가지 정보/팁

Next.js에서 회원가입 구현하기

by 윈백 2023. 6. 19.

Next.js 에서 app/dir을 이용한 커스텀 회원가입을 구현한걸 기록으로 남긴다.

 

next.js, 몽고DB, next-auth를 이용해 만들어봤다.

 

1. next.js, 몽고DB를 설치해준다.

//next.js 설치
npx create-next-app

//몽고db 설치
npm i mongodb

2-1 회원가입 페이지

import SignUp from "@/app/components/Auth/SignUp";
import { Metadata } from "next";
//import { authOptions } from "@/app/lib/auth";
//import { getServerSession } from "next-auth";
//import { redirect } from "next/navigation";

export const metadata: Metadata = {
  title: "회원가입",
  description: "회원가입을 위한 페이지 입니다.",
};

export default async function SignUpPage() {

/*

 아래의 코드는 나중에 next-auth를 이용해 페이지를 보호할때 사용.
 const session = await getServerSession(authOptions);


if (session) {
    redirect("/");
  }
*/
  
  return <SignUp />;
}

2-2 회원가입 컴포넌트.

"use client";

import { User } from "@/type/type";
import Link from "next/link";
import { useState } from "react";
import { checkUser } from "./use/check-user";
import { useRouter } from "next/navigation";

export default function SignUp() {
  const router = useRouter();
  const [userValue, setUserValue] = useState<User>({
    email: "",
    password: "",
    name: "",
  });
  const [checkPassword, setCheckPassword] = useState("");
  const [checkOutput, setCheckOutput] = useState("");

  function changeHandler(e: React.ChangeEvent<HTMLInputElement>) {
    setUserValue({
      ...userValue,
      [e.target.name]: e.target.value,
    });
  }

  function checkPasswordHandler(e: React.ChangeEvent<HTMLInputElement>) {
    setCheckPassword(e.target.value);
  }
  async function signUpHandler(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setCheckOutput("가입중...");

//유저가 입력한 정보가 유효한지 확인하는 checkUser()
    const data = checkUser(userValue, checkPassword);

    if (!data.isValid) {
      return setCheckOutput(data.message);
    }

//회원정보를 DB에 저장하기 위해 fetch해준다.
    const response = await fetch("/api/signup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(userValue),
    });

    const result = await response.json();
    if (result.status === 201) {
      router.push("/");
      return;
      //가입완료
    } else {
      //가입실패
      setCheckOutput(result.message);
      return;
    }
  }
  return (
    <div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
      <div className="sm:mx-auto sm:w-full sm:max-w-sm">
        <h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
          회원가입
        </h2>
      </div>

      <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
        <form onSubmit={signUpHandler} className="space-y-6">
          <div>
            <div className="flex items-center justify-between">
              <label
                htmlFor="email"
                className="block text-sm font-medium leading-6 text-gray-900"
              >
                이메일
              </label>
              <div className="text-sm">
                <button className="font-semibold text-indigo-600 hover:text-indigo-500">
                  이메일 중복 확인
                </button>
              </div>
            </div>

            <div className="mt-2">
              <input
                value={userValue.email}
                onChange={changeHandler}
                id="email"
                name="email"
                type="email"
                autoComplete="email"
                required
                className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
              />
            </div>
          </div>

          <div>
            <label
              htmlFor="name"
              className="block text-sm font-medium leading-6 text-gray-900"
            >
              닉네임
            </label>
            <div className="mt-2">
              <input
                value={userValue.name}
                onChange={changeHandler}
                id="name"
                name="name"
                type="text"
                required
                className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
              />
            </div>
          </div>

          <div>
            <div className="flex items-center justify-between">
              <label
                htmlFor="password"
                className="block text-sm font-medium leading-6 text-gray-900"
              >
                비밀번호
              </label>
            </div>
            <div className="mt-2">
              <input
                value={userValue.password}
                onChange={changeHandler}
                id="password"
                name="password"
                type="password"
                autoComplete="current-password"
                required
                className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
              />
            </div>
          </div>

          <div>
            <div className="flex items-center justify-between">
              <label
                htmlFor="password2"
                className="block text-sm font-medium leading-6 text-gray-900"
              >
                비밀번호 확인
              </label>
            </div>
            <div className="mt-2">
              <input
                value={checkPassword}
                onChange={checkPasswordHandler}
                id="password2"
                name="password2"
                type="password"
                autoComplete="current-password"
                required
                className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
              />
            </div>
          </div>

          <div>
            {checkOutput && (
              <p className="text-center text-red-500 font-bold">
                {checkOutput}
              </p>
            )}
          </div>

          <div>
            <button
              type="submit"
              className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
            >
              회원가입
            </button>
          </div>
        </form>

        <p className="mt-10 text-center text-sm text-gray-600">
          <Link
            href="/auth/in"
            className="font-semibold leading-6 text-indigo-600 hover:text-indigo-500"
          >
            로그인
          </Link>
        </p>
      </div>
    </div>
  );
}

 

3. app/api/signup/route.ts를 만들고 코드를 짜준다.

import { MongoDbSignUp } from "@/app/lib/signUp";
import { User } from "@/app/type/type";
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  try {
    const data: User = await req.json();

    const dbResponse = await MongoDbSignUp(data);

    if (dbResponse.status !== 201) {
      throw new Error(dbResponse.message);
    }

    return NextResponse.json({ message: "가입 성공", status: 201 });
  } catch (e) {
    if (e instanceof Error) {
      return NextResponse.json({ message: e.message });
    } else {
      return NextResponse.json({ message: String(e) });
    }
  }
}

- req에 SignUp 컴포넌트에서 fetch한 데이터가 들어가있다.

- MongodbSignUp함수를 만들건데 이곳에 받은 데이터를보내준다.

 

 

3. app/lib/signUp.ts 의 MongoDbSignUp

import { User } from "../type/type";
import { hashPassword } from "./auth";
import { collectionUsers } from "./collectionName";
import { checkEmail, checkName, connectDatabase } from "./db";

export async function MongoDbSignUp(req: User) {
  const client = await connectDatabase();

  try {
    const { email, name, password } = req;

    if (
      email.trim().length === 0 ||
      !email.includes("@") ||
      name.trim().length === 0 ||
      !name ||
      !password ||
      password.trim().length === 0
    ) {
      throw new Error("모든 정보를 채워주세요.");
    }

    const db = client.db();

    const checkedEmail = await checkEmail(email);
    const checkedName = await checkName(name);

    if (checkedEmail) {
      throw new Error(checkedEmail.message);
    }
    if (checkedName) {
      throw new Error(checkedName.message);
    }

    const hashedPassword = await hashPassword(password);

    await db.collection(collectionUsers).insertOne({
      email: email,
      name: name,
      password: hashedPassword,
    });

    return { status: 201, message: "가입 성공" };
  } catch (e) {
    if (e instanceof Error) {
      return { message: e.message };
    } else {
      return { message: String(e) };
    }
  } finally {
    client.close();
  }
}

-connectDatabase()와 hashPassword()는 이전 게시글에 코드가있으며 각각 데이터베이스에 연결하고 비밀번호를 암호화 해주는 함수다.

 

이렇게 해주면 몽고DB에 데이터가 저장된다.