당근먹는하니
귀엽고 행복해
당근먹는하니
전체 방문자
오늘
어제
  • 분류 전체보기 (274)
    • 다람쥐🐿 (26)
    • C++ 공부빵야 (7)
    • 공부👻 (5)
    • 프론트엔드✏️ (228)
      • 코드캠프 (120)
      • 팀 프로젝트✨ (31)
      • 개인공부 (67)
    • 프론트엔드 - 바닐라js (7)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • react
  • React-hook-form
  • 팀프로젝트
  • ssg
  • 자바스크립트
  • 코드캠프
  • JS
  • HTML
  • 팀 프로젝트
  • graphql
  • javascrpit
  • javascript
  • 팀플
  • 공통 컴포넌트
  • 알고리즘
  • 리액트
  • 배포
  • 코딩 부트캠프
  • 배열
  • next.js
  • emotion
  • 프론트엔드
  • 부트캠프
  • typescript
  • CSS
  • refreshtoken
  • algorithm
  • 코딩
  • 프로그래머스
  • 회고

최근 댓글

최근 글

티스토리

250x250
반응형
hELLO · Designed By 정상우.
당근먹는하니

귀엽고 행복해

[Next.js] [MongoDB] CRUD의 Create, Read 연결하기
프론트엔드✏️/개인공부

[Next.js] [MongoDB] CRUD의 Create, Read 연결하기

2024. 5. 6. 16:19
728x90
반응형

 

 작년 여름부터 이어져온 몽고디비와의 (일방적)사투

연결이 너무 안돼서, 쉬운 연결 하나도 못 하는 나는 개발자 못 하는 거 아닐까 하고 그냥 개발을 접어버렸었다. 

그러다 만들고 싶은 게 생겨서 다시 해보다가, 역시 DB가 필요해서... 다시 시도해봤다.

 

 클라우드는 몽고디비 아틀라스를 이용했다.

 

Next.js - 13 

App router

typescript 

 

 

이 글에선 Next.js 자체 서버를 그냥 서버로 부릅니다.

 

1. 환경변수 설정, 타입 선언

// .env.local

MONGODB_URI=mongodb+srv://<아이디>:<비밀번호>@어쩌구저쩌구appName=Cluster-XXXX
MONGO_DB=lovablePassages

 

최상단에 .env.local 파일에 MONGODB_URI 입력하는데 그 옆에 들어갈 내용은 친절하게도 아틀라스에서 다 알려준다. 아이디와 비밀번호만 따로 입력하면 된다.

 

MONGO_DB는 그냥 DB이름을 적었다.

 

// global.d.ts

import type { MongoClient } from "mongodb";

declare global {
  namespace globalThis {
    var _mongo: Promise<MongoClient>;
  }
}

 

최상단 위치에 global.d.ts 파일을 만들고 타입을 적어주었다.

 

 

2. MongoDB와 연결하는 파일 생성

// src/lib/database.ts

import { MongoClient } from "mongodb";

const url: string = process.env.MONGODB_URI!;

if (!url) {
  throw new Error("환경 변수가 설정되지 않았습니다.");
}

const options: any = { useNewUrlParser: true };
let connectDB: Promise<MongoClient>;

if (process.env.NODE_ENV === "development") {
  // 개발 중 재실행을 막음
  if (!global._mongo) {
    global._mongo = new MongoClient(url, options).connect();
  }
  connectDB = global._mongo;
} else {
  connectDB = new MongoClient(url, options).connect();
}

export { connectDB };

 

 이 부분은 철저하게 다른 블로그에 써진 것을 바탕으로 쓰고, 그 뒤에 오류를 조금 고쳤다. 

 

 

헷갈리거나 문제가 있던 부분들을 따로 적어놓자면,

1) 환경변수에 NEXT_ 어쩌구... 붙이지 않아도 된다. - 이건 클라이언트에서 가져올 때 필요하다. 우린 서버측에서 쓸 거여서 필요없다. 

2) url에 string 타입이라고 선언하면 가져온 환경변수가 string | undefined 라서 안 된다고 뜨는데 뒤에 !("non-null assertion operator") 를 붙여줌으로써 undefined가 아니라고 알려준다. 

3) 그래서 그 밑에 url이 없다면 환경 변수가 설정되지 않았음을 예외처리 해줘야 한다.

4) Next.js와 같은 서버 사이드 런타임 환경에서는 코드 변경 시 빠른 개발과 테스트를 위해 핫 리로딩(또는 라이브 리로딩)이 자주 발생한다. 이러한 환경에서 데이터베이스 연결과 같은 자원을 사용할 때, 매번 새로운 연결을 생성하는 것은 비효율적일 수 있다.

5) 내보낸 것은 'connectDB', 함수가 아니기 때문에 가져와 쓸 때 뒤에 ()를 붙이지 않는다. 

 

 

MongoClient 재사용: 개발 환경에서는 global._mongo 변수를 사용하여 MongoClient 인스턴스의 연결 프로미스를 저장하고 재사용한다. 이렇게 하면, 파일이 수정되어 서버가 재시작되더라도 이미 생성된 데이터베이스 연결을 재사용할 수 있다.

연결 생성 방지: if (!global._mongo) 조건을 통해 global._mongo에 이미 연결 프로미스가 저장되어 있다면 새로운 MongoClient 인스턴스를 생성하고 연결하는 과정을 건너뛴다. 즉, 불필요한 데이터베이스 연결 생성을 방지한다.

 

 

 

3. books의 'READ' api 설정하기(서버)

// src/app/api/books/route.ts

import { NextResponse } from "next/server";
import type { NextApiResponse } from "next";
import { connectDB } from "@/lib/database";

export async function GET() {
  try {
    if (!process.env.MONGO_DB) {
      throw new Error("MONGO_DB 환경변수가 설정되지 않았습니다.");
    }

    const client = await connectDB;
    const booksCollection = client.db(process.env.MONGO_DB).collection("books");
    const books = await booksCollection.find().toArray();


    return NextResponse.json({ books });
    
  } catch (error) {
    console.error("Database access error:", error);
    return NextResponse.json({ error: error });
  }
}

 

api 설정하는데 엄청난 수정 수정 수정 수정을 거쳤다.

서버쪽 api 만드는 게 거의 처음이기도 하고, next.js로 하는 건 더더욱 처음이고

13으로 바뀌면서 뭔가 대거 바뀌었는데, 검색하면 짬뽕되어 나와서 혼란스러웠다.

chatGPT도 정확하지 않고...

 

그리고 처음엔 스키마 만들어서 가져와서 거기에 추가하고 그래서 아틀라스에 안 뜨고 정말 혼란 그 자체 -_-

아직도 이 부분 이해를 못 했다. 스키마 만들고 추가하면 대체 어디서 볼 수 있는거지...? 대체 어느 DB에 떠돌고 있는 거야 그 정보들은?

 

계속 오류가 났던 중요 포인트

1) 함수 이름은 GET, POST 등으로 대문자로 작성한다.
2) (export) default를 사용하지 않는다. 그냥 export만 한다.
3) connectDB는 함수가 아니므로 뒤에 ()를 붙이지 않는다. 
4) return할 때 방식에 유의한다. 

 

 

 

💥 계속 헷갈리는 Response, NextResponse, NextApiResponse

 

 

 

chatGPT 4.0

더보기
import { NextResponse } from 'next/server'
 
export async function GET(request: Request) {
  return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
}

 

NextResponse는 status를 따로 적어주고 싶으면 이렇게 안에 적어주어야 한다. 

 

return NextResponse.json({
      status: 201,
      message: "Book successfully created",
      data: response.insertedId,
    });

이렇게도 가능. 

 

참고 : https://nextjs.org/docs/app/api-reference/functions/next-response

 

3-1. books 'READ' api 호출하기(클라이언트)

 

// src/components/units/books/Books.tsx

const [books, setBooks] = useState<iBookType[]>([]);

  useEffect(() => {
    async function fetchBooks() {
      try {
        const response = await axios.get("/api/books");

        setBooks(response.data.books);

      } catch (error) {
        console.error("Failed to fetch books:", error);
      }
    }
    fetchBooks();

  }, []);

 

axios를 이용해서 api를 호출했다. 

여기있는 애들이 

 

이런식으로 날아옵니다. 

 

 

4. books의 'CREATE' api 설정하기(서버)

READ를 참고해서 CREATE도 만들어보았다.

이번엔 블로그말고 공식 독스에서 찾아서 해봤다. 

몽고디비 독스에 보면 여러가지 방법이 있는데, 거기서 어떤 방식을 따라야 하는 지 그게 헷갈렸다. 

 

(최종 코드는 밑에)

🐛Troubleshooting 먼저...

 

1) insertOne 후 반환받는 값

const response = await booksCollection.insertOne({
      title,
      authors,
      publishedDate,
    });

 

처음엔 await를 안 붙이니까 Promise 통째로 와서 await를 붙여주니 

이렇게 왔다.

 

return NextResponse.json({
      status: 201,
      message: "Book successfully created",
      data: response.insertedId,
    });

브라우저 콘솔

 

MongoDB Altlas

 

이렇게 잘 들어옵니다. 기대는 안 했지만 ,로 author 구분하는 건 안 된다.

 

// src/app/api/books/route.ts

import { NextResponse } from "next/server";
import type { NextApiResponse } from "next";
import { connectDB } from "@/lib/database";


export async function POST(request: NextRequest) {
  try {
    if (!process.env.MONGO_DB) {
      throw new Error("MONGO_DB 환경변수가 설정되지 않았습니다.");
    }

    const { title, authors, publishedDate } = await request.json();
    const client = await connectDB;
    const booksCollection = client.db(process.env.MONGO_DB).collection("books");

    const response = await booksCollection.insertOne({
      title,
      authors,
      publishedDate,
    });

    // 성공 응답
    return NextResponse.json({
      status: 201,
      message: "Book successfully created",
      data: response.insertedId,
    });
  } catch (error) {
    console.error("Failed to create a new book:", error);
    return NextResponse.json({
      status: "error",
      message: "Failed to create a new book",
      error: error,
    });
  }
}

// 작동

 

계속 헷갈렸던 포인트

1) request.json() 에서 뽑아낸다. 
2) Request, NextRequest 헷갈렸는데 Request를 써도 큰 상관은 없는 것 같다. 

 

 

4-1. books 'CREATE' api 호출하기(클라이언트) 

// src/components/units/books/bookform/bookform.tsx

"use client";

import axios from "axios";
import { useState } from "react";

function BookForm() {
  const [title, setTitle] = useState("");
  const [authorName, setAuthorName] = useState("");
  const [publishedDate, setPublishedDate] = useState("");

  // 폼 제출 핸들러
  const handleSubmit = async (event) => {
    event.preventDefault();

    try {
      const response = await axios.post("/api/books", {
        title,
        authors: [{ name: authorName }],
        publishedDate: new Date(publishedDate),
      });

      alert("Book added successfully!");
      console.log(response.data);

      // 폼 초기화
      setTitle("");
      setAuthorName("");
      setPublishedDate("");
    } catch (error) {
      console.error("Failed to add the book:", error);
      alert("Failed to add the book.");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <h1>Add a New Book</h1>
      <div>
        <label>Title:</label>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          required
        />
      </div>
      <div>
        <label>Author Name:</label>
        <input
          type="text"
          value={authorName}
          onChange={(e) => setAuthorName(e.target.value)}
          required
        />
      </div>
      <div>
        <label>Published Date:</label>
        <input
          type="date"
          value={publishedDate}
          onChange={(e) => setPublishedDate(e.target.value)}
          required
        />
      </div>
      <button type="submit">Add Book</button>
    </form>
  );
}

export default BookForm;

 

이 부분은 일단 확인만 하려고 gpt한테 짜달라고 한 거여서,

그냥 axios를 썼고 post로 호출했다,,, 정도

 

계속 헷갈리는 부분이 "use client" 이건데, 뭔가 하다보면 모든 부분에서 이걸 붙이게 돼서 상당히 불편하다.

이러라고 만든 게 아닐 것 같은데.

 

일단 create->new로 폴더이름 바꾸고 퍼블리싱하기부터,,!

 

 

 


 

MongoDB Docs 

https://www.mongodb.com/docs/drivers/node/current/fundamentals/typescript/

 

TypeScript - Node.js Driver v6.5

Docs Home → Develop Applications → MongoDB Drivers → Node.js Driver In this guide, you can learn about the TypeScript features and limitations of the MongoDB Node.js driver. TypeScript is a strongly typed programming language that compiles to JavaScr

www.mongodb.com

 

 

Next.js Response Docs

https://nextjs.org/docs/app/api-reference/functions/next-response

 

Functions: NextResponse | Next.js

API Reference for NextResponse.

nextjs.org

 

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'프론트엔드✏️ > 개인공부' 카테고리의 다른 글

[react] 회고페이지) 배열 로컬스토리지 복원, 초기화 문제 배열 상태관리, setState / useEffect  (1) 2024.12.19
[flutter] 설치부터 난관.  (1) 2024.02.24
[firebase] Firebase 이메일 중복확인  (0) 2023.07.28
gyp ERR!  (0) 2023.04.07
[git]  (0) 2023.04.06
    '프론트엔드✏️/개인공부' 카테고리의 다른 글
    • [react] 회고페이지) 배열 로컬스토리지 복원, 초기화 문제 배열 상태관리, setState / useEffect
    • [flutter] 설치부터 난관.
    • [firebase] Firebase 이메일 중복확인
    • gyp ERR!
    당근먹는하니
    당근먹는하니

    티스토리툴바