본문 바로가기
프론트엔드

Next.js + MongoDB

반응형

이제 우리는 Next.js로 프로젝트를 시작합니다.

기획 중인 프로젝트는 코드드림의 어드민 서비스를 만들어볼 계획입니다.

 

본 포스팅에서는 Next.js 프로젝트에서 DB를 연동하는 과정을 소개합니다.

DB는 MongoDB를 사용했으며 Mongoose라는 라이브러리를 사용했습니다. MongoDB와 Mongoose에 대해서는 메리님의 포스팅에서 확인하실 수 있습니다.

 

예제에 사용된 버전은 다음과 같습니다.

  • Next.js 14
  • Mongoose 8.0.3
  • Typescript 5

1. MongoDB 설정

1-1) 프로젝트 생성

MongoDB 계정을 만들고 사이트에 접속해 프로젝트를 생성합니다.

 

1-2) Database 생성

생성된 프로젝트에서 Create를 통해 Database를 생성합니다.

 

1-3) Database 설정

우리는 무료를 사용할 것이기 때문에 맨 우측에 있는 M0를 선택합니다. 

무료 버전에서는 저장소의 512MB를 제한합니다.

 

1-4) 커넥션 정보 입력

DB에 커넥션할 때 입력할 아이디/패스워드 정보를 입력합니다. 이후에 나오는 단계는 Close로 넘어가도 무방합니다.

 

1-5) 보안설정

Database를 생성하게 되면 기본적으로 로컬 IP만 접속이 가능하도록 등록되어 있습니다.

SECURITY - Network Access에 접속합니다.

 

1-6) IP 추가

어디에서든 접속이 가능하도록 IP를 허용합니다.

ADD IP ADDRESS 클릭 후 ALLOW ACCESS FROM ANYWHERE을 통해 0.0.0.0/0 IP를 추가합니다.

 

 

1-7) 샘플 데이터 생성

이제 DB 연동 후 예제로 사용할 데이터를 만들어보겠습니다.

DEPLOYMENT - Database 클릭 후 Browse Collections를 클릭합니다.

 

Load a Sample Dataset를 클릭합니다.

 

몇 분 후 좌측 탭에 샘플 데이터베이스가 생성됩니다.

본 포스팅에서는 sample_guides 데이터베이스의 Planets 컬렉션을 사용했습니다.

 

 

2. Mongoose 설정

이제 Next 프로젝트로 돌아옵니다.

 

2-1) Mongoose 라이브러리 설치

Mongoose 라이브러리를 설치합니다.

npm i mongoose
npm i ts-mongoose mongoose @types/mongoose

 

예제에 디렉토리는 아래와 같이 되어있습니다.

├─ app
│  ├─ api
│  │  └─ planet
│  │     └─ route.ts
│  ├─ favicon.ico
│  ├─ globals.css
│  ├─ layout.tsx
│  ├─ lib
│  │  └─ dbConnect.ts
│  ├─ models
│  │  └─ planet.ts
│  ├─ page.tsx
│  └─ planet
│     └─ page.tsx

 

2-2) .env 파일 생성

env 파일은 환경 변수를 정의하여 사용합니다. Next.js는 .env.local에 정의된 환경 변수를 로드할 수 있는 기능을 제공합니다.

루트 경로에 env.local 이라는 이름으로 파일을 생성하여 DB 연결 정보와 API 호출에 사용될 베이스 호스트를 등록합니다.

 

.env.local

CODEDREAMDB_URI=mongodb+srv://codedream:패스워드입력@DB클러스터명.semt1bz.mongodb.net/데이터베이스명?retryWrites=true&w=majority
NEXT_PUBLIC_DEV_URL=http://localhost:3000
  • 패스워드, DB 클러스터 : 1-3), 1-4)를 통해 생성한 정보를 입력합니다.
  • 데이터베이스명 : 예제로 사용하기로한 sample_guides 입력합니다.

 

패스워드에 " : / ? # [ ] @ " 같은 특수문자가 들어간 경우 아래와 같은 에러가 발생합니다. 여기를 참고하세요. 

Error connecting to MongoDB: MongoAPIError: URI must include hostname, domain name, and tld

 

이런 경우 인코딩 후 패스워드를 입력해주어야 합니다. 개발자도구에서 encodeURIComponent() 함수를 이용해 간단히 인코딩할 수 있습니다.

 

2-3) DB 커넥션 설정

DB 연결을 시도하여 연결이 된 이후부터는 global에 캐싱하여 사용합니다. 

connect() 함수는 Promise를 반환하기 때문에 .then() 함수를 사용해 처리 결과를 통해 mongoose 커넥션을 저장합니다.

 

lib/dbConnects.js

import _mongoose, { connect } from "mongoose";

const CODEDREAMDB_URI = process.env.CODEDREAMDB_URI;

declare global {
  var mongoose: {
    promise: ReturnType<typeof connect> | null;
    conn: typeof _mongoose | null;
  };
}

if (!CODEDREAMDB_URI) {
  throw new Error("DB 접속 정보를 확인해주세요.");
}

let cached = global.mongoose;

if (!cached) {
  cached = global.mongoose = { conn: null, promise: null };
}

async function dbConnect() {
  if (cached.conn) {
    console.log("캐싱된 커넥션을 사용합니다.");
    return cached.conn;
  }
  if (!cached.promise) {
    const opts = {
      bufferCommands: false,
    };
        
    cached.promise = connect(CODEDREAMDB_URI!, opts)
      .then((mongoose) => {
        console.log("DB 커넥션이 생성되었습니다.");
        return mongoose;
      })
      .catch((error) => {
        console.error("DB 커넥션에 실패했습니다.");
        throw error;
      });
  }

  try {
    cached.conn = await cached.promise;
  } catch (e) {
    cached.promise = null;
    throw e;
  }

  return cached.conn;
}

export default dbConnect;

 

2-4) 스키마 생성

RDB의 테이블은 Mongoose에서 스키마로 정의됩니다. 스키마 정의에 의해 컴파일된 결과로 모델을 생성하게 됩니다. 그리고 모델을 통해 MongoDB로부터 데이터를 조회합니다.

예제에서 사용될 스키마의 필드 타입은 다음과 같습니다.

 

ObjectId 타입은 MongoDB에서 각각의 Document를 식별할 수 있는 id입니다. 이 타입은 json 형태로 사용할 경우 String으로 변환하는 작업이 필요합니다.

 

models/planet.ts

import { Schema, InferSchemaType } from "mongoose";
const mongoose = require("mongoose");

const PlanetSchema = new Schema({
  _id: Schema.Types.ObjectId,
  name: String,
  orderFromSun: Number,
  hasRings: Boolean,
  mainAtmosphere: Array,
  surfaceTemperatureC: {
    min: Number,
    max: Number,
    mean: Number,
  },
});

type PlanetType = InferSchemaType<typeof PlanetSchema>;

export default mongoose.models.Planets || mongoose.model("Planets", PlanetSchema);
export type { PlanetType };

 

2-5) API 생성

Next13부터는 예약어가 route.ts로 바뀌었고 HTTP 메소드에 따라 사용되는 handler 방식이 아닌 HTTP 메소드명(GET, POST, ...)이 사용됩니다.

db를 통해 데이터를 조회하기 때문에 await dbConnect()를 통해 db 연결이 보장되어야 합니다. 이후에 생성한 Planet 모델을 통해 데이터를 불러옵니다.

 

api/planet/routes.ts

import dbConnect from "../../lib/dbConnect";
import Planet from "../../models/planet";

export async function GET(req: Request) {
  try {
    await dbConnect();

	// 데이터 조회
    const planetList = await Planet.find();

    console.log(planetList);

    return Response.json({ data: planetList });
  } catch (error) {
    console.error("Error connecting to MongoDB:", error);
    return Response.error();
  }
}

 

여기까지 작업을 하게 되면 샘플 데이터를 조회할 수 있습니다.

npm run dev를 통해 서버를 실행하고 /api/planet으로 접속해봅니다.

 

2-6) 조회 페이지 추가

이번엔 샘플 데이터를 호출해 화면에 표시해보겠습니다.

2-5)에서 생성한 API를 호출하기 위해 getData() 함수를 정의합니다. 여기서 사용된 NEXT_PUBLIC_DEV_URL은 2-2)에서 정의한 주소입니다.

 

planet/pages.tsx

import { PlanetType } from "../models/planet";

async function getData() {
  const response = await fetch(`${process.env.NEXT_PUBLIC_DEV_URL}/api/planet`);

  if (!response.ok) {
    throw new Error("Failed to fetch data");
  }

  return response.json();
}

export default async function Planet() {
  const response = await getData();
  const planetList = response.data;
  
  return (
    <main>
      <div>행성 리스트</div>
      <div>
        {planetList.map((item: PlanetType) => (
          // ObjectId -> String 변환
          <div key={item._id?.toString()}>
            <h3>
              {item.name} (Ring : {item.hasRings == true ? "O" : "X"})
            </h3>
            <p>Order from Sun: {item.orderFromSun}</p>
            <p>Main Atmosphere:</p>
            <ul>
              {item.mainAtmosphere!.map((atmosphere, index) => (
                <li key={index}>{atmosphere}</li>
              ))}
            </ul>
            <p>Surface TemperatureC:</p>
            <ul>
              <li>min : {item.surfaceTemperatureC!.min}</li>
              <li>max : {item.surfaceTemperatureC!.max}</li>
              <li>mean : {item.surfaceTemperatureC!.mean}</li>
            </ul>
          </div>
        ))}
      </div>
    </main>
  );
}

 

 

 

여기까지 Next 프로젝트에서 MongoDB를 연결해봤습니다.

아직까지는 Next.js, Typescript가 많이 어색한데요. 디렉토리를 나누는 것 부터 정답이 없는 것 같아 고민을 많이 했습니다.

Database 연결, 스키마 정의, API 호출 등 저희가 고민하며 해결한 이 과정이 누군가에게 도움이 되길 바랍니다.

 

참고문서
 

Mongoose v8.0.3: Getting Started

Getting Started First be sure you have MongoDB and Node.js installed. Next install Mongoose from the command line using npm: npm install mongoose --save Now say we like fuzzy kittens and want to record every kitten we ever meet in MongoDB. The first thing

mongoosejs.com

 

Next.js 13 + MongoDB - User Registration and Login Tutorial with Example App | Jason Watmore's Blog

Tutorial built with Next.js 13.2.4, React 18.2.0 and MongoDB In this tutorial we'll go through an example of how to build a simple user registration, login and user management (CRUD) application with Next.js and MongoDB. Tutorial contents Example Next.js +

jasonwatmore.com

 

How to Integrate MongoDB Into Your Next.js App | MongoDB

Learn how to easily integrate MongoDB into your Next.js application with the official MongoDB package.

www.mongodb.com

 

반응형

'프론트엔드' 카테고리의 다른 글

MongoDB는 무엇인가?  (0) 2024.01.10
물러가라! Next.js (기초)  (0) 2024.01.10
자 이제 시작이야, Next 세계로  (3) 2024.01.04
React 맛보기 - React Hooks  (1) 2024.01.03
Hello Typescript - part.2  (1) 2024.01.02