Next.js에서는 pages/api
폴더를 사용하여 API 엔드포인트를 생성할 수 있다.
pages/api
폴더 안에 있는 JavaScript 파일들은 API 라우트로 간주되며, 파일의 이름 = 엔드포인트 경로가 된다.
예를 들어, pages/api
폴더 안에 users.js
파일을 생성한다면,
이 파일은 /api/users
경로에 해당하는 API를 생성하게 된다. 그리고 이 파일 내에서 요청을 처리하고 응답을 반환할 수 있다.
// pages/api/users.js
export default function handler(req, res) {
// 요청 처리 로직
// ...
// 응답 반환
res.status(200).json({ message: 'API 엔드포인트에서 요청을 받았습니다.' })
}
위의 예시는 api/users
엔드포인트로 요청이 들어오면 'API 엔드포인트에서 요청을 받았습니다.'라는 JSON 응답을 반환한다.
pages/api
폴더 내에 여러 개의 JavaScript 파일을 만들 수 있으며, 각 파일은 해당하는 엔드포인트 경로로 요청을 라우팅한다.
예를 들어, pages/api/post.js
파일은 /api/posts
경로에 해당하는 API 엔드포인트를 생성한다.
Next.js는 자동으로 이러한 API 엔드포인트를 생성하고, 서버 사이드 렌더링과 클라이언트 사이드 렌더링을 지원한다. API 엔드포인트는 서버에서 실행되며, 클라이언트에서 해당 엔드포인트로 요청을 보낼 수 있다.
새로운 약속을 submit할 수 있는 new-meetup
페이지가 있고, 이 페이지는 <NewMeetupForm>
컴포넌트를 리턴하고 있다.
그리고 props로 addMeetupHandler()
함수를 전달해주고 있다.
// pages/new-meetup/index.js
import React from 'react';
import NewMeetupForm from '../../components/meetups/NewMeetupForm';
function NewMeetUpPage() {
async function addMeetupHandler(enteredMeetupData) {
console.log(enteredMeetupData);
}
return <NewMeetupForm onAddMeetup={addMeetupHandler} />;
// props로 전달한 onAddMeetup 함수는 NewMeetupForm 컴포넌트에서
// props.onMeetup({전송할 데이터}) 형식으로 사용할 수 있다.
}
export default NewMeetUpPage;
현재 코드에서는 NewMeetupForm
컴포넌트로 부터 받은 enteredData
를 콘솔에 출력하고 있지만, 이 부분을 수정하여 데이터를 MongoDB의 데이터베이스와 연결하고 컬렉션에 도큐먼트를 추가하도록 하려고 한다.
pages
폴더 안에 api
폴더를 만들고 new-meetup.js
파일을 만든다.
이 파일명은 곧 API 엔드포인트가 될 것이다!
이 API 라우트는 React 컴포넌트를 정의하고 렌더링하거나, 리턴하지 않는다.
대신에 서버 사이드 코드를 포함하는 함수를 정의할 것이다. 왜냐하면 API 라우트에서 작성하는 코드는 서버에서만 돌아가기 때문이다.
// pages/api/new-meetup.js
function handler(req, res) {
// ...
}
export default handler;
이렇게 작성한 함수는 라우트에 요청이 들어올때마다 트리거된다.
즉, 위의 핸들러는 api/new-meetup
경로로 요청이 보내지면 실행되어 요청과 응답을 처리한다.
매개변수로 받는 요청 객체는 들어오는 요청에 대한 데이터를 포함하며, 응답 객체는 응답을 반환할 때 필요하다.
// pages/api/new-meetup.js
function handler(req, res) {
if (req.method === 'POST') { // 요청 메소드가 POST일 때
const data = req.body; // req.body로 요청의 바디를 받아올 수 있다.
const {title, image, address, description } = data; // 구조분해할당도 사용 가능
// ...
}
}
export default handler;
MongoClient를 사용하여 MongoDB 데이터베이스에 연결할 수 있다.
MongoClient의 connect()
메소드를 호출하고 매개변수로 MongoDB 연결 문자열(connect string)을 매개변수로 전달하면 된다.
비밀번호 부분은 실제 MondgoDB 데이터베이스 암호로 바꿔주어야 한다. 이때, API 라우트는 클라이언트 사이드에서는 실행되지 않으므로 인증 정보를 저장해도 안전하다.
// pages/api/new-meetup.js
import { MongoClient } from 'mongodb';
function handler(req, res) {
if (req.method === 'POST') {
const data = req.body;
const {title, image, address, description } = data;
MongoClient.connect('mongodb+srv://janechun22:<비밀번호>@cluster0.jrwnfs4.mongodb.net/?retryWrites=true&w=majority');
// MongoClient를 이용해 connect 메소드를 호출하고 connection string을 전달한다.
// 비밀번호 입력할 때 <>는 삭제
}
}
export default handler;
connect()
메소드는 Promise를 리턴하기 때문에 handler 함수를 async 함수로 바꿔주고, 연결 결과를 client
라는 변수에 담아준다.
db()
메소드를 사용해 데이터베이스를 보관하고,
collection()
메소드를 사용해 컬렉션을 보관할 수 있다. 만약 존재하지 않는 이름의 컬렉션이라면 즉시 생성된다.
// pages/api/new-meetup.js
import { MongoClient } from 'mongodb';
async function handler(req, res) {
if (req.method === 'POST') {
const data = req.body;
const {title, image, address, description } = data;
const client = await MongoClient.connect('mongodb+srv://janechun22:<비밀번호>@cluster0.jrwnfs4.mongodb.net/?retryWrites=true&w=majority');
const db = client.db(); // 데이터베이스
const meetupsCollection = db.collection('meetups'); // 데이터베이스에서 'meetups' 라는 컬렉션을 가져온다. (없으면 생성한다.)
}
}
export default handler;
컬렉션에서 insertOne()
메소드로 문서를 추가할 수 있다. MongoDB에서 문서는 JavaScript 객체이므로 데이터를 담은 객체를 전달하면 된다.
meetupsCollection.insertOne({
// ...
});
insertOne()
은 promise를 리턴하므로 await 키워드를 붙여주고, 결과값을 result 변수에 담는다.
필요한 작업을 모두 마쳤다면, close()
메소드를 호출하여 데이터베이스 연결을 차단해야 한다.
그리고 마지막으로 응답 객체를 이용해 응답을 반환하면 된다.
// pages/api/new-meetup.js
import { MongoClient } from 'mongodb';
async function handler(req, res) {
if (req.method === 'POST') {
const data = req.body;
const client = await MongoClient.connect('mongodb+srv://janechun22:<비밀번호>@cluster0.jrwnfs4.mongodb.net/?retryWrites=true&w=majority');
const db = client.db();
const meetupsCollection = db.collection('meetups');
const result = await meetupsCollction.insertOne(data); // 데이터베이스에 문서 추가
console.log(result);
client.close(); // 데이터베이스 연결 차단
res.status(201).json({ message: 'Meetup inserted!' }); // 응답 반환
}
}
export default handler;
이제 new-meetup
페이지에 있는 addMeetupHandler()
를 완성해보자.
내부 API 경로로 요청을 보내는 것은 외부 API나 백엔드로 요청을 보내는 작업과 동일하다.
경로는 /api/[파일명]
로 요청을 보내면 된다.
import React from 'react';
import NewMeetupForm from '../../components/meetups/NewMeetupForm';
function NewMeetUpPage() {
async function addMeetupHandler(enteredMeetupData) {
const response = await fetch('/api/new-meetup', { // fetch로 POST 요청 보내기
method: 'POST',
body: JSON.stringify(enteredMeetupData),
headers: {
'Content-Type': 'application/json',
},
});
const data = await response.json(); // 응답 결과 얻기
console.log(data);
}
return <NewMeetupForm onAddMeetup={addMeetupHandler} />;
}
export default NewMeetUpPage;
그리고 form을 submit한 후에는 홈(/
) 경로로 이동하도록 해주면 된다.
useRouter
의 push()
메소드를 사용한다. 또는 뒤로가기가 가능한 replace()
메소드를 사용할 수도 있다.
import React from 'react';
import NewMeetupForm from '../../components/meetups/NewMeetupForm';
import { useRouter } from 'next/router';
function NewMeetUpPage() {
const router = useRouter();
async function addMeetupHandler(enteredMeetupData) {
const response = await fetch('/api/new-meetup', {
method: 'POST',
body: JSON.stringify(enteredMeetupData),
headers: {
'Content-Type': 'application/json',
},
});
const data = await response.json();
console.log(data);
router.push('/');
// router.replace('/') 뒤로가기 가능
}
return <NewMeetupForm onAddMeetup={addMeetupHandler} />;
}
export default NewMeetUpPage;
데이터베이스 컬렉션에 잘 추가된 모습
css가 상당히 맥시형 스타일이네요 ~!