Next.js에서 mongoose 연동해서 API 만들기

Tesseractjh

·

2022. 3. 27. 23:46

Next.js에서 API 만들기

Next.js에서 pages/api 디렉토리에서 파일/디렉토리 이름으로 API endpoint를 나타낼 수 있다.

pages/api/user.ts

export default function handler(req, res) {
  if (req.method === 'GET') {
    // GET 요청 처리
  } else if (req.method === 'POST') {
    // POST 요청 처리
  }
}

/user로 들어온 요청이 pages/api/user.ts에서 처리된다.

next-connect 라이브러리를 사용하면 마치 Express.js에서처럼 미들웨어의 형태로 코드를 작성할 수 있다.

import { NextApiRequest, NextApiResponse } from 'next';
import nextConnect from 'next-connect';

const handler = nextConnect<NextApiRequest, NextApiResponse>();

handler.get((req, res) => {
  // GET 요청 처리
});

handler.post((req, res) => {
  // POST 요청 처리
});

export default handler;

 

실제 프로젝트에서 사용한 방법

models/User.ts

import mongoose, { Schema } from 'mongoose';

export const UserSchema = new Schema(
  {
    userId: {
      type: String,
      unique: true,
      required: true,
      match: /[a-z0-9_]{4,16}/
    },
    userName: {
      type: String,
      required: true,
      match: /^[가-힣]{2,8}$/
    },
    nickname: {
      type: String,
      unique: true,
      required: true,
      match: /[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,8}/
    },
    phone: {
      type: String,
      required: true,
      match: /^\d{3}-\d{3,4}-\d{4}$/
    },
    email: {
      type: String,
      unique: true,
      required: true,
      match:
        /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/
    },
    money: {
      type: Number,
      default: 1000,
      min: 0,
      max: Number.MAX_SAFE_INTEGER
    }
  },
  { timestamps: true }
);

export default mongoose.models.User || mongoose.model('User', UserSchema);

lib/mongoose/dbConnect.ts

import mongoose from 'mongoose';
import { NextApiRequest, NextApiResponse } from 'next';

const { MONGODB_URI } = process.env;

export default async (
  req: NextApiRequest,
  res: NextApiResponse,
  next: Function
) => {
  if (!global.mongoose) {
    global.mongoose = await mongoose.connect(MONGODB_URI);
  }
  return next();
};

global에 mongoose를 캐시해서 mongoose.connect를 한 번만 실행하도록 한다.

lib/mongoose/createHandler.ts

import { NextApiRequest, NextApiResponse } from 'next';
import nextConnect, { Middleware } from 'next-connect';
import dbConnect from './dbConnect';

export default (
  ...middleware: Middleware<NextApiRequest, NextApiResponse>[]
) => {
  return nextConnect<NextApiRequest, NextApiResponse>({
    onError: (err, req, res) => {
      // 에러 발생시 처리 내용
    },
    onNoMatch: (req, res) => {
      // 어떠한 route에도 매치되지 않았을 때 처리 내용
    }
  }).use(dbConnect, ...middleware);
};

위에서 작성한 dbConnect 미들웨어를 포함하고, 인자로 다른 미들웨어를 받아서 사용할 수 있는 핸들러를 반환한다.

onError에는 에러 발생시 처리할 내용을, onNoMatch에는 아무런 route에도 매치되지 않았을 때 (404 error) 처리할 내용을 작성할 수 있다.

pages/api/user/index.ts

import mongoose from 'mongoose';
import createHandler from 'lib/mongoose/createHandler';

const handler = createHandler();

handler.get(async (req, res) => {
  // GET 요청 처리 내용
});

handler.post(async (req, res) => {
  // POST 요청 처리 내용
});

export default handler;

 

mongoose의 populate를 사용한 타 collection 참조

mongoose의 populate를 사용하여 ObjectId를 컬렉션 객체로 치환할 수 있다.

위에 User 스키마에 유저가 작성한 리뷰들을 모아놓은 reviews라는 field가 있고, 이 reviews라는 field에 Review collection의 ObjectId를 저장한다.

models/User.ts

export const UserSchema = new Schema(
  {
    ...
  
    reviews: {
      type: [Schema.Types.ObjectId],
      ref: 'Review'
    }
    
    ...
   }
 );

models/Review.ts

export const ReviewSchema = new Schema(
  {
    ...
  
    rating: {
      type: Number,
      required: true,
      min: 1,
      max: 5
    },
    content: {
      type: String,
      required: true,
      minlength: 10,
      maxlength: 1000
    },
    
    ...
   }
 );

pages/api/user/index.ts

...
import User from 'models/User';

handler.get(async (req, res) => {
  const data = await User.find({}).populate('reviews');
  res.json(data);
});

...
const { data } = axios.get('/api/user');
console.log(data);

// {
//   userId: 'gildong123',
//   userName: '홍길동',
//   nickName: '길동',
//   phone: '010-1234-5678',
//   email: 'gildong123@naver.com',
//   money: 100000,
//   reviews: [
//     {
//       rating: 4,
//       content: '배송이 빨라서 좋아요~'
//     },
//     {
//       rating: 5,
//       content: '향이 너무 좋아요!!'
//     },
//     {
//       rating: 1,
//       content: '포장이 뜯어져서 왔어요 ㅡㅡ'
//     }
//   ]
// }

만약 populate를 사용하지 않았다면 reviews에는 ObjectId만 담겨 있었을 것이다.

 

그런데 Next.js에서 mongoose를 사용할 때 populate를 하게 되면 MissingSchemaError가 발생한다.

pages/api/user/index.ts에서는 User 모델만 import 하고 있는데, populate를 하는 시점에서 단 한 번도 Review 모델이 import 되지 않았다면 이러한 현상이 발생한다.

이 문제를 해결하기 위해서 처음에 DB를 연결할 때 모든 모델들을 같이 생성하기로 하였다.

lib/mongoose/dbConnect.ts

import mongoose from 'mongoose';
import { NextApiRequest, NextApiResponse } from 'next';
import { UserSchema } from 'models/User';
import { ReviewSchema } from 'models/Review';

const { MONGODB_URI } = process.env;

export default async (
  req: NextApiRequest,
  res: NextApiResponse,
  next: Function
) => {
  if (!global.mongoose) {
    global.mongoose = await mongoose.connect(MONGODB_URI);
    mongoose.model('User', UserSchema);
    mongoose.model('Review', ReviewSchema);
  }
  return next();
};

 

 

참고자료

https://github.com/hoangvvo/next-connect

 

GitHub - hoangvvo/next-connect: The TypeScript-ready, minimal router and middleware layer for Next.js, Micro, Vercel, or Node.js

The TypeScript-ready, minimal router and middleware layer for Next.js, Micro, Vercel, or Node.js http/http2 - GitHub - hoangvvo/next-connect: The TypeScript-ready, minimal router and middleware lay...

github.com

https://itnext.io/using-mongoose-with-next-js-11-b2a08ff2dd3c

 

Using Mongoose with Next.js 11

A simple guide on how to use the Mongoose ORM for MongoDb with the latest version of the Next.js framework.

itnext.io

https://nextjs.org/docs/api-routes/introduction

 

API Routes: Introduction | Next.js

Next.js supports API Routes, which allow you to build your API without leaving your Next.js app. Learn how it works here.

nextjs.org

https://velog.io/@familyman80/%EB%AA%BD%EA%B3%A0DB-%EB%A1%9C%EC%BB%AC-ATLAS-%EA%B2%BD%EB%A1%9C

 

몽고DB, 몽구스를 NEXT에서 사용해보자. 초기설정 (첫설정부터 첫 저장까지)

로컬 : mongodb://localhost:포트번호/데이터베이스이름ex)mongodb://localhost:27017/데이터베이스이름atlas경로 :DB_CONN_STR=mongodb+srv://아이디:비밀번호@cluster0.자기주소.mongodb.net/데이터베이

velog.io