MongoDB 연결 설정 추가
MONGODB_URI
) 설정API 라우트 변경
클라이언트 컴포넌트 수정
id
대신 _id
사용MongoDB Atlas 가입하기
mongodb+srv://username:<password>@cluster0...
).env
파일을 생성합니다MONGODB_URI=mongodb+srv://username:<password>@cluster0.xxxxx.mongodb.net/<database>?retryWrites=true&w=majority
<password>
는 MongoDB Atlas에서 생성한 데이터베이스 사용자의 실제 비밀번호로 교체<database>
는 사용하고 싶은 데이터베이스 이름으로 교체 (예: "blog")npm install mongodb mongoose
.env
파일 생성:
MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/<database>?retryWrites=true&w=majority
if (이미_연결되어있나?) {
기존_연결_재사용();
} else {
새로운_연결_생성();
}
src/lib/mongodb.js
파일 생성:
import mongoose from 'mongoose';
// MongoDB 연결 문자열을 환경변수에서 가져옴
const MONGODB_URI = process.env.MONGODB_URI;
// 연결 문자열이 없으면 에러 발생
if (!MONGODB_URI) {
throw new Error('MONGODB_URI must be defined');
}
// 전역 변수에 mongoose 연결 정보를 저장
// 이렇게 하면 서버가 재시작되어도 연결이 유지됨
let cached = global.mongoose;
// 처음 실행될 때는 cached가 없으므로 초기화
if (!cached) {
cached = global.mongoose = {
conn: null, // 현재 연결 객체
promise: null // 연결 시도중인 Promise
};
}
async function connectDB() {
// 이미 연결되어 있다면 그 연결을 재사용
if (cached.conn) {
return cached.conn;
}
// 연결 시도중이 아니라면 새로운 연결 시도
if (!cached.promise) {
const opts = {
bufferCommands: false, // 연결되기 전에 명령어 버퍼링 비활성화
};
// MongoDB 연결 시도
cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
return mongoose;
});
}
try {
// 연결이 완료될 때까지 대기
cached.conn = await cached.promise;
} catch (e) {
// 연결 실패시 promise 초기화하고 에러 던지기
cached.promise = null;
throw e;
}
return cached.conn;
}
export default connectDB;
{
title: { // 제목 필드
type: String, // 문자열 타입
required: true, // 필수 입력
trim: true // 앞뒤 공백 제거
},
content: { // 내용 필드
type: String,
required: true
}
}
models/Post.js
파일 생성:
import mongoose from 'mongoose';
// 이미 모델이 있다면 그것을 사용, 없다면 새로 생성
const PostSchema = new mongoose.Schema({
title: {
type: String,
required: [true, '제목을 입력해주세요.'], // 필수 입력
trim: true, // 앞뒤 공백 제거
},
content: {
type: String,
required: [true, '내용을 입력해주세요.'],
},
createdAt: {
type: Date,
default: Date.now, // 기본값은 현재 시간
}
});
// 모델이 이미 있다면 그것을 사용, 없다면 새로 생성
export default mongoose.models.Post || mongoose.model('Post', PostSchema);
app/api/posts/route.js
)import { NextResponse } from 'next/server';
// MongoDB 연결을 위한 유틸리티 함수 가져오기
import connectDB from '@/lib/mongodb';
// MongoDB의 Post 모델 가져오기
import Post from '@/models/Post';
export async function GET() {
try {
// MongoDB 연결
await connectDB();
// Post 모델을 사용해 모든 게시글을 찾고, 생성일 기준 내림차순 정렬
const posts = await Post.find({}).sort({ createdAt: -1 });
return NextResponse.json(posts);
} catch (error) {
return NextResponse.json(
{ error: '게시글을 불러오는데 실패했습니다.' },
{ status: 500 }
);
}
}
export async function POST(req) {
try {
// MongoDB 연결
await connectDB();
const data = await req.json();
if (!data.title || !data.content) {
return NextResponse.json(
{ error: '제목과 내용은 필수입니다.' },
{ status: 400 }
);
}
// Post 모델을 사용해 새 게시글 생성
const post = await Post.create(data);
// 클라이언트 응답
return NextResponse.json(post, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: '게시글 작성에 실패했습니다.' },
{ status: 500 }
);
}
}
app/api/posts/[id]/route.js
)MongoDB의 각 문서(document)를 고유하게 식별하는 12바이트의 식별자
507f1f77bcf86cd799439011
isValidObjectId 함수의 역할
const isValidObjectId = (id) => mongoose.Types.ObjectId.isValid(id);
주어진 id가 MongoDB의 ObjectId 형식에 맞는지 검사
예를 들면 :
isValidObjectId('507f1f77bcf86cd799439011') // true
isValidObjectId('invalid-id') // false
// app/api/posts/[id]/route.js
import { NextResponse } from 'next/server';
import Post from '@/models/Post';
// MongoDB의 ObjectId 검증을 위한 mongoose import
import mongoose from 'mongoose';
// MongoDB 연결을 위한 유틸리티 함수 가져오기
import connectDB from '@/lib/mongodb';
// MongoDB의 ObjectId가 유효한지 검사하는 함수
const isValidObjectId = (id) => mongoose.Types.ObjectId.isValid(id);
export async function GET(req, { params }) {
try {
// MongoDB 연결
await connectDB();
// Next.js 13에서는 params를 비동기로 처리하도록 변경되었다.
// 일반 값을 Promise로 변환
// const regular = 1;
// const promise = Promise.resolve(regular); // Promise { 1 }
// Promise의 결과값을 추출
// const result = await promise; // 1
const resolvedParams = await Promise.resolve(params);
// 게시글 ID가 유효한 MongoDB ObjectId 형식인지 검사
if (!isValidObjectId(resolvedParams.id)) {
return NextResponse.json(
{ error: '유효하지 않은 게시글 ID입니다.' },
{ status: 400 }
);
}
// Post 모델을 사용해 특정 ID의 게시글 찾기
const post = await Post.findById(resolvedParams.id);
if (!post) {
return NextResponse.json(
{ error: '게시글을 찾을 수 없습니다.' },
{ status: 404 }
);
}
return NextResponse.json(post);
} catch (error) {
return NextResponse.json(
{ error: '게시글을 불러오는데 실패했습니다.' },
{ status: 500 }
);
}
}
export async function PUT(req, { params }) {
try {
// MongoDB 연결
await connectDB();
const resolvedParams = await Promise.resolve(params);
// ID 유효성 검사
if (!isValidObjectId(resolvedParams.id)) {
return NextResponse.json(
{ error: '유효하지 않은 게시글 ID입니다.' },
{ status: 400 }
);
}
const data = await req.json();
// findByIdAndUpdate: ID로 게시글을 찾아 업데이트
// $set: MongoDB 업데이트 연산자, new: true는 업데이트된 문서 반환
const post = await Post.findByIdAndUpdate(
resolvedParams.id,
{ $set: data },
{ new: true, runValidators: true }
);
if (!post) {
return NextResponse.json(
{ error: '게시글을 찾을 수 없습니다.' },
{ status: 404 }
);
}
return NextResponse.json(post);
} catch (error) {
return NextResponse.json(
{ error: '게시글 수정에 실패했습니다.' },
{ status: 500 }
);
}
}
export async function DELETE(req, { params }) {
try {
// MongoDB 연결
await connectDB();
const resolvedParams = await Promise.resolve(params);
// ID 유효성 검사
if (!isValidObjectId(resolvedParams.id)) {
return NextResponse.json(
{ error: '유효하지 않은 게시글 ID입니다.' },
{ status: 400 }
);
}
// findByIdAndDelete: ID로 게시글을 찾아 삭제
const post = await Post.findByIdAndDelete(resolvedParams.id);
if (!post) {
return NextResponse.json(
{ error: '게시글을 찾을 수 없습니다.' },
{ status: 404 }
);
}
return NextResponse.json({ message: '게시글이 삭제되었습니다.' });
} catch (error) {
return NextResponse.json(
{ error: '게시글 삭제에 실패했습니다.' },
{ status: 500 }
);
}
}
기존 클라이언트 컴포넌트의 대부분은 그대로 사용할 수 있습니다. MongoDB의 _id
를 사용하도록 수정이 필요한 부분만 변경하면 됩니다.
app/posts/page.js
){posts.map((post) => (
<Link
key={post._id} // id 대신 _id 사용
href={`/posts/${post._id}`} // id 대신 _id 사용
className="cursor-pointer block"
>
<h2>{post.title}</h2>
<p>{post.content}</p>
<span>{new Date(post.createdAt).toLocaleDateString()}</span>
</Link>
))}
날짜 포맷팅 함수는 여러 컴포넌트에서 재사용할 수 있으므로, utils
폴더에 따로 만드는 것이 좋습니다
// utils/formatDate.js
export const formatDate = (date) => {
// MongoDB의 Date 객체를 로컬 시간으로 변환
return new Date(date).toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
// app/posts/page.js 또는 다른 컴포넌트
import { formatDate } from '@/utils/formatDate';
// 컴포넌트 내부에서 사용
<span>{formatDate(post.createdAt)}</span>
))}