Next.js API 만들기 (2) MongoDB

LeeKyungwon·2024년 6월 5일
0

프론트엔드 

목록 보기
45/56
post-custom-banner

데이터베이스 : 데이터를 따로 저장하고 관리하는 프로그램

MongoDB 준비하기

MongoDB Atlas 사용법
현재 폴더에서 터미널을 열고 아래 명령어를 입력해서 패키지를 설치한다.

npm install mongoose

환경 변수

수많은 서버와 데이터베이스를 연결할 때 사용하는 것을 환경변수라고 한다.
코드 상에는 MONGODB_URI와 같은 값으로 데이터베이스 주소를 지정해 두고, 각 서버를 실행할 때마다 다른 데이터베이스 주소를 넣어준다.

환경 변수는 프로그램에서 실행 환경에 따라 다른 값을 지정할 수 있는 변수이다.
서버와 데이터베이스를 여러 개 쓰는 경우가 아니더라도 데이터베이스 주소와 같은 값을 소스코드에 그대로 쓰는 것은 위험하므로 반드시 환경 변수로 사용하는 것이 좋다.
node.js 환경에서는 이런 환경 변수들을 process.env라는 객체를 통해서 참조할 수 있다.

Next.js에서 환경 변수 추가하기

Next.js에서는 기본적으로 dotenv라는 라이브러리를 지원하는데, 이 라이브러리는 .env 같은 이름의 파일에서 환경 변수들을 저장해 두면, Node.js 프로젝트를 실행할 때 환경 변수로 지정해 주는 라이브러리이다. 이때 주의할 점은 .env 파일 같은 건 소스 코드에 포함시키면 안 된다는 것이다.

Next.js 프로젝트에서는 기본적으로 dotenv 설정이 되어 있어서, .env.local 같은 파일을 추가하면 손쉽게 환경 변수를 추가할 수 있다.

.env.local

MONGODB_URI=mongodb+srv://admin:blahblah@.clusterName.blahblah.mongodb.net/databaseName?retryWrites=true&w=majority

예를 들어 이렇게 추가한 값을 process.env.MONGODB_URI로 참조할 수 있다.

export default function handler(req, res) {
  const DB_URI = process.env.MONGODB_URI;
  // 데이터베이스 접속 ...
}

Next.js에서 사용하는 특별한 환경 변수

환경 변수가 웹 사이트에 노출되는 사고를 막기 위해서 Next.js에서는 클라이언트 사이드에서 사용하는 환경 변수에 특별한 접두사를 사용한다.
NEXT_PUBLIC_이라고 이름 붙이면 이 환경 변수는 클라이언트 사이드에서도 사용할 수 있다.
예를 들어서 클라이언트 사이드에서 현재 사이트의 호스트 주소를 저장해 두고 참조하고 싶다면 아래와 같이 NEXT_PUBLIC_HOST 라는 이름으로 사용하면 된다.

MONGODB_URI=mongodb+srv://admin:blahblah@cluster0.blahblah.mongodb.net/databaseName?retryWrites=true&w=majority
NEXT_PUBLIC_HOST=http://localhost:3000
export default Home() {
  // 페이지 컴포넌트에서는 아래와 같이 사용
  return (
    <>호스트 주소: {process.env.NEXT_PUBLIC_HOST}</>
  );

Next.js 공식 홈페이지 - 환경변수

Next.js에서 데이터베이스 연동하기

공식 레포지토리에 있는 코드를 가져왔다.

db/dbConnect.js

import mongoose from "mongoose";

const MONGODB_URI = process.env.MONGODB_URI;

if (!MONGODB_URI) {
  throw new Error(
    "Please define the MONGODB_URI environment variable inside .env.local"
  );
}

let cached = global.mongoose;

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

async function dbConnect() {
  if (cached.conn) {
    return cached.conn;
  }

  if (!cached.promise) {
    const opts = {
      bufferCommands: false,
    };

    cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
      return mongoose;
    });
  }

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

  return cached.conn;
}

export default dbConnect;

MONGODB URI는 환경변수에서 가져와서 쓰고 있다.
MONGO Atlas로 접속해서 Connect->Drivers에 있는 application code를 복사하고, .env.local 파일을 만들어

MONGODB_URI= 복사한거 붙여넣기

따로 메모해둔 유저네임과 비밀번호도 넣어준다.
주고 뒷부분의 경로 부분에 원하는 데이터베이스 이름도 적어준다.
cluster0.haezxz3.mongodb.net/shortit-> 데이터베이스 이름을 shortit이라고 적은 예시

사용 예시

pages/api/short-links/index.js

import dbConnect from "@/db/dbConnect";
import mongoose from "mongoose";

export default async function handler(req, res) {
  await dbConnect();
  console.log(mongoose.connection.readyState);
  
  switch (req.method) {
    case "PATCH":
      res.send("ShortLink 수정");
      break;

    case "GET":
      res.send("ShortLink 조회");
      break;

    default:
      res.send();
      break;
  }
}

다음과 같이 dbConnect()와 콘솔로그를 사용하여 연동을 했다.
request.http

POST http://localhost:3000/api/short-links/
Content-Type: application/json

{
  "title":"codeit"
}

이 리퀘스트를 실행하면
콘솔에 1이 찍힌다. (connected 되었다는 의미)

모델 만들기

틀을 미리 만들어두고 데이터를 부어서 사용하기 편하게 만드는 것
데이터를 참조하는 것 말고도 데이터베이스에 있는 데이터를 수정하거나 삭제하는 등 다양한 작업을 편하게 할 수 있다.

mongoose에서 모델을 만들려면 일단 스키마라는 것을 만든다.
Schema는 모델의 구조와 프로퍼티를 정하는 것
mongoose.model 이용해서 모델을 만든다.
첫번째 아규먼트가 모델의 이름, 이 이름은 mongoose.models[]프로퍼티에서 활용한다.
db/models/ShortLink.js

import mongoose from "mongoose";

const shortLinkSchema = new mongoose.Schema({
  title: { type: String, default: "" },
  url: { type: String, default: "" },
  shortUrl: { type: String, default: "" },
});

const ShortLink =
  mongoose.models["ShortLink"] || mongoose.model("ShortLink", shortLinkSchema);
export default ShortLink;

위 코드에서는 mongoose.models에 있는지 확인하고 없다면 모델을 생성한다.
모델을 여러번 만들지 않으려고 이런 구조를 사용

생성날짜, 수정날짜는 몽구스에서 편하게 추가할 수 있다.

const shortLinkSchema = new mongoose.Schema(
  {
    title: { type: String, default: "" },
    url: { type: String, default: "" },
    shortUrl: { type: String, default: "" },
  },{ 
    timestamps: true,
  }
);

이렇게 timestamps: true를 추가해주면 된다.

아무 엔드포인트에 가서

console.log(ShortLink);

콘솔로그를 출력해 모델이 잘 생성되었는지 확인할 수 있다.

도큐먼트 생성, 조회하기

생성: Model.create()

req.body에는 다양한 값들이 들어올 수 있지만 기본적으로 몽구스에서는 우리가 만든 스키마에 맞지 않는 값들은 무시하기 때문에 그대로 써도 된다.
데이터베이스에 저장하는 작업은 비동기 작업이기 때문에 await로 기다려준다.
pages/api/short-links

import dbConnect from "@/db/dbConnect";
import ShortLink from "@/db/models/ShortLink";

export default async function handler(req, res) {
await dbConnect();
console.log(ShortLink);

switch (req.method) {
  case "POST":
    const newShortLink = await ShortLink.create(req.body);
    res.status(201).send(newShortLink);
    break;

리퀘스트로

POST http://localhost:3000/api/short-links/
Content-Type: application/json

{
  "title":"codeit",
  "url":"https://codeit.kr"
}

POST 요청을 보내주면 생성된 데이터가 리스폰스로 온다.
리스폰스로 온 것들 중에
_id 는 MongoDB에서 데이터를 서로 구분해주기 위해 만들어주는 아이디이고,
__v는 monggose가 내부적으로 활용하는 값이다.

id 값으로 도큐먼트 조회: Model.findById()

pages/api/short-links/[id].js

import dbConnect from "@/db/dbConnect";
import ShortLink from "@/db/models/ShortLink";

export default async function handler(req, res) {
await dbConnect();
const { id } = req.query;

switch (req.method) {
  case "GET":
    const shortLink = await ShortLink.findById(id);
    res.send(shortLink);
    break;

  case "PATCH":
    res.send("ShortLink 수정");

  default:
    res.send();
    break;
}
}

request.http

GET http://localhost:3000/api/short-links/POST 요청으로 받은 아이디

참고) Request 사이에는 ###로 구분을 해줘야 한다.

여러 개 조회: Model.find()

pages/api/short-links

import dbConnect from "@/db/dbConnect";
import ShortLink from "@/db/models/ShortLink";

export default async function handler(req, res) {
  await dbConnect();

  switch (req.method) {
    case "POST":
      const newShortLink = await ShortLink.create(req.body);
      res.status(201).send(newShortLink);
      break;

    case "GET":
      const shortLinks = await ShortLink.find();
      res.send(shortLinks);
      break;

    default:
      res.status(404).send();
  }
}

request.http

GET http://localhost:3000/api/short-links/

리퀘스트를 보내면 아까 만들었던 도큐먼트가 배열 안에 담겨서 온다.

도큐먼트 수정, 삭제하기

도큐먼트 수정하기: Model.findByIdAndUpdate()

수정하는 것은 findByIdAndUpdate(id, 업데이트할 데이터)를 사용하면 된다.

findByIdAndUpdate 이 함수의 리턴값은 수정하기 전의 도큐먼트 값이다. 그래서 세번째 아규먼트에 옵션을 추가해서 new: true로 만들어준다.
findByIdAndUpdate(id, 업데이트할 데이터, {new: true})
이러면 업데이트 된 도큐먼트가 잘 도착한다.

pages/api/short-links/[id].js

import dbConnect from "@/db/dbConnect";
import ShortLink from "@/db/models/ShortLink";

export default async function handler(req, res) {
  await dbConnect();
  const { id } = req.query;

  switch (req.method) {
    case "GET":
      const shortLink = await ShortLink.findById(id);
      res.send(shortLink);
      break;

    case "PATCH":
      const updatedShortLink = await ShortLink.findByIdAndUpdate(id, req.body, {
        new: true,
      });
      res.send(updatedShortLink);
      break;

    default:
      res.send();
      break;
  }
}

request.http

PATCH http://localhost:3000/api/short-links/6667d9059b6b14451796c3a8
Content-Type: application/json

{
  "title":"edit"
}

도큐먼트 삭제하기: Model.findByIdAndDelete()

findByIdAndDelete(id)함수를 사용한다.

    case "DELETE":
      await ShortLink.findByIdAndDelete(id);
      res.status(204).send();
      break;
DELETE http://localhost:3000/api/short-links/6667f7549b6b14451796c3b5

그 외 자주 사용하는 함수들

조건으로 하나만 조회

아규먼트로 조건을 넘겨주고 해당하는 도큐먼트를 하나만 조회한다.

const shortLink = await ShortLink.findOne({ shortUrl: 'c0d317' });

조건으로 업데이트하기

첫 번째 아규먼트로 넘겨준 조건에 해당하는 도큐먼트를 업데이트한다. 두 번째 아규먼트로는 업데이트할 값을 넘겨준다.

await ShortLink.updateOne({ _id: 'n3x7j5' }, { ... }); // 업데이트만 하고 업데이트 된 값을 리턴하지는 않음

const updatedShortLink = await ShortLink.findOneAndUpdate({ _id: 'n3x7j5' }, { ... });

조건으로 삭제하기

아규먼트로 넘겨준 조건에 해당하는 도큐먼트를 삭제한다.

await ShortLink.deleteOne({ _id: 'n3x7j5' }, { ... }); // 삭제만 하고  기존 값을 리턴하지는 않음

const deletedShortLink = await ShortLink.findOneAndDelete({ _id: 'n3x7j5' }, { ... });
post-custom-banner

0개의 댓글