Next.js에서는 Node.js Serverless function으로 api endpoint를 쉽게 만들 수 있다.
/pages/api
디렉토리에 함수 하나만 만들면 된다.
옵션에 따라 Serverless 가능
page/api/user.js
는 상태코드 200과 함께 json 형식의 응답을 한다.
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
확인해보자.
다른 HTTP method(POST, GET, DELETE 등)를 처리하기 위해 'req.method'를 사용할 수 있다.
export default function handler (req, res) => {
if (req.method === 'POST') {
// POST request 처리
} else {
// 다른 HTTP method 처리
}
}
ㆍreq : 요청 데이터
ㆍres : 응답 데이터
API Routes도 dynamic routes를 제공해준다.
예를 들어, pages/api/post/[pid].js
파일이 다음과 같다고 하자.
export default function handler(req, res) {
const { pid } = req.query
res.end(`Post: ${pid}`)
}
/api/post/abc
를 호출하면 'Post: abc'가 나타날 것이다.
동적 경로 라우팅과 같다.
RESTful api 패턴은 대표적으로 아래와 같다.
RESTful api가 무엇인지는 여기 를 참고하자.
ㆍGET api/posts
-블로그 글들의 리스트를 불러온다.
ㆍGET api/posts/12345
- 글 아이디(예시)가 12345인 글의 api 결과를 불러온다.
두가지 방법으로 구현할 수 있는데,
-Option 1:
/api/posts.js
/api/posts/[postId].js
-Option 2:
/api/posts/index.js
/api/posts/[postId].js
두 개는 동일하다. 두 방법 모두 리스트가 반환될 지, 글 한 개가 반환될 지 코드를 구분해서 사용할 수 있기 때문에 추후에 Serverless 형태로 가기 좋은 구조이다.
실제로 서버 구현할 때 많이 사용되는 방법이다.
/api/posts/[postId].js
만 사용하는 세번 째 옵션은 유효하지 않다.
Dynamic Routes(Catch-all routes를 포함하는)가 'undefined' 상태를 가지지 않기 때문에! 그래서 /api/posts
는 /api/posts/[postId].js
와 매치되지 않을 것이다.
위에서 api/post/12345
가 url 하나에만 매칭되었다면, api/post/12345/a
, api/post/12345/a/b
와 같이 여러 depth에 걸친 url을 함께 다루기도 가능하다.
방법은 파일명의 대괄호 안에 점 3개(...)를 추가하여 모든 경로가 잡히도록 확장시키는 것!
pages/api/post/[...slug].js
는 /api/post/a
와 매치된다. 또한, /api/post/a/b
와 /api/post/a/b/c
등에도 전부 매칭이 이루어진다.
만약 api/post/a
url이 매칭되면 req객체의 query는 다음과 같다.
{ "slug": ["a"] }
url이 api/post/a/b/c
인 경우,
{ "slug": ["a","b","c"] }
export default function handler(req, res) {
const { slug } = req.query
res.end(`Post: ${slug.join(', ')}`)
}
리턴 값은 "Post: a,b,c"가 된다.
추천하는 방법은 아니지만, api/post
와 api/post/a
두 개의 경로를 한 번에 다룰수 있기는 하다.
방법은 파일명에 대괄호 두 번 사용하기!
pages/api/post/[[...slug]]
query 값은 아래와 같이 나올 것이다.
{} //
api/post
{ "slug": ["a"] } //api/post/a
{ "slug" : ["a","b"] } //api/post/a/b
사전 정의된 api 라우트 > 동적 api 라우트 > catch all api 라우트
req를 해석하는 built-in된 미들웨어
ㆍreq.cookies : 요청에 쿠키가 보내졌는지 담는 객체. 기본 {}
ㆍreq.query : 쿼리스트링을 포함하는 객체. 기본 {}
ㆍreq.body : content-Type 별로 해석된 body를 담는 객체, body에 아무것도 없으면 null
모든 api 경로는 config
객체를 사용하여 기본 설정을 변경할 수 있다.
export const config = {
api: {
bodyParser: {
sizeLimit: '1mb',
},
},
}
sizeLimit은 해석된 body에 담길 최대 크기이다. (byte 단위)
bodyParser는 body parsing을 활성화, Stream으로 처리하고 싶으면 비활성화
export const config = {
api: {
bodyParser: false,
},
}
아래의 externalResolver
속성은 경로가 외부 확인자(express or connect)에 의해 처리되고 있음을 서버에 알리는 플래그이다.
해당 옵션을 사용하면 확인되지 않은 요청에 대한 경고가 비활성화 된다.
export const config = {
api: {
externalResolver: true,
},
}
적합한 middleware를 connect 연결할 수 있다.
예로, api endpoint를 위해서 CORS
설정이 필요한 경우에 cors 패키지를 활용하여 수행할 수 있다.
cors란? Cross-Origin Resource Sharing(CORS)은 추가적인 HTTP header를 사용해서 애플리케이션이 다른 origin의 리소스에 접근할 수 있도록 하는 메커니즘을 말한다. 하지만 다른 origin에서 내 리소스에 함부로 접근하지 못하게 하기 위해 사용된다.
cors
를 설치하자.
npm i cors
or
yarn add cors
그 후, cors
를 api route에 더해주면 된다.
import Cors from 'cors'
// Initializing the cors middleware
const cors = Cors({
methods: ['GET', 'HEAD'],
})
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
async function handler(req, res) {
// Run the middleware
await runMiddleware(req, res, cors)
// Rest of the API logic
res.json({ message: 'Hello Everyone!' })
}
export default handler
api route의 response는 개발자 환경을 개선시키고, 새로운 api endpoint를 만드는 속도를 높이기 위해, Express.js와 유사한 method set을 포함한다.
ㆍres.status(code)
: 상태 코드를 설정하는 함수이다. 코드는 유효한 HTTP 상태 코드여야 한다. (보통 200이 성공)
ㆍres.json(body)
: json 응답을 보낸다. body는 직렬화가능한 객체여야한다.
ㆍres.send(body)
: HTTP 응답을 전송한다. body는 문자열, 객체, 또는 버퍼일 수 있다.
ㆍres.redirect([status,] path)
: 지정된 경로, url로 리디렉션된다. status는 유효한 HTTP 상태 코드여야 한다. 지정하지 않을 시, 상태 코드는 307(임시 리디렉션)으로 기본 설정된다.
참고
책은 p.135부터 참고하면 될듯하다. (typescript기반이지만 비슷할듯하다.)