🔗 레파지토리에서 보기
📌 스스로 해보기
- 뉴스레터 구독 기능 추가 : 이메일 주소를 일단 콘솔에 기록
- 댓글 제출 기능
📖 뉴스레터 구독 기능 추가
import { useRef } from "react";
import classes from "./newsletter-registration.module.css";
function NewsletterRegistration() {
const emailInputRef = useRef();
function registrationHandler(event) {
event.preventDefault();
const userEmail = emailInputRef.current.value;
if (!userEmail || userEmail.trim() === "" || !userEmail.includes("@")) {
console.log("이메일을 입력해주세요.");
}
const registrationEmail = { email: userEmail };
fetch("api/newsletter", {
method: "POST",
body: JSON.stringify(registrationEmail),
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data) => {
console.log(data);
emailInputRef.current.value = null;
});
}
return (
<section className={classes.newsletter}>
<h2>Sign up to stay updated!</h2>
<form onSubmit={registrationHandler}>
<div className={classes.control}>
<input
type="email"
id="email"
placeholder="Your email"
aria-label="Your email"
ref={emailInputRef}
/>
<button>Register</button>
</div>
</form>
</section>
);
}
export default NewsletterRegistration;
💎 pages/api/newsletter.js
export default function handler(req, res) {
if (req.method === "POST") {
const registrationEmail = req.body.email;
res.status(200).json({
message: "뉴스레터 구독에 성공하였습니다.",
email: registrationEmail,
});
}
}
📖 댓글 기능
import { useState } from "react";
import CommentList from "./comment-list";
import NewComment from "./new-comment";
import classes from "./comments.module.css";
function Comments(props) {
const { eventId } = props;
const [showComments, setShowComments] = useState(false);
function toggleCommentsHandler() {
setShowComments((prevStatus) => !prevStatus);
}
function addCommentHandler(commentData) {
const newComment = {
id: eventId,
...commentData,
};
console.log(newComment);
fetch("/api/comment", {
method: "POST",
body: JSON.stringify(newComment),
headers: { "Content-Type": "application/json" },
})
.then((response) => response.json())
.then((data) => console.log(data));
}
return (
<section className={classes.comments}>
<button onClick={toggleCommentsHandler}>
{showComments ? "Hide" : "Show"} Comments
</button>
{showComments && <NewComment onAddComment={addCommentHandler} />}
{showComments && <CommentList />}
</section>
);
}
export default Comments;
export default function handler(req, res) {
if (req.method === "POST") {
const id = req.body.id;
const commentId = new Date().toISOString();
const email = req.body.email;
const name = req.body.name;
const text = req.body.text;
const newComment = {
id,
commentId,
email,
name,
text,
};
res.status(200).json({
message: "새로운 댓글 생성에 성공했습니다.",
comment: newComment,
});
}
}
📌 프로젝트
📖 뉴스레터 경로 추가하기
💎 pages/api/newsletter.js
export default function handler(req, res) {
if (req.method === "POST") {
const registrationEmail = req.body.email;
if (!registrationEmail || !registrationEmail.includes("@")) {
res.status(422).json({
message: "유효하지 않은 이메일입니다. 이메일을 다시 입력해주세요.",
});
return;
}
res.status(201).json({
message: "뉴스레터 구독에 성공하였습니다.",
email: registrationEmail,
});
}
}
import { useRef } from "react";
import classes from "./newsletter-registration.module.css";
function NewsletterRegistration() {
const emailInputRef = useRef();
function registrationHandler(event) {
event.preventDefault();
const enteredEmail = emailInputRef.current.value;
fetch("api/newsletter", {
method: "POST",
body: JSON.stringify({ email: enteredEmail }),
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data) => {
console.log(data);
emailInputRef.current.value = null;
});
}
return (
<section className={classes.newsletter}>
<h2>Sign up to stay updated!</h2>
<form onSubmit={registrationHandler}>
<div className={classes.control}>
<input
type="email"
id="email"
placeholder="Your email"
aria-label="Your email"
ref={emailInputRef}
/>
<button>Register</button>
</div>
</form>
</section>
);
}
export default NewsletterRegistration;
📖 댓글 API 라우트 추가하기
- 댓글은 각 이벤트의 id에 따라 달라지므로 동적으로 구성해야한다.
export default function handler(req, res) {
const eventId = req.query.eventId;
if (req.method === "POST") {
const { email, name, text } = req.body;
if (
!email ||
!email.inclues("@") ||
!name ||
name.trim() === "" ||
!text ||
text.trim() === ""
) {
res
.status(422)
.json({ message: "유효하지 않은 값입니다. 다시 입력해주세요." });
return;
}
const newComment = {
id: new Date().toISOString(),
email,
name,
text,
};
res.status(201).json({
message: "새로운 댓글 생성에 성공했습니다.",
comment: newComment,
});
}
if (req.method === "GET") {
const dummyList = [
{ id: "c1", name: "Zoe", email: "test@test.com", text: "1등" },
{ id: "c2", name: "Taemin", email: "test@test.com", text: "2등" },
];
res.status(200).json({ comments: dummyList });
}
}
import { useEffect, useState } from "react";
import CommentList from "./comment-list";
import NewComment from "./new-comment";
import classes from "./comments.module.css";
function Comments(props) {
const { eventId } = props;
const [comments, setComments] = useState([]);
const [showComments, setShowComments] = useState(false);
useEffect(() => {
if (showComments) {
fetch(`/api/comments/${eventId}`)
.then((response) => response.json())
.then((data) => {
setComments(data.comments);
});
}
}, [showComments]);
console.log(comments);
function toggleCommentsHandler() {
setShowComments((prevStatus) => !prevStatus);
}
function addCommentHandler(commentData) {
fetch(`/api/comments/${eventId}`, {
method: "POST",
body: JSON.stringify(commentData),
headers: { "Content-Type": "application/json" },
})
.then((response) => response.json())
.then((data) => console.log(data));
}
return (
<section className={classes.comments}>
<button onClick={toggleCommentsHandler}>
{showComments ? "Hide" : "Show"} Comments
</button>
{showComments && <NewComment onAddComment={addCommentHandler} />}
{showComments && <CommentList items={comments} />}
</section>
);
}
export default Comments;
import classes from "./comment-list.module.css";
function CommentList({ items }) {
return (
<ul className={classes.comments}>
{}
{items.map((item) => (
<li key={item.id}>
<p>{item.text}</p>
<div>
By <address>{item.name}</address>
</div>
</li>
))}
</ul>
);
}
export default CommentList;

📌 MongoDB 사용하기
📖 MongoDB 데이터베이스 설정하기
- MongoDB 페이지에서 회원가입 → 무료 클러스터 설정
- 설치 :
npm install mongodb
→ mongodb 패키지가 MongoDB를 위한 공식 드라이버
- 보안 상의 문제로 서버 사이드(즉 API 라우트)에만 데이터베이스 코드를 실행하도록 해야한다.
- MongoDB의 클러스터에서 connect 클릭
📖 API 라우트의 내부로부터 MongoDB 쿼리 실행하기
🔗 npm mongodb
💎 pages/api/newsletter.js
- mongodb의 url에 들어갈 password는 MongoDB 페이지의 Database Access 에서 설정 가능하다.
import { MongoClient } from "mongodb";
export default async function handler(req, res) {
if (req.method === "POST") {
const registrationEmail = req.body.email;
if (!registrationEmail || !registrationEmail.includes("@")) {
res.status(422).json({
message: "유효하지 않은 이메일입니다. 이메일을 다시 입력해주세요.",
});
return;
}
const client = await MongoClient.connect(
"mongodb+srv://<username>:<password>@cluster0.stdfag3.mongodb.net/?retryWrites=true&w=majority&appName=<name>"
);
const db = client.db("newsletter");
await db.collection("emails").insertOne({ email: registrationEmail });
client.close();
res.status(201).json({
message: "뉴스레터 구독에 성공하였습니다.",
});
}
}

📖 데이터베이스로 댓글 삽입하기
import { MongoClient } from "mongodb";
export default async function handler(req, res) {
const eventId = req.query.eventId;
const client = await MongoClient.connect(
"mongodb+srv://<username>:<password>@cluster0.stdfag3.mongodb.net/events?retryWrites=true&w=majority&appName=<name>"
);
if (req.method === "POST") {
const { email, name, text } = req.body;
if (
!email ||
!email.includes("@") ||
!name ||
name.trim() === "" ||
!text ||
text.trim() === ""
) {
res
.status(422)
.json({ message: "유효하지 않은 값입니다. 다시 입력해주세요." });
return;
}
const newComment = {
email,
name,
text,
eventId,
};
const db = client.db();
const result = await db.collection("comments").insertOne({ newComment });
console.log(result);
res.status(201).json({
message: "새로운 댓글 생성에 성공했습니다.",
});
}
if (req.method === "GET") {
const dummyList = [
{ id: "c1", name: "Zoe", email: "test@test.com", text: "1등" },
{ id: "c2", name: "Taemin", email: "test@test.com", text: "2등" },
];
res.status(200).json({ comments: dummyList });
}
client.close();
}
- 아예 events라는 이름의 테이블 생성 후, 그 안에 comments와 newsletter가 들어가도록 했다.

📖 데이터베이스로부터 데이터 가져오기
import { MongoClient } from "mongodb";
export default async function handler(req, res) {
const eventId = req.query.eventId;
const client = await MongoClient.connect(
"mongodb+srv://<username>:<password>@cluster0.stdfag3.mongodb.net/events?retryWrites=true&w=majority&appName=<name>"
);
if (req.method === "POST") {
const { email, name, text } = req.body;
if (
!email ||
!email.includes("@") ||
!name ||
name.trim() === "" ||
!text ||
text.trim() === ""
) {
res
.status(422)
.json({ message: "유효하지 않은 값입니다. 다시 입력해주세요." });
return;
}
const newComment = {
email,
name,
text,
eventId,
};
const db = client.db();
const result = await db.collection("comments").insertOne({ newComment });
console.log(result);
res.status(201).json({
message: "새로운 댓글 생성에 성공했습니다.",
});
}
if (req.method === "GET") {
const db = client.db();
const documents = await db
.collection("comments")
.find()
.sort({ _id: -1 })
.toArray();
res.status(200).json({ comments: documents });
}
client.close();
}
documents
를 받아올 때, toArray()
를 실행한 이유 : 기본값은 배열이 아니라 커서가 나타난다. 단순히 comments 컬렉션의 모든 엔트리를 배열로 제공.
sort({ _id: -1 })
: _id를 기준으로 내림차순으로 정렬
📖 에러 핸들링 추가하기
- 데이터베이스 연결이 실패하거나 데이터 삽입에 실패할 수도 있기 때문에 데이터베이스에 대한 에러 핸들링이 필요하다.
💎 pages/api/newsletter.js
import { MongoClient } from "mongodb";
async function connectDB() {
const client = await MongoClient.connect(
"mongodb+srv://<username>:<password>@cluster0.stdfag3.mongodb.net/events?retryWrites=true&w=majority&appName=<name>"
);
return client;
}
async function insertDocument(client, document) {
const db = client.db();
await db.collection("newsletter").insertOne({ email: document });
}
export default async function handler(req, res) {
if (req.method === "POST") {
const registrationEmail = req.body.email;
if (!registrationEmail || !registrationEmail.includes("@")) {
res.status(422).json({
message: "유효하지 않은 이메일입니다. 이메일을 다시 입력해주세요.",
});
return;
}
let client;
try {
client = await connectDB();
} catch (error) {
res.status(500).json({ message: "데이터베이스 연결 실패" });
return;
}
try {
await insertDocument(client, { email: registrationEmail });
client.close();
} catch (error) {
res.status(500).json({ message: "데이터 삽입 실패" });
}
res.status(201).json({
message: "뉴스레터 구독에 성공하였습니다.",
});
}
}

📖 에러 핸들링 더 자세히 살펴보기
💎 helper/db-util.js
import { MongoClient } from "mongodb";
export async function connectDB() {
const client = await MongoClient.connect(
"mongodb+srv://<username>:<password>@cluster0.stdfag3.mongodb.net/events?retryWrites=true&w=majority&appName=<name>"
);
return client;
}
export async function insertDocument(client, collection, document) {
const db = client.db();
const result = await db.collection(collection).insertOne({ email: document });
return result;
}
export async function getAllDocument(client, collection, sort) {
const db = client.db();
const documents = await db.collection(collection).find().sort(sort).toArray();
return documents;
}
💎 pages/api/newsletter.js
import { connectDB, insertDocument } from "../../helper/db-util";
export default async function handler(req, res) {
if (req.method === "POST") {
const registrationEmail = req.body.email;
if (!registrationEmail || !registrationEmail.includes("@")) {
res.status(422).json({
message: "유효하지 않은 이메일입니다. 이메일을 다시 입력해주세요.",
});
return;
}
let client;
try {
client = await connectDB();
} catch (error) {
res.status(500).json({ message: "데이터베이스 연결 실패" });
return;
}
try {
await insertDocument(client, "newsletter", { email: registrationEmail });
client.close();
} catch (error) {
res.status(500).json({ message: "데이터 삽입 실패" });
return;
}
res.status(201).json({
message: "뉴스레터 구독에 성공하였습니다.",
});
}
}
import {
connectDB,
insertDocument,
getAllDocument,
} from "../../../helper/db-util";
export default async function handler(req, res) {
const eventId = req.query.eventId;
let client;
try {
client = await connectDB();
} catch (error) {
res.status(500).json({ message: "DB 연결 실패" });
return;
}
if (req.method === "POST") {
const { email, name, text } = req.body;
if (
!email ||
!email.includes("@") ||
!name ||
name.trim() === "" ||
!text ||
text.trim() === ""
) {
res
.status(422)
.json({ message: "유효하지 않은 값입니다. 다시 입력해주세요." });
client.close();
return;
}
const newComment = {
email,
name,
text,
eventId,
};
let result;
try {
result = await insertDocument(client, "comments", { newComment });
newComment._id = result.insertedId;
res.status(201).json({ message: "새로운 댓글 생성에 성공했습니다." });
} catch (error) {
res.status(500).json({ message: "댓글 삽입 실패" });
}
}
if (req.method === "GET") {
try {
const documents = await getAllDocument(client, "comments", { _id: -1 });
res.status(200).json({ comments: documents });
} catch (error) {
res.status(500).json({ message: "댓글 데이터 가져오기 실패" });
}
}
client.close();
}
- 현재
client.close()
를 이용해 MongoDB의 연결을 항상 닫는다. 하지만 MongoDB 관련 코드가 자주 실행되는 어플리케이션을 구축하는 경우, MongoDB의 Connection Pool을 활용하는 것이 좋다.
🔗 Connection Pool 블로그 참고
📖 (+) 특정 이벤트에 대한 댓글 가져오기
💎 helper/db-util.js
import { MongoClient } from "mongodb";
export async function connectDB() {
const client = await MongoClient.connect(
"mongodb+srv://<username>:<password>@cluster0.stdfag3.mongodb.net/events?retryWrites=true&w=majority&appName=<name>"
);
return client;
}
export async function insertDocument(client, collection, document) {
const db = client.db();
const result = await db.collection(collection).insertOne({ email: document });
return result;
}
export async function getAllDocument(client, collection, sort, filter = {}) {
const db = client.db();
const documents = await db
.collection(collection)
.find(filter)
.sort(sort)
.toArray();
return documents;
}
import {
connectDB,
insertDocument,
getAllDocument,
} from "../../../helper/db-util";
export default async function handler(req, res) {
const eventId = req.query.eventId;
let client;
try {
client = await connectDB();
} catch (error) {
res.status(500).json({ message: "DB 연결 실패" });
return;
}
if (req.method === "POST") {
const { email, name, text } = req.body;
if (
!email ||
!email.includes("@") ||
!name ||
name.trim() === "" ||
!text ||
text.trim() === ""
) {
res
.status(422)
.json({ message: "유효하지 않은 값입니다. 다시 입력해주세요." });
client.close();
return;
}
const newComment = {
email,
name,
text,
eventId,
};
let result;
try {
result = await insertDocument(client, "comments", { newComment });
newComment._id = result.insertedId;
res.status(201).json({ message: "새로운 댓글 생성에 성공했습니다." });
} catch (error) {
res.status(500).json({ message: "댓글 삽입 실패" });
}
}
if (req.method === "GET") {
try {
const documents = await getAllDocument(
client,
"comments",
{ _id: -1 },
{ eventId: eventId }
);
res.status(200).json({ comments: documents });
} catch (error) {
res.status(500).json({ message: "댓글 데이터 가져오기 실패" });
}
}
client.close();
}
- GET 메서드일 때,
getAllDocument(client,"comments",{_id:-1},{eventId:eventId})
로 설정하여 특정 이벤트에 속한 댓글만을 가져오게 한다.