App router 사용해야지! 하고선 api routes 방식으로 작성한 api폴더를 냅다 app폴더로 이동시키고 에러와 싸웠다..
항상 공식문서를 확인하자...⭐
기존 미니프로젝트에서는 페이지라우터를, 팀 파이널 프로젝트에서는 앱라우터를 사용했었는데
백엔드와의 협업 프로젝트였기 때문에 외부 API를 호출하는 방식으로만 진행되어 API Routes, Route Handlers 모두 사용할 기회가 없었다.
혼자 next.js를 공부했을 때는 페이지라우터 방식을 우선적으로 접했기 때문에 api routes가 익숙한 상태..
Route Handlers 사용은 익숙하지 않아서인지 너무..복잡하당......😭
그래서 정리하는 Pages router의 API Routes 코드 => App router의 Route Handlers와 비교하고 코드 수정하기
구조: /app/api/ 디렉토리 내에 라우트를 정의
예) /app/api/text/route.js 파일에 API 로직을 구현
명시적인 HTTP 메서드 이름 사용: App Router 방식에서는 파일 내에 직접 HTTP 메서드 이름(GET, POST, PUT, DELETE 등)을 사용하여 함수를 정의. 별도의 req, res 매개변수가 필요 없다.
(특정 경우에 요청 객체에서 추가 데이터를 추출하거나, 요청의 다른 속성을 사용해야 할 때 req 매개변수를 함수에 추가)
export async function GET() {
// GET 요청을 처리하는 로직
}
export async function POST() {
// POST 요청을 처리하는 로직
}
응답 객체 생성 방식: : NextResponse와 표준 Response 객체를 사용하여 HTTP 응답을 생성하고 반환할 수 있다.
NextResponse.json(data)을 사용하면, 자동으로 Content-Type 헤더가 application/json으로 설정되고, 상태 코드는 기본적으로 200 OK로 설정된다. 따라서 별도로 상태 코드나 헤더를 지정할 필요가 없다.
// App Router_ return NextResponse 방식 예시
return NextResponse.json(
{ error: "서버 오류가 발생했습니다." },
{ status: 500 }
);
>
// App Router_ return NextResponse 방식 예시2
export async function GET() {
const data = await db.collection("post").find().toArray();
return NextResponse.json(data);}
}
`
Response 객체는 Fetch API의 일부로, HTTP 응답을 나타낸다. new Response(body, options)을 통해 생성할 수 있으며, options 객체를 통해 상태 코드, 헤더 등을 명시적으로 설정할 수 있다.
// App Router_ return new Response 방식 예시
return new Response(JSON.stringify({ error: "에러 메시지" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
// App Router_ return new Response 방식 예시2
export async function GET() {
const data = await db.collection("post").find().toArray();
return new Response(JSON.stringify(data), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
}
// Pages Router 방식 예시
export default async function handler(req, res) {
// req로부터 요청 데이터 접근, res를 통해 응답 전송
if (req.method === "GET") {
// GET 요청을 처리하는 로직,
} else if (req.method === "POST") {
// POST 요청을 처리하는 로직,
}
}
`// Pages Router 방식 예시
res.status(400).json({ error: "에러 메시지" });// /pages/api/post/new.js
import { connectDB } from "@/util/database"
export default async function handler(req, res){
const db = (await connectDB).db("board")
let result = await db.collection('post').find().toArray()
res.status(200).json(result)
}
기존 pages router 코드 (POST)
// pages/api/post/new.js
import { connectDB } from "@/util/database";
export default async function handler(req, res) {
if (req.method === "POST") {
console.log(req.body); //유저가 보낸 데이터
if (req.body.title === "" || req.body.content === "") {
return res.status(500).json("제목과 내용을 모두 입력 해주세요");
}
try {
const db = (await connectDB).db("blog");
const result = await db.collection("post").insertOne(req.body);
return res.status(200).redirect(302, "/list");
} catch (error) {
console.error(error);
}
}
}
// /src/app/write/page.js
export default function Write() {
return (
<div>
<h2>글 작성</h2>
<form action="/api/post/new" method="POST">
<input name="title" placeholder="글제목" />
<textarea name="content" placeholder="글내용" cols="30" rows="10"></textarea>
<button type="button">글 작성</button>
</form>
</div>
);
}
mongo db의 전체 게시글 목록 불러오기!
// /app/api/new/route.js
const client = await connectDB;
const db = client.db("board");
export async function GET() {
const data = await db.collection("post").find().toArray();
return NextResponse.json(data);
}
🌞 MongoDB에 데이터 저장하는 법
await db.collection('collection이름').insertOne(저장할object자료)
// /app/api/new/route.js
import { connectDB } from "@/utils/database";
const client = await connectDB;
const db = client.db("board");
export async function POST(req, res) {
try {
const data = await req.json(); // 요청 본문 파싱
console.log(data);
if (!data.title || data.title.trim() === "") {
return NextResponse.json(
{ error: "제목은 필수입니다." },
{ status: 400 }
);
}
await db.collection("post").insertOne(data);
return NextResponse.json(data);
} catch (error) {
console.error(error);
return NextResponse.json(
{ error: "서버 오류가 발생했습니다." },
{ status: 500 }
);
}
}
handleSubmit 함수 작성하기.
처음엔 평소 익숙한 방법으로 작성하였다.
폼의 각 입력 필드에서 직접 데이터를 가져와서 객체형태로 전달하기!
기존 코드에 제목, 내용이 비어있을 경우 에러처리, 메시지를 추가해주었다.
const handleSubmit = async (e) => {
e.preventDefault();
const title = e.target.title.value;
const content = e.target.content.value;
try {
if (title.length === 0 && content.length === 0) {
throw new Error("모든 항목을 입력해주세요.");
} else if (title.length === 0) {
throw new Error("제목을 입력해주세요.");
} else if (content.length === 0) {
throw new Error("내용을 입력해주세요");
}
const response = await fetch("/api/new", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title, content }),
});
if (!response.ok) {
throw new Error("네트워크 응답이 올바르지 않습니다.");
}
setMessage("새 글이 등록되었습니다😊");
router.push("/list");
} catch (error) {
console.error(error, "");
setMessage(error.message);
}
};
그러던 중 new FormData, Object.fromEntries(formData)에 대한 코드를 알게 되어 새롭게 적용해보기로 했다!
new FormData(e.target)는 폼에 입력된 데이터를 FormData 객체로 변환.
이 객체는 폼 필드 각각을 키-값 쌍으로 저장한다.Object.fromEntries(formData)는 이 FormData 객체를 일반 JavaScript 객체로 변환하는 과정이다.
formData의 키-값 쌍을 사용해 객체의 속성과 값을 설정한다. 이렇게 변환된 객체는 JSON으로 쉽게 변환될 수 있어, 서버로 데이터를 보낼 때 사용하기 편리하다.
<input name="username" value="JohnDoe"> 와 같은 입력 필드가 있다면,
FormData 객체 내에서 이 필드는 "username": "JohnDoe"라는 키-값 쌍으로 저장되고,
폼을 제출할 때 이러한 키-값 쌍이 FormData 객체에 자동으로 설정된다!
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
try {
//.get() 메서드를 사용하여 특정 키에 대한 값을 검색할 수 있다.
const title = formData.get("title");
const content = formData.get("content");
if (!title.trim() && !content.trim()) {
// 객체에서는 length의 직접적인 사용이 불가.
// if (!title || title.trim().length === 0)와 같이
// length 속성을 사용하기 전에 null 체크를 수행해야 함.
// title이 null이 아닌 경우에만 title.length를 사용할 수 있다.
throw new Error("모든 항목을 입력해주세요.");
} else if (!title.trim()) {
throw new Error("제목을 입력해주세요.");
} else if (!content.trim()) {
throw new Error("내용을 입력해주세요");
}
const response = await fetch("/api/new", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error("네트워크 응답이 올바르지 않습니다.");
}
setMessage("새 글이 등록되었습니다😊");
router.push("/list");
} catch (error) {
console.error(error, "");
setMessage(`${error.message} `);
}
};
v1: 폼의 각 입력 필드에서 직접 데이터를 가져오는 방식.
폼의 필드 수가 적고, 처리해야 할 데이터가 많지 않을 때 적합
v2: FormData 객체와 Object.fromEntries()를 사용하여 폼 데이터를 처리. 폼의 필드가 많고, 다루어야 할 데이터가 복잡하거나 동적일 때 적절하다. 코드가 좀 더 일반화되어 있어 다양한 폼에 쉽게 적용할 수 있다.
결론은 폼의 필드수가 적기 때문에 다시 v1 코드로 돌아갔다.
// /src/app/write/page.js
"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import styles from "./write.module.css";
export default function WritePage() {
const router = useRouter();
const [message, setMessage] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
const title = e.target.title.value;
const content = e.target.content.value;
try {
if (title.length === 0 && content.length === 0) {
throw new Error("모든 항목을 입력해주세요.");
} else if (title.length === 0) {
throw new Error("제목을 입력해주세요.");
} else if (content.length === 0) {
throw new Error("내용을 입력해주세요");
}
const response = await fetch("/api/new", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title, content }),
});
if (!response.ok) {
throw new Error("네트워크 응답이 올바르지 않습니다.");
}
setMessage("새 글이 등록되었습니다😊");
router.push("/list");
} catch (error) {
console.error(error, "");
setMessage(error.message);
}
};
return (
<div className={styles["flex-col"]}>
<h2>게시물 작성</h2>
{message && <p>{message}</p>}
<form
className={`${styles.form} ${styles["flex-col"]}`}
onSubmit={handleSubmit}
>
<input className={styles.input} name="title" placeholder="글제목" />
<textarea name="content" cols="30" rows="10" />
<button type="submit">글 작성</button>
</form>
</div>
);
}
확실히 pages router 방식이 훨씬 편하당...................