프로그래밍/JS, Node.js

[Next.js] MongoDB 연결하기 (next-connect, typescript)

Lou Park 2022. 6. 26. 01:21

next-connect는 Next.js에서 미들웨어를 사용할 수 있게 도와주는 라이브러리다. 여기서 말하는 미들웨어는 웹 서버에서의 미들웨어(Middleware)와 의미가 통하는데, 요청과 응답 사이 중간에서 한번 거치면서 수행되는 함수다. 보통 여기서 연결된 DB 객체를 넘겨준다던가 세션 관리를 하게된다. 

express의 middleware

 

시작하기

몽고 DB를 설치하자. 귀찮게 몽고DB설정까지는 다루지 않을 것이다. 사실...패키지 설치부터 말하는 것 조차 귀찮지만!ㅋㅋㅋ

yarn add mongodb

middleware라는 폴더를 생성하고, 그 아래에 database.ts를 추가시켜 주자. 이 파일에서는 연결된 mongodb가 없다면 새로 연결해서 req 객체에 담아 넘겨줄 것이다.

src/
├── pages/
│   └── api/
│       └── test.ts
└── middlewares/
    └── database.ts

 

 

database.ts

NextApiRequest를 상속하는 DatabaseRequest 인터페이스를 생성한다. 여기에는 직접만든 Database라는 유형의 객체를 할당할 수 있다. Database는 보다시피 MongoClient를 가지고 있다. 

import * as mongo from "mongodb";
import { NextApiRequest } from 'next';
const MONGO_URI = `mongodb://${process.env.MONGO_HOST}:${process.env.MONGO_PORT}`

interface Database {
    client?: mongo.MongoClient;
}

export interface DatabaseRequest extends NextApiRequest {
    db: Database
}

 

이제 연결부로 가보자면, connectDB라는 함수가 핵심이다. 많은 Next.js - MongoDB 커넥션 예시 코드에서는 MongoDB연결중 동시에 접근했을때 연결이 여러개가 되는 문제를 안고있다.

const connectDB = (async function () {
  console.log(`Start connecting to mongodb...${MONGO_URI}`);
  const mongoClient: mongo.MongoClient = new mongo.MongoClient(MONGO_URI, {
    connectTimeoutMS: 5000,
  });
  const client = await mongoClient.connect();
  const database: mongo.Db = client.db(process.env.MONGO_DATABASE);
  await createIndexes(database); // optional
  return {
    client
  };
})();

따라서 MongoDB가 연결중일때는 새로 연결을 만들지 않도록 Promise를 이용해 원자적으로 처리해둘 필요가 있다. 아래 코드를 보고 콘솔에 메세지가 어떻게 찍힐지 생각해보자.

 

function sleep(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

const TEST = (async function() {
    console.log("calling TEST")
    await sleep(300)
    console.log("TEST!")
    return 
})();

for (var i = 0; i < 3; i++) {
    await TEST;
}

 

300ms안에 모든 루프를 돌아버렸으니 TEST는 첫번째 단 한번만 찍히게 된다.(이러한 기법을 뭐라고 부르는지는 잘 모르겠다.)

 

calling TEST
TEST

 

connectDB도 다음과같이 사용하면 된다. connectDB가 동시에 단 한번만 호출될 것을 알기 때문에, 인덱스를 만드는 함수인 createIndexes에서도 인덱스를 만들었냐, 아니냐에대한 추가적인 boolean flag가 필요없게된다.

 

 

async function createIndexes(database: mongo.Db) {
  await database.collection("characters").createIndex(
    {
      unique_id: 1,
    },
    {
      unique: true,
      name: "unique_id_idx",
    }
  );
}

export async function getDatabase(req: DatabaseRequest, res: any, next: any) {
  try {
    req.db = await connectDB;
  } catch (err: unknown) {
    console.error(err);
  } finally {
    return next();
  }
}

 

next-connect 사용하기

이제는 pages/api/test.ts로 넘어와서 연결된 DB를 받아서 사용해보겠다. 별건 없고...handler.use(getDatabase).use(otherMiddleware)같은 식으로 체이닝해서 사용할 수 있다는 코멘트를 하고 마치겠다!

import { getDatabase, DatabaseRequest } from "@middlewares/database";
import { NextApiResponse } from "next";
import nc from "next-connect";

const handler = nc();

handler.use(getDatabase).post<DatabaseRequest, NextApiResponse>(async (req, res) => { 
	// ex) await req.db.client....
	res.status(200).json({ 
        hasConnection: req.db.client != null
    });
})

export default handler