게시판용 프로젝트 생성 및 MongoDB 세팅
DB에는 두 종류가 있는데 관계형 DB와 비관계형 DB로 나뉨.
- 관계형 DB는 엑셀처럼 표 형태로 데이터를 저장하는 DB. 별도의 DB용 문법인 SQL을 사용함.
- 비관계형 DB는 표가 아닌 형태로 데이터를 저장하는 DB.
- 대표적으로 MongoDB는 JS의 object 자료형처럼 데이터를 저장 ( { 데이터이름1: 내용1 .. } )
- 대용량 트래픽 처리를 고려하여 설계되어 분산처리가 뛰어나다.
MongoDB.com 가입 및 무료 플랜을 통해 무료 DB를 호스팅받아 진행.
Database Access에서는, atlas admin 역할을 받은 admin 계정을 생성하여 이용
Network Access에서는, 임시로 모든 IP(0.0.0.0)에 대해 접근을 허용 (실제 서비스 시 신뢰할 수 있는 IP만 허용하게 변경)
Next.js에서 MongoDB 사용하기
Database는 일반적으로 하나의 프로젝트에서 쓰는 DB. Collection은 하나의 폴더. 이 폴더 안에 document를 작성해 밀어넣는다. 하나의 document는 object 자료형으로 이루어짐.
기본적인 MongoDB 세팅
1. 터미널을 열고, mongodb 설치 (npm install mongodb)
2. mongodb 연결
- DB접속 URL은 Mongodb.com 내에 나의 DB Cluster에서 connect-driver를 눌러 확인 가능
const client = await MongoClient.connect('DB접속URL', { useNewUrlParse: true })
const db = client.db('컬렉션명')
3. 이후 db.collection('컬렉션명').find()
와 같이 mongodb의 데이터를 가져와 활용할 수 있음.
MongoDB를 쓰려면 await을 문법처럼 써줘야 하는데, 이 await을 쓰려면 function 옆에 async를 붙여야 한다.
그런데 connect 같은 경우 서버 실행 당 1번만 연결하게끔 하는 것이 좋으므로, 별도 파일로 빼놓는 것이 좋다.
외부 파일로 따로 빼서 MongoDB 세팅
최상위 폴더에 util 폴더를 만들고, 그 안에 database.js 파일을 만든 뒤, 여기에 connect 코드를 보관한다.
// database.js 템플릿
import { MongoClient } from 'mongodb'
const url = 'DB URL'
const options = { useNewUrlParser: true }
let connectDB
if (process.env.NODE_ENV === 'development') {
if (!global._mongo) {
global._mongo = new MongoClient(url, options).connect()
}
connectDB = global._mongo
} else {
connectDB = new MongoClient(url, options).connect()
}
export { connectDB }
Next.js 개발 시 파일을 저장하는 경우 모든 JS파일 코드를 전부 다시 읽고 지나간다. 때문에 connect가 저장 시마다 재실행되지 않게 하기 위해서는 위와 같이 처리를 해줘야 한다.
이후 function 안에 아래와 같이 명시한다.
// 상단에 import
import { connectDB } from '@/util/database';
const client = await connectDB
const db = client.db('컬렉션명')
이후 await db.collection('post').find().toArray()
등의 명령어를 사용하여 DB의 데이터를 활용할 수 있는데 주의할 점은 클라이언트 컴포넌트 안에 있는 모든 코드는 클라이언트에게 전달되기 때문에, DB 입출력 코드는 서버 컴포넌트 안에서만 써야 한다.
DB 데이터 출력
프로그램을 만드는 방법은 먼저 프로그램이 필요한 기능을 전부 정리하고, 쉬운 기능부터 하나씩 개발해 나가는 것이다. 그리고 각 기능의 세부기능을 만드는 방법은 마찬가지로 어떤 식으로 동작하는지 상세하게 설명을 적고, 이를 코드로 구현하면 된다.
글 목록 페이지/기능
- 글 목록 HTML 페이지 필요
- 페이지 방문 시 DB에서 글 목록 꺼내오기
- 글 목록들을 HTML에 출력하기
DB에서 글 목록 꺼내오기
아까 배웠듯이 mongoDB를 연결한 뒤, 전용 명령어들을 사용해 DB로부터 글 목록을 꺼내오면 된다. (find 함수)
이때 await을 사용하는 이유는, JS는 처리가 늦게되는 코드를 발견 시 이를 기다리지 않고 다음 줄을 실행하는 형태이기 때문이다. 이는 개발 의도와 다른 동작을 일으킬 수 있으므로, await를 통해 해당 코드 처리가 완료된 뒤 다음 줄을 실행하게 한다. (다만 이를 아무데나 붙일 수는 없는데, Promise를 return하는 코드들만 await을 붙일 수 있다.)
import { connectDB } from '@/util/database';
export default async function List() {
const client = await connectDB
const db = client.db('board')
let result = await db.collection('post').find().toArray()
return (
<div className="list-bg">
{result.map((post, i) => {
return (
<div className="list-item" key={i}>
<h4>{post.title}</h4>
<p>{post.content}</p>
</div>
)
})}
</div>
)
}
이때 return() + 중괄호만 있다면 동시에 생략 가능하다.
Dynamic Route
글이 1000개인 경우, 폴더를 1000개 만들고 page.js를 만들기는 어렵다. 이때 dynamic route를 사용하면 비슷한 페이지를 여러 개 만들 필요가 없어진다.
Dynamic Route 만들기
1. 임의의 이름(폴더명)으로 폴더 만들기
2. 그 안에 폴더를 만드는데, 이름을 [원하는이름] 으로 만들기
3. 해당 url을 접속하려면 /폴더명/~~ 로 접속하면 된다.
DB에서 원하는 document 하나만 가져오기
.findOne({ 컬럼명: '찾을document만 가진 값' }) 으로 찾아올 수 있다. 이때 title 등 중복값이 발생할 확률이 있는 컬럼을 사용할 경우, 원하는 document만 가져올 수 없으므로 각 document가 가진 _id라는 고유한 값을 이용할 수 있다. 이를 이용하려면 ObjectId를 import하고, new ObjectId() 안에 해당 고유 값을 넣어야 한다.
let result = await db.collection('post').findOne({ _id: new ObjectId('684e52bec5342e56dec18ccf') })
그렇다면 이제는 해당 글을 클릭하면, 그 글에 해당하는 ObjectId로부터 DB에서 내용을 찾아 보여줘야 한다. 단, Next.js 13 이상에서의 동적 라우트 params는 Promise 객체이므로, await를 통해 먼저 전체 객체 자체를 해제한 뒤 이용해야 한다.
export default async function Deatil(props) {
const db = (await connectDB).db('board')
const resolvedParams = await props.params
let result = await db.collection('post').findOne({ _id: new ObjectId(String(resolvedParams.seq)) })
return (
<>
<h4>상세페이지</h4>
<h4>{result.title}</h4>
<p>{result.content}</p>
</>
)
}
글 목록에서 글을 클릭했을 때 해당 글의 상세페이지로 이동하기
export default async function List() {
const db = (await connectDB).db('board')
let result = await db.collection('post').find().toArray()
console.log(result[0])
return (
<div className="list-bg">
{result.map((post, i) =>
<div className="list-item" key={post._id.toString()}>
<Link href={`/detail/${post._id.toString()}`}><h4>{post.title}</h4></Link>
<p>1월1일</p>
</div>
)}
</div>
)
}
useRouter
페이지 이동 방식에는 여러 가지가 있는데 그 중 하나가 useRouter이다. 이를 이용하기 위해서는 클라이언트 컴포넌트여야 한다. 이후 컴포넌트 안에 next/navigation으로부터 useRouter를 import하고 활용한다.
페이지를 이동한다는 개념은 Link 태그와 같지만(push 함수), 뒤로 가기(back 함수)나 앞으로 가기(forward 함수), soft한 새로고침(refresh 함수. HTML 전체가 아닌 변동 사항만 새로고침) 등을 지원한다는 점에서 매력적이다.
(페이지 미리 로드 기능도 prefetch 함수를 통해 제공하지만, 이는 Link 태그도 제공하고 있음)
만약 서버 컴포넌트 안에서 이를 이용하고 싶다면, 클라이언트 컴포넌트 파일을 하나 만든 후, 여기서 useRouter를 사용한 뒤 이를 서버 컴포넌트에 import해 활용하면 된다.
또한 페이지 라우트 관련 useRouter 외에도 유용한 함수들이 있는데 여기에는 usePathname(현재 URL 출력), useSearchParams(Search Parameter 출력), useParams(클라이언트 dynamic route 입력한 내용 출력) 등이 있다. 필요 시 검색해서 활용하자.
- 여러 페이지를 만들 때 [Dynamic Route]를 사용한다.
- 클라이언트가 입력한 현재 URL 확인은 props 혹은 useRouter를 사용한다.
- useRouter는 페이지 이동, prefetch 등의 기능을 제공한다.
서버기능 개발
글 작성을 한다고 했을 때, 클라이언트가 작성한 글을 바로 DB에 반영하는 것은 위험하다. 때문에 중간에 프로그램을 두어 글 검사 등을 진행하는데, 이 중간 프로그램을 서버라고 한다. 이렇게 클라이언트-서버-DB로 이루어진 아키텍처를 3-tier architecture라고 한다.
서버는 특정한 요청에 대해 특정한 코드를 실행해준 뒤 이에 대한 응답을 반환하는데, 여기서 이 특정한 요청을 구분하는 것이 url과 method이다.
URL은 개발자가 정한 특정한 주소이고, method는 GET, POST, PUT, DELETE, PATCH로 목적에 따라 정해져 있다. 엄격하게 mehtod 선택을 제한하고 있지는 않지만 웬만하면 지키는 것이 권장된다.
- GET: 클라이언트에게 데이터 전송
- POST: 새로운 데이터 추가
- PUT: 기존 데이터 수정
- DELETE: 기존 데이터 삭제
Next.js에서 서버 기능을 만들기 위해서는 루트 폴더에 pages 폴더를 만들고, 그 안에 api 폴더를 만든 뒤 그 안에 서버 기능을 가진 API 파일들을 만들어 구현하는 식이다. 유의사항으로, 새로운 파일이 추가된 경우 개발 서버가 켜져있었다면, 껐다 다시 키는 것이 좋다.
Next.js는 자동 라우팅 기능을 지원하기 때문에, /api/API명 으로 GET/POST/PUT/DELETE/PATCH를 요청하면 해당 API 안의 코드를 실행해준다.
서버는 기능 실행 후에 유저에게 응답을 해줘야 한다.
export default function handler(request, response) {
console.log(123)
return response.status(200).json('success')
}
하나의 API가 가진 handler 함수는 두 개의 파라미터를 가지며, 첫 번째 파라미터는 클라이언트의 요청 정보를, 두 번째 파라미터는 서버의 응답 정보를 가지고 있다.
status는 클라이언트에게 응답할 서버의 처리 결과 상태이다. 이 status code에는 여러 종류가 있다.
- 200: 처리 성공
- 500: 서버 기능 처리 실패
- 400: 서버 기능 처리 실패 (클라이언트 쪽 잘못)
만약 하나의 API에서 method에 따라 다른 처리를 해주고 싶다면, if (request.method == 'POST') { }
등으로 분기하여 처리해줄 수 있다.
또한, 응답의 일환으로 페이지를 이동시킬 수 있는데 이를 리다이렉션이라고 하고, '.json' 대신 '.redirect('/경로')'를 통해 처리 완료 후 원하는 페이지로 이동시킬 수 있다.
만약 서버에서 받은 요청 중 빈 문자열 등을 받은 경우, 에러 코드와 에러 메시지를 return할 수 있다.
그리고 DB 에러 처리 또한 가능한데, try-catch문을 활용하거나 result 변수에 DB 처리 결과를 받아서 활용할 수도 있다.
- 유저 - 서버 - DB의 형태로 개발하는 것이 좋다.
- 서버기능은 pages/api 폴더에 만든다.
- document 발행은 insertOne 함수를 이용한다.
회원가입 기능 만들기
// 회원가입 페이지
export default async function Register() {
return (
<div className="p-20">
<h4>회원가입</h4>
<form action="/api/user/register" method="POST">
<input name="userId" placeholder="새 아이디" />
<input name="password" placeholder="새 비밀번호" type="password" />
<button type="submit">가입</button>
</form>
</div>
)
}
// 회원가입 api
import { connectDB } from '@/util/database';
export default async function handler(request, response) {
if (request.method == 'POST') {
const body = request.body
const db = (await connectDB).db('board')
let findId = await db.collection('user').findOne({ userId: body.userId })
if (findId != null) {
return response.status(500).json('이미 존재하는 아이디입니다!')
}
const result = await db.collection('user').insertOne({ userId: body.userId, password: body.password })
response.status(200).json('success')
}
}
참고자료
코딩애플 온라인 강의 <Next.js로 웹서비스 만들기>
'크래프톤 정글 > Equipped in 정글(나만무)' 카테고리의 다른 글
[나만무] Jungle-Board 기본 CRUD 구현하기 (0) | 2025.06.17 |
---|---|
[나만무] 06.16 TIL - Next.js 학습 by 코딩애플 (0) | 2025.06.16 |
[나만무] 06.14 TIL - Next.js 학습 by 코딩애플 (0) | 2025.06.14 |
[나만무] 06.13 TIL - Next.js 학습 by 코딩애플 (0) | 2025.06.13 |
[나만무] Next.js 설치 및 개발환경 설정하기 (by 코딩애플) (0) | 2025.06.13 |