요청 페이지 생성해보기
본래는 API 라우터를 먼저 생성하는게 맞지만...
익숙하지 않은 API라우터 페이지보다는 프론트 페이지를 먼저 만들어봐야지.
pages/quiz/create.tsx파일을 생성한다.
import axios from "axios";
import React, { useState } from "react";
const PostCreate = () => {
const [content, setContent] = useState("");
const onSubmit = async (e) => {
e.preventDefault();
try {
const res = await axios.post("/api/articles", { content });
if (res.status === 200) {
alert(res.data.message);
}
} catch (e) {
alert(e.message);
}
};
return (
<div>
<form method="post">
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="무슨 일이 일어나고 있나요?"
/>
<button type="submit" onClick={onSubmit}>
전송하기
</button>
</form>
</div>
);
};
export default PostCreate;
textarea에 내용을 입력하고,
해당 내용을 '/api/articles'에,
post 요청으로,
body : { content } 처럼 바디에 담아 보낸다.
api route 만들기 준비
프로젝트 폴더/db/articles.json
폴더와 파일을 먼저 만든다.articles.json
폴더의 내용을[]
으로 만든다.
api에서 파일 읽기 쓰기를 실행하기 위해서는 미리 파일이 있어야 한다. 따라서 위와 같이 초기화해준다.
또 JSON.parse 메서드를 쓰기 위해서도 파일 내부가 비어있으면 안된다. (파일 내부가 비어있으면 unexpected end 에러를 발생시키며, 클라이언트에서 받는 에러코드는 500이다.)
api route 만들기
api 주소가 잘못되면 404가 뜬다. 정확한 경로에 아래의 파일을 만들자
요청 주소 : /api/articles
실행 되는 파일 : '프로젝트 폴더/api/articles
.tsx'
핸들러 만들기
import { NextApiHandler } from "next";
const articlesApiHandler: NextApiHandler = (req, res) => {
};
export default articlesApiHandler;
NextApiHandler는 클라이언트로부터 http 객체를 주고 받는 API함수의 타입이다.
handler는 req와 res 객체를 파라미터로 받는다.
req객체는 아래와 같은 프로퍼티를 가지고 있으며,
해당 프로퍼티들로부터 필요한 정보들을 읽어들일 수 있다.
req {
params, // 라우팅 매개 변수
query, // URL 쿼리 매개 변수
body, // 요청 본문 데이터
cookies, // HTTP 쿠키
session, // 세션
headers, // HTTP 헤더
method, // HTTP 메서드
url, // URL
path, // URL 경로
protocol, // 프로토콜 (http 또는 https)
ip, // 클라이언트 IP 주소
xhr, // 요청이 Ajax 요청인지 여부
... // 기타 속성 및 메소드
}
res는 아래와 같은 프로퍼티를 가지고 있으며,
핸들러 내부에서 해당 값을 변경하여 반환한다.
res {
status, // HTTP 상태 코드
setHeader, // HTTP 응답 헤더 설정 메소드
send, // 응답 데이터 전송 메소드
json, // JSON 형태의 응답 데이터 전송 메소드
cookie, // HTTP 쿠키 설정 메소드
clearCookie, // HTTP 쿠키 삭제 메소드
redirect, // URL 리다이렉트 메소드
... // 기타 속성 및 메소드
}
API 작성하기
import { NextApiHandler } from "next";
import fs from "fs/promises";
import path from "path";
interface Article {
content: string;
}
const articlesApiHandler: NextApiHandler = async (req, res) => {
//메서드가 post일 때에,
if (req.method === "POST") {
//body에 있는 content 프로퍼티를 읽는다.
const { content } = req.body as { content: string };
//프로세스의 current working directory/db/articles.json
const filePath = path.join(process.cwd(), "db", "articles.json");
//fs.readFileSync는 동기적으로 처리한다. 반면 readFile는 비동기적으로 작동한다.
const jsonData = await fs.readFile(filePath, "utf-8");
const data: Article[] = JSON.parse(jsonData);
// 읽어온 json의 배열에 새로운 객체를 추가한다.
data.push({ content });
// 배열을 다시 문자열로 만들어서 비동기적으로 파일을 작성한다.
await fs.writeFile(filePath, JSON.stringify(data));
res.status(200).json({ message: "글이 작성되었습니다." });
} else {
res.status(405).json({ message: "메서드가 잘못 되었습니다." });
}
};
export default articlesApiHandler;
readFile, writeFile은 기본적으로 비동기 처리이다.
동기적인 처리를 위해서는 writeFileSync 등을 사용한다.
(일반적으로 비동기 처리를 사용하는 것이 더 많은 요청을 처리하기에 유리하다.)
내용이 추가되었디.
유의할 점
'퍼블릭' 폴더는 외부에 공개되며, 외부에서 접근할 수 있다는 점에 유의한다.
따라서 데이터는 '퍼블릭'폴더가 아닌 'data' 혹은 'db' 폴더에 저장한다.