관련 링크
이 글에서는 Node.js에서의 사용을 다루고 있습니다. Serverless Function을 지원하는 언어들은 다음과 같습니다: Node.js, Go, Python, Ruby
먼저 명령 프롬포트를 이용해 예시 프로젝트를 생성해보겠습니다.
> npm create vite@latest
√ Project name: ... serverless-function-example
√ Select a framework: » react
√ Select a variant: » react
...
> cd serverless-function-example
> npm install
여기서는 Vite를 사용하여 React 프로젝트를 생성하였습니다.
그런 다음 Serverless Function에 쓰이는 API 폴더를 생성해보겠습니다.
> mkdir api
Next.js를 사용하는 경우 pages 폴더에 api 폴더를 생성하여야 합니다.
api 폴더는 Serverless Function 전용입니다. 이제 그 속에 있는 JS 파일들은 모두 경로가 됩니다.
테스트 경로를 생성해보겠습니다. 방금 만든 api 폴더 속에 test.js를 생성해보겠습니다.
이 파일 속에서 export되는 함수가 처리를 담당합니다.
기본적인 형태는 다음과 같습니다.
// api/test.js
export default function handler(req, res) {
res.json({
queries: req.query,
cookies: req.cookies,
body: req.body,
test: 'Hello, world!'
})
}
형태는 express.js와 비슷합니다. 그밖의 요청되는 값들은 req.query에 담깁니다. 만약 비동기로 처리하고 싶다면 다음과 같이 함수 앞에 async을 붙이면 됩니다.
export default async function handler(req, res) {
// ...
}
지원되는 메소드는 다음과 같습니다:
- req.cookies
- req.body
- res.status(<상태 코드>)
- res.send(<Body>)
- res.json(<JSON>)
- res.redirect(<URL>)
- res.redirect(<상태 코드>, <URL>)
이 기능을 실행하기 위해서 vercel dev 명령어를 실행시키겠습니다. 만약 Vercel 프로젝트가 생성되지 않았다면 이 명령어로 생성시킬 수 있습니다.
Vercel CLI가 설치되지 않았다면 npm i -g vercel 명령어로 설치할 수 있습니다.
서버 실행을 성공적으로 마쳤다면 http://localhost:3000으로 들어가 확인할 수 있습니다. URL은 다를 수 있습니다.
방금은 test.js를 생성하였기 때문에 <주소>/api/test로 접속하여 확인할 수 있습니다. 접속하면 방금 짠 코드에 맞는 결과가 나옵니다.
{
"queries": {},
"cookies": {},
"test": "Hello, world!"
}
이제 간단한 API를 만들어보겠습니다. <주소>/api/v1/random으로 들어가면 파일에서 설정한 글들 중에서 무작위로 하나를 뽑는 프로그램을 만들어보겠습니다.
글들을 설정하기 위해 파일을 생성하겠습니다. 하지만 일반적인 JS 파일로 생성하면 해당 파일도 경로로 인식되어 문제가 생길 수 있습니다. 따라서 우리는 파일 이름 앞에 _(underscore)를 붙여 경로로 인식되는 것을 막을 수 있습니다. _posts.js 파일을 생성해보겠습니다.
// api/_posts.js
export default [
'lorem ipsum',
'dolor sit amet',
'consectetur adipiscing elit',
'Donec euismod',
'nisl eget consectetur sagittis',
'nisl nunc egestas nisi',
'eu consectetur nisi nisl euismod nisi'
]
다음과 같은 코드로 글들을 생성하였습니다. 이제 이 글에서 무작위로 추출하는 코드를 만들겠습니다.
// api/v1/random.js
import posts from '../_posts'
export default function handler(req, res) {
res.json({
test: posts[Math.floor(Math.random() * posts.length)]
})
}
이런 식으로 임포트할 수도 있습니다.
<주소>/api/v1/random으로 들어가 정상 작동하는지 확인합니다.
{
"test": "nisl nunc egestas nisi"
}
다음과 같이 무작위로 추출하여 보내는 것을 볼 수 있습니다.
또 방금 생성한 _posts 파일도 경로로 인식되지 않는 것을 확인할 수도 있습니다.
만약 값을 받아 처리하는 프로그램을 만들고 싶다면 req.query나 req.body를 사용할 수 있습니다.
예시로 Query 문자열을 뒤집어서 보내는 API를 만들어보겠습니다.
// api/v1/reverse.js
export default function handler(req, res) {
if (!('value' in req.query)) return res.status(400).json(
{
error: 'value가 필요합니다.'
}
)
res.send(
req.query.value
.split('')
.reverse()
.join('')
)
}
<주소>/api/v1/reverse?value=<문자열>로 사용할 수 있습니다. 만약 이때 value가 주어지지 않았다면 오류를 보내고, 주어졌다면 문자열을 뒤집어 응답합니다.
◀ 제대로 응답한 모습
값이 주어지지 않았을 때의 모습 ▶
만약 Query 대신 경로에 직접적으로 주고 싶은 경우에는 다음처럼 할 수 있습니다.
파일을 대괄호로 묶어 처리하면 그 경로 뒤 값(/api/v1/reverse/<값>)을 req.query에 포함합니다. 다음 예시로 쉽게 이해할 수 있습니다.
// api/v1/reverse/[value].js
export default function handler(req, res) {
res.send(
req.query.value
.split('')
.reverse()
.join('')
)
}
대괄호 안의 이름인 value를 req.query에 넣어 보냅니다. 또 req.query.value가 없었을 때의 오류 처리기를 없앴습니다. 뒤에 값이 주어지지 않는다면 404 오류를 어차피 Vercel에서 보내기 때문입니다.
◀ 정상적으로 처리된 모습
오류 처리기는 다음과 같이 사용할 수 있습니다.
// api/v1/reverse/[value].js
export default function handler(req, res) {
if (req.query.value === 'throw error!') return res.status(400).json({ error: 'Error!' })
// 아래 생략
}
◀ 오류가 발생된 모습
상태 코드 확인 ▶
기본적인 개념은 끝났습니다. 이제 이 API를 실제 React 앱에 사용해보겠습니다.
API에 요청을 보내기 위해 axios를 설치하겠습니다.
> npm i axios
added 8 packages, and audited 94 packages in 4s
9 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
그 다음 React 앱에 코드를 짜겠습니다. React는 이 글의 주제가 아니기 때문에 설명은 생략하겠습니다.
React 앱 ▼
// src/App.jsx
import { useState } from 'react'
import axios from 'axios'
import styles from './App.module.css'
function App() {
const [request, setRequest] = useState('') // 요청
const [response, setResponse] = useState('') // 응답
const getResponse = async (route) => {
// 응답을 받고 처리하는 함수
const res = await axios.get(`/api/v1/${route}`)
setResponse(
typeof res.data === 'object' ? // 응답이 JSON이라면 문자열로 변환
JSON.stringify(res.data) : res.data
)
}
const submit = (e) => { // 글 뒤집기 요청이 들어왔을 때
e.preventDefault()
if (request.length > 0) getResponse(`reverse/${request}`)
else alert('글을 써주세요.') // 글이 없다면 알림
}
return (
<div className={styles.App}>
<textarea
value={response}
readOnly={true} />
<button type={'button'} onClick={() => getResponse('random')}>무작위 글</button>
<form
className={styles.form}
onSubmit={submit}>
<input
type={'text'}
value={request}
onChange={(e) => setRequest(e.target.value)}
placeholder={'요청할 글'} />
<button type={'submit'}>글 뒤집기</button>
</form>
</div>
)
}
export default App
CSS 파일(scoped) ▼
/* src/App.module.css */
.App {
display: grid;
justify-content: left;
gap: 10px;
}
.form {
display: flex;
gap: 5px;
}
◀ 무작위 글 생성 결과
글 뒤집기 결과 ▶
본 코드는 BitBucket에 올려두었습니다. 바로 가기
사이트는 이곳에서 확인할 수 있습니다.