Skip to content

vuenos/point_app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

This is a Next.js project bootstrapped with create-next-app.

Point App

πŸ–₯ Interface

Front

  • νšŒμ›κ°€μž…, 둜그인
  • Point μΉ΄λ“œ 등둝, 쑰회
  • Point 적립, μ‚¬μš© λ‚΄μ—­ 확인, 쑰회
  • Point μ‚¬μš©κ°€λŠ₯처 쑰회 (Map)

Backend

  • νšŒμ› 관리(등둝, μ‚­μ œ, 기본정보, 포인트 λ‚΄μ—­)
  • Point μΉ΄λ“œ 관리(등둝, μ‚­μ œ)
  • μ‚¬μš©κ°€λŠ₯처 등둝(Map)

⏳ History

πŸ“† Jul 03, 2024

μœ μ €κ°€ λ“±λ‘ν•œ μΉ΄λ“œ 정보 κ°€μ Έμ˜€κΈ°

  • μœ μ €κ°€ μΉ΄λ“œ λ“±λ‘μ‹œ μœ μ €μ •λ³΄(userEmail, userName, userId)κ°€ 기본적으둜 같이 등둝됨.
  • 둜그인 session 정보 쀑 user._id κ°’μœΌλ‘œ μΉ΄λ“œμ˜ userId 값을 νŠΉμ •ν•˜μ—¬ μžμ‹ μ΄ λ“±λ‘ν•œ μΉ΄λ“œλ₯Ό νŠΉμ •ν•  수 있음.
  • λ°±μ—”λ“œ apiμ—μ„œλŠ” ν˜„μž¬ session의 user._id 값을 userId (cardModel schema)에 λŒ€μž…ν•΄μ„œ db둜의 쿼리 μš”μ²­μ„ ν•˜κ²Œ 됨.
const cards = await Card.find({ userId: session?.user._id });
  • μ—¬κΈ°μ„œ session 정보λ₯Ό λ§€μΉ­ν•˜μ§€ μ•Šκ³  Card.find({ }) 빈 κ°’μœΌλ‘œ μΏΌλ¦¬ν•˜λ©΄ λͺ¨λ“  μΉ΄λ“œμ •λ³΄κ°€ 호좜 λœλ‹€.
  • μ„œλ²„μ‚¬μ΄λ“œ μ—μ„œ ν˜„μž¬ session 정보가 μžˆμ„λ•Œ dbλ₯Ό μ‘°νšŒν•΄μ„œ userId 값을 가져와야 ν•  것이닀.

1μ°¨ μ‹œλ„

  • api/mypage/route.ts 생성
  • db Schema λŠ” cardModel 을 κ·ΈλŒ€λ‘œ μ‚¬μš©.
import {useSession} from "next-auth/react";

const {data: session, status} = useSession();

try {
    await connectDB();
    const cards = await Card.find({ userId: session?.user._id });
    return NextResponse.json(cards, { status: 200 });
}
  • κ²°κ³ΌλŠ” Internal Server Error 500 μ‹€νŒ¨
  • api의 endpoint λŠ” λ¬Έμ œκ°€ μ—†κ³ , μ•„λ§ˆλ„ session 을 κ°€μ Έμ˜€μ§€ λͺ»ν•˜κ±°λ‚˜ user._id λ₯Ό κ°€μ Έμ˜€μ§€ λͺ»ν•˜μ—¬ DB 쿼리가 μ•ˆλœκ²ƒμΌλ“―?
  • Card.find({ }) 와 같이 λΉˆκ°’μœΌλ‘œ ν˜ΈμΆœν•΄λ΄λ„ μ—­μ‹œ 500μ—λŸ¬. session 을 μ·¨λ“ν•˜λŠ” μ½”λ“œμ— λ¬Έμ œκ°€ μžˆλŠ”λ“― ν•˜λ‹€. session μ½”λ“œλ₯Ό μ§€μš°κ³  λͺ¨λ“ κ°’ ν˜ΈμΆœμ‹œ λͺ¨λ“  μΉ΄λ“œ 정보가 호좜됨.

2μ°¨ μ‹œλ„

  • https://next-auth.js.org/tutorials/securing-pages-and-api-routes
  • nextauth κ³΅μ‹μ‚¬μ΄νŠΈμ— μ„œλ²„μ‚¬μ΄λ“œμ—μ„œμ˜ μ„Έμ…˜μ·¨λ“ 관련글이 μžˆλ‹€.
  • 1μ°¨ μ‹œλ„ 방법은 ν”„λ‘ νŠΈμ—μ„œμ˜ μ μš©λ°©λ²• μ΄μ—ˆμŒ.
  • getServerSession λ©”μ˜λ“œλ₯Ό μ΄μš©ν•΄μ„œ api 경둜λ₯Ό λ³΄ν˜Έν•  수 μžˆλ‹€λΌλŠ” λ‚΄μš©.
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/libs/auth";

const session = await getServerSession(req, res, authOptions);

if(session) {
    const cards = await Card.find({ userId: session?.user._id });
    return NextResponse.json(
        cards,
        { status: 200 }
    );
} else {
    res.status(401);
}
  • κ²°κ³ΌλŠ” Internal Server Error 500 μ‹€νŒ¨

πŸ“† June 28, 2024

session 정보 νŠΉμ •ν•˜κΈ°

  • session 의 user 정보 쀑 _id 값이 ν•„μš”ν–ˆλ‹€.

πŸ“† June 21, 2024

μΉ΄λ“œ 등둝 κΈ°λŠ₯

  • μΉ΄λ“œλ“±λ‘μ„ μœ„ν•œ μΉ΄λ“œλͺ¨λΈ, API λΌμš°ν„°, 등둝 form ν™”λ©΄ μΆ”κ°€
  • μž…λ ₯λ°›λŠ” μΉ΄λ“œμ •λ³΄λŠ” μΉ΄λ“œλ²ˆν˜Έ, CVC

πŸ—„οΈ μΉ΄λ“œμ •λ³΄κ°€ λ°μ΄ν„°λ‘œ μ €μ •λ˜λŠ” ꡬ쑰 μ •μ˜ - cardModel

  • mongoDB 에 μ €μž₯될 db ꡬ쑰λ₯Ό cardModel.ts 에 μ •μ˜ν•œλ‹€.
  • μ—¬κΈ°μ„œ userEmail, userName 은 인증 ν›„μ˜ session μ •λ³΄μ—μ„œ μ°Έμ‘°ν•˜κ²Œ λœλ‹€.
  • ("Card", cardSchema) 둜 μ •μ˜λ˜μ–΄ mongoDB 의 cards λΌλŠ” Collection 에 μ €μž₯될 것이닀.
  • TODO: ν˜„μž¬λŠ” test λΌλŠ” db 에 μ €μž₯되고 μžˆλŠ”λ° db λ₯Ό μ§€μ •ν•˜μ—¬(e.g. db 이름이 pointapp) μ €μž₯ν•˜λŠ” 방법도 확인
  • userλŠ” n개의 μΉ΄λ“œλ₯Ό 등둝할 수 μžˆκΈ°μ— μΉ΄λ“œκ΄€λ¦¬λ₯Ό μœ„ν•΄ μΉ΄λ“œμ™€ 맀칭 될 μœ μ €μ˜ unique ν•œ 정보가 λ“±λ‘λ˜μ–΄μ•Ό ν•˜κ³  이 μ •λ³΄λ‘œ μΉ΄λ“œλ₯Ό 검증해야 ν•  것이닀.
  • user 의 unique μ •λ³΄λŠ” email κ³Ό _id 값이 있음.
// cardModel.ts
const cardSchema: Schema = new Schema({
    cardNumber: {
      type: Number,
      unique: true,
      required: [true, "Card number is required"],
    },
    cvc: {
      type: Number,
      unique: false,
      required: [true, "CVC number is required"],
    },
    userEmail: {
      type: String,
      required: true
    },
    userName: {
      type: String,
      required: true
    },
  },
  {
    timestamps: true,
  }
);

const Card: Model<PointCard> = mongoose.models.Card || mongoose.model<PointCard>("Card", cardSchema);

πŸ“† June 18, 2024

포인트 μΉ΄λ“œ 생성

  • μΉ΄λ“œμƒμ„± ν™”λ©΄μ—μ„œ μΉ΄λ“œ μ‹ μ²­ λ²„νŠΌ ν΄λ¦­μ‹œ Random μΉ΄λ“œμΌλ ¨μ™€ CVC(Card Verification Code) 번호 생성
  • μƒμ„±λœ μΉ΄λ“œμ •λ³΄λ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯.

포인트 μΉ΄λ“œ 등둝

  • μ‹€λ¬ΌμΉ΄λ“œκ°€ μ•„λ‹Œ λ””μ§€ν„ΈμΉ΄λ“œ 등둝 (μΉ΄λ“œλ²ˆν˜Έ + CVC)

πŸ“† June, 2024

OAuth

  • google, github λ“±μ˜ OAuth 섀정이 생각보닀 쉽닀.
  • 둜컬 env에 GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET 와 같은 섀정을 μΆ”κ°€ν•˜κ³ , ν•΄λ‹Ή μ½˜μ†”ν™”λ©΄(Configuration)μ—μ„œ Callback URI λ₯Ό μΆ”κ°€ν•΄ μ£Όλ©΄ λœλ‹€.
  • OAuth 제곡 νŽ˜μ΄μ§€ μ—μ„œ μžμ„Έν•œ 섀정을 μ°Έκ³ .
  • 인증을 μœ„ν•œ callback URI λŠ” /api/auth/callback/google μ΄λŸ°μ‹μ΄λ‹€.
  • production 도메인과 localhostλ₯Ό 같이 μΆ”κ°€ν•  수 μ—†λŠ” κ²½μš°λŠ” 둜컬 ν™˜κ²½μ—μ„œ production λ„λ©”μΈμœΌλ‘œ λ„˜μ–΄κ°ˆ 수 μžˆλ‹€.
  • 이 λ•Œ, λ°°ν¬λ˜λŠ” μ„œλ²„μ— 배포 ν™˜κ²½μ„€μ •λ„ 같이 μ„€μ •ν•΄ μ€˜μ•Ό ν•œλ‹€.
  • Vercel μ—μ„œ λ°°ν¬μ‹œ Enviroment 의 μš©λ„λ₯Ό μ„€μ •ν•  수 μžˆλ‹€. (Production, Development)

πŸ“† June, 2024

Session 정보 display 이슈

  • Nextauth (ν˜„μž¬ 4.x) κ°€ 5.x λ²„μ „μœΌλ‘œ μ—…λ°μ΄νŠΈ λ˜λ©΄μ„œ μ„œλΉ„μŠ€ λͺ…칭이 Auth.js 으둜 λ°”λ€Œμ—ˆλ‹€. μ•„μ§κΉŒμ§€λŠ” 5λ²„μ „μœΌλ‘œ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ ν•˜μ§€ μ•ŠμŒ μƒνƒœ.
  • 인증을 ν•˜κ³  session 이 ν’€λ¦¬λŠ” ν˜„μƒμ΄ 있음. (μƒˆλ‘œκ³ μΉ¨ λ“±)
  • session λ”œλ ˆμ΄: 인증 session 취득 ν›„ next/link λ₯Ό ν†΅ν•œ 인터널 νŽ˜μ΄μ§€ μ΄λ™μ—μ„œλŠ” 보이지 μ•ŠμœΌλ‚˜ μƒˆλ‘œκ³ μΉ¨μ„ ν–ˆμ„ 경우 session 취득 μ΄μ „μ˜ UI (Sign In, Log In λ²„νŠΌ λ“±) 이 μ•½ 1~2초 정도 보인 ν›„ session 이 적용된 UI (μœ μ €λ„€μž„, Log Out)κ°€ λ³΄μ΄λŠ” ν˜„μƒμ΄ 있음.
  • SSR μ΄μŠˆμΈκ°€? Nextauth의 버그인가? 5.x 둜 μ—…λ°μ΄νŠΈ ν•˜λ©΄ ν•΄κ²°λ˜λŠ” 것인지? ν˜„μž¬λ‘œμ¨λŠ” μ’€ 더 확인해 봐야 ν•  λ¬Έμ œκ°™λ‹€.
  • Auth.js μ—μ„œ session prefetch κΈ°λŠ₯이 μžˆλ‹€. Roor μ»΄ν¬λ„ŒνŠΈ Provider μ—μ„œ session μ΄λž€ props λ₯Ό 톡해 route μ΄λ™μ‹œμ— 미리 session 정보λ₯Ό 가지고 μžˆλŠ”κ°€λŠ” κ°œλ…κ°™μ€λ°, SessionProvider μ—μ„œ 이 뢀뢄이 이미 μ μš©λ˜μ–΄ μžˆλŠ”κ±° μ•„λ‹ˆμ—ˆλ‚˜? 이 뢀뢄은 μ’€ 더 ν™•μΈν•„μš”ν•¨.
  • 일단은 Session status 의 Loading μƒνƒœλ‘œ 쀑간에 μƒκΈ°λŠ” λ”œλ ˆμ΄ 쀑에 ν•΄λ‹Ή UI 뢀뢄에 μŠ€μΌˆλ ˆν†€ UIλ₯Ό μ μš©ν•΄μ„œ λ‘œλ”© UIλ₯Ό ν‘œν˜„ν•¨.
// Session status 'Loading' 일 λ•Œ 보여 쀄 UI 객체
const loadingAnimation = keyframes`
  0% {
    background-position: -200% 0;
  }
  100% {
    background-position: 200% 0;
  }
`;

const SkeletonSpan = styled.span < {width: string, height: string, margin: string} > `
    display: block;
    background: linear-gradient(90deg, #DADADA, #f5f5f5, #DADADA);
    background-size: 200% 100%;
    animation: ${loadingAnimation} 1.5s infinite;
    width: ${props => props.width};
    height: ${props => props.height};
    margin: ${props => props.margin};
    border-radius: 8px;
`;

// Header.tsx 
const showSession = () => {
    if (status === "authenticated") {
        return (
            <>
                ...
            </>
        )
    } else if (status === "loading") {
        return (
            <>
                <SkeletonSpan width="68px" height="32px" margin="0 0 0 16px"/>
                <SkeletonSpan width="64px" height="32px" margin="0 0 0 16px"/>
            </>
        )
    } else {
        return (
            <>
                ...
            </>
        )
    }
}

πŸ“† June 5, 2024

NextAuth.js κ΅¬ν˜„κ³Ό 였λ₯˜ ν•΄κ²°

  • μ‚¬μš©μžμΈμ¦μ„ NextAuth.js 둜 μ μš©ν•˜κΈ°λ‘œ 함.
  • κ³΅μ‹μ‚¬μ΄νŠΈμ—μ„œλŠ” Next.js 을 μœ„ν•œ μ™„μ „ν•œ μ˜€ν”ˆ μ†ŒμŠ€λ‘œ μ†Œκ°œν•˜κ³  μžˆλ‹€.
  • 인증을 κ΅¬ν˜„ν•˜λŠ” resource 듀이 μž˜μ •λ¦¬λ˜μ–΄ μžˆλ‹€. Get Started

μ‹œμž‘ν•˜λ©°

  • NextAuth.js λŠ” /api/auth path μ•ˆμ—μ„œ 인증을 μ²˜λ¦¬ν•œλ‹€.
  • /app/api/auth/[...nextauth]/route.ts 라우트λ₯Ό λ§Œλ“€μ–΄ GET, POST method λ₯Ό λ§€ν•‘ν•˜λŠ” handler λ₯Ό λ§Œλ“€μ–΄ μ€€λ‹€. (Next.js 14 κΈ°μ€€)
  • [...] 은 동적 λΌμš°νŒ…μ„ μ˜λ―Έν•¨. signIn, signOut, callback λ“±μ˜ μš”μ²­μ΄ μ—¬κΈ°λ₯Ό 톡해 μ²˜λ¦¬λœλ‹€. auth.ts λ₯Ό μ—°κ²°ν•΄μ„œ μΈμ¦λΆ€λΆ„κΉŒμ§€

All requests to /api/auth/*(signIn, callback, signOut, etc.) will automatically be handled by NextAuth.js.

  • /api/auth/* 둜 λ“€μ–΄μ˜€λŠ” api μš”μ²­μ€ /api/auth/[...nextauth]/route.ts μ—μ„œ μžλ™μœΌλ‘œ μ²˜λ¦¬ν•΄ μ€€λ‹€λŠ” 것이닀.
// app/api/auth/[...nextauth]/route.ts
import {authOptions} from "@/libs/auth";
import NextAuth from "next-auth";

const handler = NextAuth(authOptions);

export {
    handler as GET, handler as POST
}
  • 인증을 μ²˜λ¦¬ν•˜λŠ” auth.ts λ₯Ό μž‘μ„±ν•œλ‹€. λ‚˜λŠ” /app/lib/auth.ts 둜 μž‘μ„±
// /app/lib/auth.ts

export const authOptions: NextAuthOptions = {
    providers: [
        GoogleProvider({
            clientId: process.env.GOOGLE_CLIENT_ID as string,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
        }),
        CredentialsProvider({
            name: "Credentials",
            id: "credentials",
            credentials: {
                email: {label: "Email", type: "text", placeholder: "Email"},
                password: {label: "Password", type: "password"},
            },
            async authorize(credentials, req): Promise<any> {
                await connectDB();
                const userFound = await User.findOne({
                    email: credentials?.email,
                }).select("+password");

                if (!userFound) throw new Error("Invalid Email");

                const passwordMatch = await bcrypt.compare(
                    credentials
                !
            .
                password,
                    userFound.password,
            )
                ;

                if (!passwordMatch) throw new Error("Invalid Password");
                return userFound;
            },
        }),
    ],
// μƒλž΅
  • 기본적으둜 email κ³Ό λΉ„λ°€λ²ˆν˜Έ 인증을 μ œκ³΅ν•œλ‹€. ꡬ글 OAuth 도 μ œκ³΅ν•œλ‹€.
  • provider 둜 ꡬ글, κΉƒν—ˆλΈŒ 같은 OAuth 인증, 기본적인 credentials 와 같은 자격증λͺ…인증(email λ˜λŠ” ID 와 λΉ„λ°€λ²ˆν˜Έ)을 μ œκ³΅ν•œλ‹€.
  • CredentiaslProvider μ—μ„œλŠ” credentials ν”„λ‘œνΌν‹°λ₯Ό 톡해 인증을 μœ„ν•΄ μ–΄λ–€ 정보λ₯Ό μž…λ ₯받을지 지정할 수 μžˆλ‹€. (email or user id)
  • μœ„μ—μ„œ μž…λ ₯받은 ν”„λ‘œνΌν‹° 값을 가진 객체가 authorize 콜백 ν•¨μˆ˜μ— 첫번째 인수둜 전달됨(HTTP POST) 그러면 μ‚¬μš©μžκ°μ²΄(email, name, _id λ“±), λ˜λŠ” null, Error 쀑 ν•˜λ‚˜λ₯Ό λ°˜ν™˜ν•˜λŠ” 것이닀.

πŸ“† June 4, 2024

μ‚¬μš©μž 등둝 둜직

  • μ‚¬μš©μž 등둝을 μœ„ν•œ λ‘œμ§μ„ κ΅¬ν˜„ν•œλ‹€. ν”„λ‘ νŠΈμ—μ„œ μž…λ ₯받은 데이터λ₯Ό λ°±μ—”λ“œλ‘œ 보내고 λ°±μ—”λ“œμ—μ„œλŠ” ν•΄λ‹Ή λ°μ΄ν„°μ˜ λͺ¨λΈμ„ 톡해 κ²€μ¦ν•œ ν›„ db에 접속 -> μ €μž₯ν•˜κ²Œ λœλ‹€.

Backend

  • TODO: μ‚¬μš©μž 이메일 쀑볡체크, 데이터 λͺ¨λΈ μ •μ˜κ°€ ν•„μš”ν•˜λ‹€.
  • bcrypt둜 passwordλ₯Ό μ•”ν˜Έν™” ν–ˆλ‹€.
export async function POST(request: NextRequest) {
    try {
        const client = await connect;
        const body = await request.json();

        const salt = await bcrypt.genSalt(10);
        const hashedPassword = await bcrypt.hash(body.password, salt);

        await client.db("test").collection("users").insertOne({
            name: body.name,
            email: body.email,
            password: hashedPassword
        });

        return NextResponse.json({
            message: "successfully updated the document",
            success: true,
        })
    } catch (error: any) {
        return NextResponse.json({error: error.message}, {status: 500})
    }
}

Frontend

  • onSubmit 이벀트둜 폼을 μ „μ†‘ν•˜κ³ , event.preventDefault() 둜 폼이 λ¬΄μž‘μ • μ „μ†‘λ˜λŠ”κ±Έ λ§‰λŠ”λ‹€.
  • μž…λ ₯κ°’ validator ν•„μš”ν•¨.
  • 인증 둜직이 μ™„μ„±λ˜λ©΄ 인증여뢀에 λ”°λ₯Έ μ ‘κ·Ό μ—¬λΆ€ μž‘μ—…λ„ ν•„μš”(Router).
const [email, setEmail] = useState < string > ("");
const [name, setName] = useState < string > ("");
const [password, setPassword] = useState < string > ("");

const userInput = {
    email,
    name,
    password,
}

const submitHandler = async (event) => {
    event.preventDefault();
    try {
        const {data, status} = await axios.post("/api/users/regist", userInput, {
            headers: {
                'Content-Type': 'application/json'
            }
        });

        if (status === 200) {
            router.push("/member/login")
        }
    } catch (error) {
        console.log(error.message)
    }
}

<form onSubmit={submitHandler}>
    <InputGroup
        inputId="email"
        type="email"
        label="이메일"
        title="Email"
        required={true}
        value={email}
        placeholder="Email"
        className="input-text"
        onchange={(e) => setEmail(e.target.value)}
    />
    .
    .
    .
    <Input
        type="submit"
        value="Submit"
        className="input-button submit"
    />
</form>

일단 등둝은 μ •μƒμ μœΌλ‘œ λœλ‹€.

πŸ“† May 31, 2024

인증 둜직 κ΅¬ν˜„

  • bcrypt, jsonwebtoken νŒ¨ν‚€μ§€λ‘œ μœ μ €λ“±λ‘μ‹œ λ³΄λ‚΄λŠ” 데이터λ₯Ό μ•”ν˜Έν™”ν•˜κ³  웹토큰을 λ§Œλ“€μ–΄μ„œ μœ μ €μ˜ 데이터λ₯Ό κ²€μ¦ν•˜κ³  인증할 것 이닀.

πŸ“† May 30, 2024

UI loading

Nextjs App Router μ—μ„œλŠ” μ•½μ†λœ 파일 쀑(layout, page, route λ“±λ“±) ν•˜λ‚˜μΈ loading.ts λΌλŠ” 파일이 μžˆλ‹€. μ΄λŠ” νŽ˜μ΄μ§€κ°„ 이동 μ‹œ λ Œλ”λ§μ΄ λ˜λŠ” λ™μ•ˆ λŒ€μ²΄λ˜λŠ” ui λ₯Ό 보여쀄 수 μžˆλ‹€.
μ»΄ν¬λ„ŒνŠΈλ³„λ‘œλ„ μ μš©ν•  수 μžˆλ‹€.
layout.tsx와 page.tsx 와 같은 κ²½λ‘œμ— λ§Œλ“€μ–΄ 두면 ν•΄λ‹Ή layout.tsx 둜 λž©ν•‘λ˜μ–΄ μžˆλŠ” ν™”λ©΄ 및 μ»΄ν¬λ„ŒνŠΈλŠ” λͺ¨λ‘ μžλ™ μ μš©λœλ‹€.
loading.js
Suspense λ₯Ό 톡해 μ»΄ν¬λ„ŒνŠΈ λ³„λ‘œ λ‘œλ”© UIλ₯Ό μˆ˜λ™ μ„€μ •ν•  수 μžˆλ‹€.

Suspense

  • loading.tsx νŒŒμΌμ„ μ΅œμƒλ‹¨μ˜ page.tsx 와 같은 κ²½λ‘œμ— 두어 곡톡 λ‘œλ”© UI둜 μ μš©ν•¨. νŽ˜μ΄μ§€κ°„ μ΄λ™μ‹œμ— μ μš©λ˜λŠ” 것이 확인됨. (ν”„λ‘œμ νŠΈ 초기 단계라 νŽ˜μ΄μ§€ λ Œλ”λ§μ΄ λΉ¨λΌμ„œ 인지 거의 보이지 μ•ŠμŒ)
  • <Suspense><Component /></Suspense> μ‚¬μš©ν•΄ λ³΄μ•˜μœΌλ‚˜ 적용이 μ•ˆλ˜λŠ”κ±° κ°™μŒ... μ΄μœ λŠ” ν™•μ‹€νžˆ λͺ¨λ₯΄κ² λ‹€. react developer tools 둜 마운트 된 components λ₯Ό 보면 <Suspense>κ°€ μ‘΄μž¬ν•¨.
  • loading.tsc λ₯Ό μ‚¬μš©ν•΄μ„œ 데이터 패치 μƒνƒœμ— λ”°λ₯Έ λ‘œμ§μ„ κ΅¬ν˜„.
// loading μƒνƒœ μΆ”κ°€
const [loading, setLoading] = useState<boolean>(true);

const getUsersHandler = async () => {
  try {
    const res = await axios.get("/api/users/");

    if (res.status === 200) {
      setUsers(res.data);
      setLoading(false); // data κ°€ μ„±κ³΅μ μœΌλ‘œ 패치되면 λ‘œλ”© 감좀
    }
  } catch (error) {
    setError(true);
    setLoading(false);
    setErrMsg(
      error.reponse && error.response.data.message
        ? error.response.data.message
        : error.message
    );
    console.log(error.message);
  }

}

if (loading) return <Loading / >;

return (
  <>
...
</>
)

πŸ“† May 29, 2024

μ €μž₯된 μœ μ €λ“€ 정보 κ°€μ Έμ˜€κΈ°
users collection 에 μ €μž₯된 μ €λ“€μ˜ 데이터(document)λ₯Ό front ν™”λ©΄μ—μ„œ ν˜ΈμΆœν•˜κΈ°

  • app/api/users/route.ts 에 μœ μ €λ°μ΄ν„°λ₯Ό ν˜ΈμΆœν•˜λŠ” 콘트둀러λ₯Ό λ§Œλ“€μ–΄ /api/users API endpoint 생성
// app/api/users/route.ts
// /api/users
export async function GET(req: NextRequest) {
    const client = await connect;
    const usersData = await client.db("test").collection("users").find();
    const getUsers = await usersData.toArray();

    return NextResponse.json(getUsers);
}

// app/member/users/page.tsx
// /member/users
const [users, setUsers] = useState([]);

const getUsersHandler = async () => {
    try {
        const {data, status} = await axios.get("/api/users/");
        if (status === 200) {
            setUsers(data);
        }
    } catch (error) {
        console.log(error.message);
    }

}

useEffect(() => {
    getUsersHandler();
}, []);

return (
    <>
        <ul>
            {users && users.map((user) => (
                <li key={user._id}>
                    {user.name}({user.email})
                </li>
            ))}
        </ul>
    </>
)

[ν”„λ‘ νŠΈ ν™”λ©΄]

πŸ“† May 29, 2024

DB μ €μž₯ 이벀트 ν•Έλ“€λŸ¬ μΆ”κ°€
Backend μ—μ„œ μœ μ €λ°μ΄ν„° email, name 을 users collection 에 μ €μž₯ν•  수 μžˆλŠ” 이벀트 ν•Έλ“€λŸ¬λ₯Ό μΆ”κ°€ν•œλ‹€.
Front ν™”λ©΄μ—λŠ” email, name 데이터λ₯Ό 보낼 수 μžˆλŠ” form 을 λ§Œλ“ λ‹€. (μ‹ κ·œμœ μ € 등둝을 μœ„ν•΄μ„œλŠ” password 도 ν•„μš”ν•˜λ‹€. password λŠ” μ•”ν˜Έν™” λ‘œμ§λ„ ν•„μš”) Front μ—μ„œλŠ” μœ μ €λ°μ΄ν„°λ₯Ό /api/users/regist api λ₯Ό ν˜ΈμΆœν•˜μ—¬ db에 μ €μž₯ν•˜κ²Œ λœλ‹€.

// app/api/users/regist/route.ts
export async function POST(request: Request) {
    const client = await connect;
    const body = await request.json();
    await client.db("test").collection("users").insertOne({
        email: body.userEmail,
        name: body.userName,
    });
    return Response.json({
        message: "successfully updated the document"
    })
}

// app/member/join/page.tsx
const submitHandler = () => {
    fetch("/api/users/regist", {
        method: "POST",
        body: JSON.stringify({
            userEmail,
            userName,
            userPassword
        })
    })
}

// <InputGroup> 은 input κ³Ό label λ“±μœΌλ‘œ κ΅¬μ„±λœ μ»΄ν¬λ„ŒνŠΈ 
<form onSubmit={submitHandler}>
    <InputGroup
        inputId="userEmail"
        type="email"
        title="Email"
        required={true}
        value={userEmail}
        placeholder="Email"
        onchange={(e) => setUserEmail(e.target.value)}
    />
    <InputGroup
        inputId="userName"
        type="text"
        title="Name"
        required={true}
        value={userName}
        placeholder="Name"
        onchange={(e) => setUserName(e.target.value)}
    />

    <Input
        type="submit"
        value="Submit"
    />
</form>

[μž…λ ₯ν™”λ©΄]

[Form 전솑결과]

πŸ“† May 28, 2024

MongDB에 μ—°κ²°ν•˜κ³  데이터λ₯Ό POST, GET ν•˜λŠ” ν…ŒμŠ€νŠΈ μ½”λ“œ.

  • API route 의 κΈ°λ³Έμ½”λ“œ
const {MongoClient, ServerApiVersion} = require("mongodb");

// Replace the placeholder with your Atlas connection string
const uri = "<connection string>";

// Create a MongoClient with a MongoClientOptions object to set the Stable API version
const client = new MongoClient(uri, {
        serverApi: {
            version: ServerApiVersion.v1,
            strict: true,
            deprecationErrors: true,
        }
    }
);

async function run() {
    try {
        // Connect the client to the server (optional starting in v4.7)
        await client.connect();

        // Send a ping to confirm a successful connection
        await client.db("admin").command({ping: 1});
        console.log("Pinged your deployment. You successfully connected to MongoDB!");
    } finally {
        // Ensures that the client will close when you finish/error
        await client.close();
    }
}

run().catch(console.dir);

db에 μ ‘μ†ν•œ ν›„ 데이터λ₯Ό λ³΄λ‚΄λŠ” ν…ŒμŠ€νŠΈ νŽ˜μ΄μ§€λ₯Ό μΆ”κ°€.

// api/users/regist/route.ts
// /api/users/regist entpointκ°€ 생성됨

export async function POST(request: Request) {
    const client = await connect;
    const cursor = await client.db("test").collection("users").insertOne({
        email: "vuenos@gmail.com",
        name: "Jintae Kim",
        Age: "22"
    });
    return Response.json({message: "successfully updated the document"})
}

// app/test/page.tsx
export default function TestApi() {
    useEffect(() => {
        fetch("/api/users/regist", {
            method: "POST"
        })
    }, []);

    return (
        <h1>Some Content</h1>
    )
}

이러면 http://localhost:3000/test μ ‘μ†μ‹œ
MongoDB 의 'test' db 의 'users' collection 에 email, name, age 데이터λ₯Ό μ €μž₯함.

  • insertOne : 단일 document λ₯Ό collection 에 μ €μž₯
  • insertMany : 닀쀑 document λ₯Ό collection 에 μ €μž₯

[μ°Έκ³ ] https://www.mongodb.com/ko-kr/docs/manual/reference/method/db.collection.insertOne/

[κ²°κ³Ό]

[μ°Έκ³ ]

πŸ“† May 22, 2024

MongoDB μ„ΈνŒ…

DBλŠ” λͺ½κ³ λ””λΉ„λ₯Ό μ‚¬μš©ν•˜κΈ°λ‘œ ν–ˆλ‹€.
등둝, 쑰회 의 κΈ°λŠ₯이 λŒ€λΆ€λΆ„μ΄λΌ 무리 μ—†μ–΄ λ³΄μ˜€λ‹€.
db,collection을 생성

πŸ“† May 20, 2024

Express 둜 λ°±μ—”λ“œλ₯Ό ꡬ성.ν–ˆμ„λ•Œ
ν”„λ‘ νŠΈμ™€ λ³„κ°œλ‘œ μ„œλ²„ν˜ΈμŠ€νŒ…μ„ ꡬ성해야 ν•œλ‹€
vercel 둜 deploy ν•˜μ—¬ production μƒνƒœλ‘œ λ§Œλ“€μ–΄μ„œ 온라인으둜 접속해 보면 ν”„λ‘ νŠΈλŠ” μ •μƒμ μœΌλ‘œ λ³΄μ΄μ§€λ§Œ
API λ₯Ό ν˜ΈμΆœν–ˆμ„λ•Œ 톡신이 λ˜μ§€ μ•ŠλŠ”λ‹€. λ°±μ—”λ“œ λ¦¬μ†ŒμŠ€λ₯Ό λ³„λ„λ‘œ ν˜ΈμŠ€νŒ… ν•˜μ—¬ ν˜ΈμΆœν•΄μ•Ό ν•˜λŠ” 것이닀.

  • Next.js 둜 κ°œλ°œν•˜λŠ”λ° Express 둜 λ°±μ—”λ“œλ₯Ό λ³„λ„λ‘œ κ΅¬μ„±ν•˜κΈ° 보닀 Next.js 의 Route Handlers 둜 endpoint λ₯Ό λ§Œλ“€λ©΄ λ˜κ² λ‹€λŠ” 생각이 λ“€μ—ˆλ‹€.
  • route ꡬ성은 ν”„λ‘ νŠΈμ™€ λ™μΌν•˜λ‹€. /app/api/users/route.ts λŠ” /api/users λΌλŠ” endpoint λ₯Ό λ§Œλ“€μ–΄ μ€€λ‹€.
  • /app/api/users/route.ts 에 ν˜ΈμΆœμ‹œ λ°˜ν™˜λ˜λŠ” 이벀트 ν•Έλ“€λŸ¬λ₯Ό μž‘μ„±ν•΄ μ£Όλ©΄ λœλ‹€.
  • GET, POST λΌλŠ” ν•¨μˆ˜λͺ…이 κ·ΈλŒ€λ‘œ κ·Έ μ—­ν™œμ„ μˆ˜ν–‰ν•΄ μ€€λ‹€.
// /app/api/users/route.ts
import {NextRequest, NextResponse} from "next/server";

export async function GET(req: NextRequest) {
    return NextResponse.json({
        name: "Jintae Kim",
        email: "vuenos@gmail.com",
        isAdmin: true,
    })
}

// /app/page.tsx
const callDataHandler = async () => {
    try {
        const {data, status} = await axios.get(`${url}/test`);

        if (status === 200) {
            setUserData(data);
            setCallStatus("suceess");
            setMsg(`μ„œλ²„μ—μ„œ μ •μƒμ μœΌλ‘œ 응닡함`);
        }
    } catch (error: any) {
        console.log(error.message)
        setCallStatus("failed");
        setMsg(`μ„œλ²„ 톡신 μ‹€νŒ¨`);
    }
    ;
};

ν”„λ‘ νŠΈμ—μ„œμ˜ κ²°κ³ΌλŠ” μ•„λž˜μ™€ κ°™λ‹€.


πŸ“† May 18, 2024

ν—€λ”μ˜ 곡톡 λ„€λΉ„κ²Œμ΄μ…˜μ—μ„œ ν˜„μž¬ λ©”λ‰΄μ˜ ν™œμ„±ν™” μŠ€νƒ€μΌ 적용
React μ—μ„œλŠ” NavLink λ₯Ό μ΄μš©ν•˜λ©΄ μ‰½κ²Œ 적용이 λ˜μ§€λ§Œ Next.js μ—μ„œλŠ” μ§€μ›ν•˜μ§€ μ•ŠλŠ”λ‹€.
κ·Έλž˜μ„œ μ»€μŠ€ν…€μ„ ν•΄μ•Ό ν•œλ‹€.

  • 1μ°¨ 방법 : next/router 의 useRouter λ₯Ό μ΄μš©ν•΄μ„œ ν˜„μž¬νŽ˜μ΄μ§€μ˜ pathname 을 νŠΉμ •ν•΄μ„œ λΉ„κ΅ν•˜λŠ” 방법
  • λ¨Όμ € λ©”λ‰΄μ˜ 정보에 λŒ€ν•œ 객체 배열을 λ§Œλ“€μ–΄ μ£Όκ³ ,
const menuData = [
    {id: "menu01", title: `${userName}`, path: "/member/mypage"},
    {id: "menu02", title: "Login", path: "/member/login"},
    {id: "menu03", title: "Sign up", path: "/member/join"},
];
  • path κ°’κ³Ό ν˜„μž¬ pathname 을 λΉ„κ΅ν•˜μ—¬ νŠΉμ • μŠ€νƒ€μΌμ„ μ μš©ν•œλ‹€.
const router = useRouter();

{
    menuData.map((menuItem) => (
        <Link
            href={menuItem.path}
            className={`${menuItem.path === router.pathname ? "active" : ""}`}
        >
            {menuItem.title}
        </Link>
    ))
}

그런데 λ‹€μŒκ³Ό 같은 μ—λŸ¬κ°€ λœ¬λ‹€.
Error: NextRouter was not mounted.
Next.js 13 λΆ€ν„°λŠ” next/router λŒ€μ‹  next/navigation λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

// refactor
import {usePathname} from "next/navigation";

const pathname = usePathname();

{
    menuData.map((menuItem) => (
        <Link
            href={menuItem.path}
            className={`${menuItem.path === pathname ? "active" : ""}`}
        >
            {menuItem.title}
        </Link>
    ))
}

μ°Έκ³  : https://nextjs.org/docs/app/api-reference/functions/use-router


πŸ“† May 16, 2024

Next.js App Router

Next.js 13 λΆ€ν„° app/ 디렉토리λ₯Ό 기반으둜 λΌμš°νŒ…μ΄ κ΅¬μ„±λœλ‹€.
app/about μ΄λž€ dir λ₯Ό λ§Œλ“€κ³  page.tsx λ₯Ό μƒμ„±ν•˜λ©΄ /about μ΄λž€ route κ°€ ꡬ성이 λ˜λŠ” 것이닀.
Layout.tsx 둜 λ ˆμ΄μ•„μ›ƒμ„ ꡬ성할 수 μžˆλ‹€. /app/page.tsx κ°€ "/" μ΄λž€ route κ°€ λœλ‹€.
Layout 은 μ—¬λŸ¬ 경둜 간에 κ³΅μœ λ˜λŠ” UI 이닀. Route κ°„μ˜ 이동 μ‹œ λ ˆμ΄μ•„μ›ƒ μƒνƒœλ₯Ό μœ μ§€ν•˜κ³  μƒν˜Έκ΅ν™˜ μƒνƒœλ₯Ό μœ μ§€ν•˜λ©° λ¦¬λ Œλ”λ§ λ˜μ§€ μ•ŠλŠ”λ‹€.

// app/layout.js
export default function RootLayout({
                                       children,
                                   }: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang="ko">
            <body>
                <Header/>
                <Wrapper>{children}</Wrapper>
                <Footer/>
            </body>
        </html>
    );
}

// app/page.js
export default function Page() {
    return <h1>Hello, World</h1>;
}

πŸ“† May 13, 2024

  • User 데이터 λͺ¨λΈ 생성
  • /server/ 내에 routes, models, controller λ₯Ό μƒμ„±ν•œλ‹€.
  • routes/userRoutes.js 에 λ°±μ—”λ“œμ—μ„œ μš”μ²­λ°›μ„ routes λ₯Ό μ •μ˜ν•œλ‹€.
const router = express.Router();

router.route("/")
    .post(registerUser)
    .get(protect, admin)

router
    .post("/login", authUser)

router
    .route('/profile')
    .get(protect, getUserProfile)
    .put(protect, updateUserProfile)

router
    .route('/:id')
    .delete(protect, admin, deleteUser)
    .get(protect, admin, getUserById)
    .put(protect, admin, updateUser)
  • models/userModel.js μ—μ„œ μœ μ €λ°μ΄ν„° schema λ₯Ό ꡬ성함.
const userSchema = mongoose.Schema(
    {
        name: {
            type: String,
            required: true,
        },
        email: {
            type: String,
            required: true,
            unique: true,
        },
        password: {
            type: String,
            required: true,
        },
        isAdmin: {
            type: String,
            required: true,
            default: false,
        },
    },
    {
        timestamps: true,
    }
);

const User = mongoose.model("User", userSchema);

export default User;
// User model 을 User 둜 μ •μ˜ν•΄μ„œ 내보낸닀
  • controller/userController.js : registerUser ν•Έλ“€λŸ¬λ₯Ό 톡해 userModel μ—μ„œ κ°€μ Έμ˜¨ User λͺ¨λΈμ— λ§žμΆ”μ–΄ μž…λ ₯받은 μœ μ €λ°μ΄ν„°λ₯Ό μƒμ„±ν•œλ‹€.
const registerUser = asyncHandler(async (req, res) => {
    const {name, email, isAdmin, password} = req.body

    const userExists = await User.findOne({email})

    if (userExists) {
        res.status(400)
        throw new Error('User already exists')
    }

    const user = await User.create({
        name,
        email,
        isAdmin,
        password,
    })

    if (user) {
        res.status(201).json({
            _id: user._id,
            name: user.name,
            email: user.email,
            isAdmin: user.isAdmin,
            token: generateToken(user._id),
        })
    } else {
        res.status(400)
        throw new Error('Invalid user data')
    }
});

πŸ“† May 10, 2024

  • μ„œλ²„μš”μ²­ 방식을 async 둜 λ³€κ²½
  • μ„œλ²„μ‘λ‹΅μƒνƒœμ— 따라 μ»€μŠ€ν…€ λ©”μ‹œμ§€ 적용
const callDataHandler = async () => {
    try {
        const {data, status} = await axios.get("http://localhost:5500/getdata");

        if (status === 200) {
            console.log(data);
            setMsg(data);
            setCallStatus("suceess");
        }
    } catch (error: any) {
        console.log(error);
        setMsg("μ„œλ²„ 톡신을 μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€.");
        setCallStatus("failed");
    }
    ;
};

<div>
    {msg && (callStatus === "suceess") ? <CallStackSuccess/>
        : (callStatus === "failed") ? <CallStackFailed/>
            : null
    }
</div>

πŸ“† May 8, 2024

  • λ°±μ—”λ“œ 호좜 ν…ŒμŠ€νŠΈ
  • μ΄λ²€νŠΈν•Έλ“€λŸ¬ (callDataHandler) λ²„νŠΌμ„ ν†΅ν•΄μ„œ μ„œλ²„μš”μ²­μ„ ν…ŒμŠ€νŠΈ
// server.js μΆ”κ°€
server.get("/getdata", (req, res) => {
    res.send(`PORT: ${port} μ„œλ²„μ—μ„œ μ •μƒμ μœΌλ‘œ 응닡함`);
});

//page.tsx
// μ„œλ²„ 호좜 이벀트 ν•Έλ“€λŸ¬ μΆ”κ°€
const callDataHandler = () => {
    axios.get("http://localhost:5500/getdata").then((res) => {
        console.log(res.data);
        setMsg(res.data);
    }).catch((error) => {
        console.log(error.message);
        setMsg(error.message);
    });
};


<button onClick={callDataHandler}>데이터 μš”μ²­</button>

μ„œλ²„ 응닡 성곡, μ‹€νŒ¨


πŸ“† May 7, 2024

  • ν”„λ‘ νŠΈ, λ°±μ—”λ“œλ₯Ό 같이 같이 κ΅¬μ„±ν•˜κΈ°λ‘œ 함.
  • src/server/ μ•ˆμ— μ„œλ²„ ꡬ동을 μœ„ν•œ 파일 생성 (server.js)
  • ν”„λ‘ νŠΈ port 3000, λ°±μ—”λ“œ port 5500
//server.js
import express from "express";
import next from "next";

const dev = process.env.NODE_ENV !== "development";
const port = process.env.PORT || 5500;

const app = next({dev, port});

const handle = app.getRequestHandler();

const isDate = new Date();

app.prepare().then(() => {
    const server = express();

    server.get("/", (req, res) => {
        res.send(
            `Running SERVER... ${isDate}`
        );
    });

    server.all("*", (req, res) => {
        return handle(req, res);
    });

    server.listen(port, () => {
        console.log(
            `Server running is ${process.env.NODE_ENV} mode on port ${port}`
        );
    });
});
  • pscksge.json μˆ˜μ •
"scripts": {
"server": "nodemon src/server/server",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},

μ‹€ν–‰

npm run server

npm run dev

Getting Started

First, run the development server:

npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev

point_app
β”œβ”€ public
β”‚  β”œβ”€ next.svg
β”‚  └─ vercel.svg
β”œβ”€ src
β”‚  β”œβ”€ app
β”‚  β”‚  β”œβ”€ favicon.ico
β”‚  β”‚  β”œβ”€ globals.css
β”‚  β”‚  β”œβ”€ layout.tsx
β”‚  β”‚  β”œβ”€ page.module.css
β”‚  β”‚  └─ page.tsx
β”‚  └─ server
β”‚     └─ server.js