Next.js API Routes를 활용하여 Next.js 애플리케이션 내에서 직접 서버리스 백엔드를 구축하는 방법을 배워보세요. 이 가이드는 기본 설정부터 인증, 데이터 영속성 처리 등 고급 기술까지 모든 것을 다룹니다.
Next.js API Routes: 손쉽게 백엔드 구축하기
Next.js는 강력한 기능과 직관적인 구조로 프론트엔드 개발에 혁명을 일으켰습니다. 하지만 백엔드 개발도 크게 단순화할 수 있다는 사실을 알고 계셨나요? Next.js API Routes를 사용하면 Next.js 애플리케이션 내에서 직접 서버리스 API 엔드포인트를 생성할 수 있어, 많은 경우 별도의 백엔드 서버가 필요 없게 됩니다. 이 포괄적인 가이드는 Next.js API Routes를 사용하여 견고하고 확장 가능한 백엔드를 구축하는 과정을 안내합니다.
Next.js API Routes란 무엇인가요?
API Routes는 Next.js 프로젝트의 /pages/api
디렉토리 안에 생성하는 서버리스 함수입니다. 이 함수들은 기존 백엔드 API와 마찬가지로 들어오는 HTTP 요청을 처리하고 응답을 반환합니다. 가장 큰 차이점은 서버리스 함수로 배포된다는 점이며, 이는 서버나 인프라를 직접 관리할 필요가 없다는 것을 의미합니다.
Next.js 프론트엔드와 원활하게 통합되는 가볍고 주문형(on-demand) 백엔드 함수라고 생각할 수 있습니다.
Next.js API Routes 사용의 이점
- 간소화된 개발: 프론트엔드와 백엔드 코드를 모두 같은 프로젝트에서 JavaScript 또는 TypeScript를 사용하여 작성하세요. 더 이상 다른 프로젝트와 기술 사이를 오가며 컨텍스트를 전환할 필요가 없습니다.
- 서버리스 아키텍처: 서버리스 컴퓨팅의 확장성, 신뢰성, 비용 효율성의 이점을 누리세요. 사용한 리소스에 대해서만 비용을 지불합니다.
- 쉬운 배포: Vercel이나 Netlify와 같은 플랫폼을 사용하여 단일 명령어로 전체 애플리케이션(프론트엔드 및 백엔드)을 배포하세요.
- 내장된 보안: Next.js와 서버리스 플랫폼은 API 엔드포인트를 보호하기 위한 내장 보안 기능을 제공합니다.
- 성능 향상: API Routes는 사용자와 더 가까운 곳에 배포될 수 있어 지연 시간을 줄이고 성능을 향상시키며, 특히 전 세계 사용자에게 유리합니다.
- 코드 재사용성: 프론트엔드와 백엔드 간에 코드를 공유하여 코드 중복을 줄이고 유지보수성을 향상시킵니다.
Next.js API Routes 시작하기
JSON 응답을 반환하는 간단한 API 라우트를 만들어 보겠습니다. 먼저, Next.js 프로젝트가 설정되어 있는지 확인하세요. 그렇지 않다면 다음을 사용하여 프로젝트를 생성하세요:
npx create-next-app my-app
cd my-app
이제 /pages/api
디렉토리 안에 hello.js
라는 이름의 파일을 생성하세요:
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
이 코드는 "John Doe"라는 이름이 포함된 JSON 객체로 응답하는 간단한 API 라우트를 정의합니다. 이 API 라우트에 접근하려면 Next.js 개발 서버를 시작하세요:
npm run dev
그런 다음 브라우저를 열고 http://localhost:3000/api/hello
로 이동하세요. 다음과 같은 JSON 응답을 볼 수 있습니다:
{"name": "John Doe"}
API 라우트 핸들러 이해하기
API 라우트의 handler
함수는 두 개의 인수를 받습니다:
req
: 요청 메서드, 헤더, 본문 등 들어오는 요청에 대한 정보를 포함하는http.IncomingMessage
의 인스턴스입니다.res
: 클라이언트에 응답을 보낼 수 있게 해주는http.ServerResponse
의 인스턴스입니다.
이 객체들을 사용하여 다양한 유형의 요청을 처리하고, 요청 본문에서 데이터를 읽고, 응답 헤더를 설정하고, 다양한 유형의 응답을 보낼 수 있습니다.
다양한 HTTP 메서드 처리하기
req.method
속성을 사용하여 들어오는 요청의 HTTP 메서드를 확인하고 그에 따라 다른 메서드를 처리할 수 있습니다. 예를 들어:
// pages/api/method.js
export default function handler(req, res) {
if (req.method === 'GET') {
// GET 요청 처리
res.status(200).json({ message: 'This is a GET request' })
} else if (req.method === 'POST') {
// POST 요청 처리
res.status(200).json({ message: 'This is a POST request' })
} else {
// 다른 메서드 처리
res.status(405).json({ message: 'Method Not Allowed' })
}
}
이 예제에서 API 라우트는 GET 및 POST 요청을 모두 처리합니다. 요청 메서드가 GET이면 "This is a GET request"라는 메시지가 포함된 JSON 객체로 응답합니다. 요청 메서드가 POST이면 "This is a POST request"라는 메시지가 포함된 JSON 객체로 응답합니다. 다른 메서드일 경우, 405 Method Not Allowed 오류로 응답합니다.
요청 본문에서 데이터 읽기
POST, PUT, PATCH 요청의 경우 종종 요청 본문에서 데이터를 읽어야 합니다. Next.js는 JSON 및 URL 인코딩된 요청 본문 파싱을 기본적으로 지원합니다. JSON 요청 본문을 파싱하려면 req.body
속성을 사용할 수 있습니다. 예를 들어:
// pages/api/post.js
export default async function handler(req, res) {
if (req.method === 'POST') {
const { name, email } = req.body
// 데이터 처리
console.log('Name:', name)
console.log('Email:', email)
res.status(200).json({ message: 'Data received successfully' })
} else {
res.status(405).json({ message: 'Method Not Allowed' })
}
}
이 API 라우트를 테스트하려면 Postman이나 curl과 같은 도구를 사용하여 JSON 본문과 함께 POST 요청을 보낼 수 있습니다:
curl -X POST -H "Content-Type: application/json" -d '{"name": "Jane Doe", "email": "jane.doe@example.com"}' http://localhost:3000/api/post
응답 헤더 설정하기
res.setHeader()
메서드를 사용하여 응답 헤더를 설정할 수 있습니다. 이는 콘텐츠 유형, 캐시 제어 및 기타 중요한 정보를 설정하는 데 유용합니다. 예를 들어:
// pages/api/headers.js
export default function handler(req, res) {
res.setHeader('Content-Type', 'application/json')
res.setHeader('Cache-Control', 's-maxage=3600')
res.status(200).json({ message: 'Hello, world!' })
}
이 예제에서 API 라우트는 Content-Type
헤더를 application/json
으로 설정하여 응답이 JSON 객체임을 나타냅니다. 또한 Cache-Control
헤더를 s-maxage=3600
으로 설정하여 브라우저와 CDN에 응답을 최대 1시간 동안 캐시하도록 지시합니다.
오류 처리
API 라우트에서 오류를 정상적으로 처리하는 것이 중요합니다. try-catch 블록을 사용하여 예외를 포착하고 클라이언트에 적절한 오류 응답을 보낼 수 있습니다. 예를 들어:
// pages/api/error.js
export default async function handler(req, res) {
try {
// 오류 시뮬레이션
throw new Error('Something went wrong')
} catch (error) {
console.error(error)
res.status(500).json({ message: 'Internal Server Error' })
}
}
이 예제에서 API 라우트는 새 Error
객체를 던져 오류를 시뮬레이션합니다. catch 블록은 오류를 포착하여 콘솔에 기록하고 클라이언트에 500 내부 서버 오류 응답을 보냅니다. 프로덕션 환경에서는 Sentry나 Datadog과 같은 견고한 로깅 시스템 사용을 고려하세요.
데이터베이스에 연결하기
API 라우트의 가장 일반적인 사용 사례 중 하나는 데이터베이스에 연결하는 것입니다. Next.js API Routes는 다음을 포함한 다양한 데이터베이스와 원활하게 통합됩니다:
- MongoDB: 유연하고 비정형 데이터에 적합한 인기 있는 NoSQL 데이터베이스입니다.
- PostgreSQL: 신뢰성과 데이터 무결성으로 유명한 강력한 오픈소스 관계형 데이터베이스입니다.
- MySQL: 웹 애플리케이션에 널리 사용되는 또 다른 인기 있는 오픈소스 관계형 데이터베이스입니다.
- Firebase: 실시간 데이터베이스 및 기타 서비스를 제공하는 클라우드 기반 플랫폼입니다.
- FaunaDB: 글로벌 애플리케이션을 위해 설계된 서버리스 데이터베이스입니다.
다음은 Next.js API 라우트에서 MongoDB 데이터베이스에 연결하는 방법의 예입니다:
// pages/api/mongodb.js
import { MongoClient } from 'mongodb'
const uri = process.env.MONGODB_URI
const options = {}
let client
let clientPromise
if (!process.env.MONGODB_URI) {
throw new Error('Please add your Mongo URI to .env.local')
}
if (process.env.NODE_ENV === 'development') {
// 개발 모드에서는 HMR(Hot Module Replacement)로 인한 모듈 리로드 시
// 값을 유지하기 위해 전역 변수를 사용합니다.
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
global._mongoClientPromise = client.connect()
}
clientPromise = global._mongoClientPromise
} else {
// 프로덕션 모드에서는 전역 변수를 사용하지 않는 것이 가장 좋습니다.
client = new MongoClient(uri, options)
clientPromise = client.connect()
}
// 모듈 범위의 MongoClient 프라미스를 내보냅니다. 이렇게 별도의
// 모듈에서 수행하면 여러 함수에서 클라이언트를 안전하게 재사용할 수 있습니다.
// 참조: https://github.com/vercel/next.js/blob/canary/examples/with-mongodb/lib/mongodb.js
export default async function handler(req, res) {
try {
const client = await clientPromise
const db = client.db(process.env.MONGODB_DB)
const collection = db.collection('users')
const users = await collection.find({}).toArray()
res.status(200).json({ users })
} catch (e) {
console.error(e)
res.status(500).json({ message: 'Failed to fetch users' })
}
}
이 코드를 실행하기 전에 mongodb
패키지가 설치되어 있는지 확인하세요:
npm install mongodb
또한 MONGODB_URI
와 MONGODB_DB
환경 변수를 설정해야 합니다. 이 변수들은 .env.local
파일(또는 프로덕션을 위한 호스팅 제공업체의 환경 변수 설정)에 정의되어야 합니다. MONGODB_URI
는 MongoDB 데이터베이스 연결 문자열을 포함하고, MONGODB_DB
는 데이터베이스 이름을 지정합니다.
인증 및 인가
API 라우트를 보호하는 것은 보안에 매우 중요합니다. Next.js API Routes는 다음을 포함한 다양한 인증 및 인가 기술을 사용하여 보호할 수 있습니다:
- JSON 웹 토큰 (JWT): 당사자 간에 정보를 JSON 객체로 안전하게 전송하기 위한 표준입니다.
- API 키: API 엔드포인트에 대한 접근을 제한하는 간단한 방법입니다.
- OAuth: 사용자가 자신의 자격 증명을 공유하지 않고도 제3자 애플리케이션에 리소스 접근 권한을 부여할 수 있도록 하는 위임 프로토콜입니다.
- NextAuth.js: Next.js 애플리케이션을 위한 완전한 오픈소스 인증 솔루션입니다.
다음은 JWT 인증을 사용하여 API 라우트를 보호하는 방법의 예입니다:
// pages/api/protected.js
import jwt from 'jsonwebtoken'
const secret = process.env.JWT_SECRET
export default function handler(req, res) {
const token = req.headers.authorization?.split(' ')[1]
if (!token) {
return res.status(401).json({ message: 'Unauthorized' })
}
try {
const decoded = jwt.verify(token, secret)
// "decoded" 객체에는 토큰에 포함된 사용자 정보가 들어 있습니다.
// 예: const userId = decoded.userId;
// 요청 처리 계속
res.status(200).json({ message: 'Protected resource accessed successfully' })
} catch (error) {
return res.status(401).json({ message: 'Invalid token' })
}
}
이 코드를 실행하기 전에 jsonwebtoken
패키지가 설치되어 있는지 확인하세요:
npm install jsonwebtoken
또한 JWT_SECRET
환경 변수를 설정해야 합니다. 이것은 JWT를 서명하고 확인하는 데 사용되는 강력하고 무작위로 생성된 비밀 키여야 합니다. 이를 안전하게 저장하고 클라이언트 측 코드에 절대 노출하지 마세요.
미들웨어
Next.js는 Express.js와 같은 방식으로 API 라우트를 위한 전통적인 미들웨어를 제공하지는 않지만, API 라우트 핸들러를 재사용 가능한 함수로 감싸 유사한 기능을 구현할 수 있습니다. 이를 통해 다음과 같은 작업을 수행할 수 있습니다:
- 인증: API 엔드포인트에 접근하기 전에 사용자 자격 증명을 확인합니다.
- 인가: 사용자가 특정 작업을 수행할 수 있는 필요한 권한을 가지고 있는지 확인합니다.
- 로깅: 감사 및 디버깅 목적으로 들어오는 요청과 나가는 응답을 기록합니다.
- 유효성 검사: 요청 데이터가 특정 기준을 충족하는지 검증합니다.
- 속도 제한: 사용자가 주어진 시간 내에 할 수 있는 요청 수를 제한하여 API를 남용으로부터 보호합니다.
다음은 간단한 로깅 미들웨어를 만드는 방법의 예입니다:
// utils/middleware.js
export function withLogging(handler) {
return async function(req, res) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`)
return handler(req, res)
}
}
이 미들웨어를 사용하려면 API 라우트 핸들러를 withLogging
함수로 감싸기만 하면 됩니다:
// pages/api/logged.js
import { withLogging } from '../../utils/middleware'
async function handler(req, res) {
res.status(200).json({ message: 'This request was logged' })
}
export default withLogging(handler)
Next.js API Routes 구축을 위한 모범 사례
- API 라우트를 작고 집중적으로 유지하세요. 각 API 라우트는 특정 작업이나 리소스를 처리해야 합니다.
- 민감한 데이터에는 환경 변수를 사용하세요. 비밀번호나 API 키를 코드에 하드코딩하지 마세요.
- 보안 취약점을 방지하기 위해 요청 데이터의 유효성을 검사하세요. Joi나 Yup과 같은 라이브러리를 사용하여 요청 본문을 검증하세요.
- 오류를 정상적으로 처리하고 유용한 오류 메시지를 제공하세요. try-catch 블록을 사용하고 오류를 중앙 위치에 기록하세요.
- 성능 향상을 위해 캐싱을 사용하세요. 자주 접근하는 데이터를 캐시하여 데이터베이스 부하를 줄이세요.
- API 라우트의 성능과 오류를 모니터링하세요. Sentry나 Datadog과 같은 모니터링 도구를 사용하여 API의 상태를 추적하세요.
- Swagger나 OpenAPI와 같은 도구를 사용하여 API 라우트를 문서화하세요. 이는 다른 개발자들이 API를 더 쉽게 사용하도록 만듭니다.
- 타입 안전성을 위해 TypeScript 사용을 고려하세요. TypeScript는 오류를 조기에 발견하고 코드의 유지보수성을 향상시키는 데 도움이 될 수 있습니다.
- 처음부터 국제화(i18n)를 고려하세요. 애플리케이션이 다른 국가의 사용자에 의해 사용될 경우, 여러 언어와 통화를 지원하도록 API 라우트를 설계하세요. 예를 들어, 전자 상거래용 API 엔드포인트는 사용자 위치에 따라 다른 세율과 배송비를 처리해야 할 수 있습니다.
- 적절한 CORS(Cross-Origin Resource Sharing) 구성을 구현하세요. 이는 API가 Next.js 애플리케이션과 다른 도메인에서 접근될 때 매우 중요합니다. 승인된 출처만 API 리소스에 접근할 수 있도록 CORS를 신중하게 구성하세요.
고급 기술
백그라운드 작업
API 응답을 차단해서는 안 되는 장기 실행 작업의 경우, 백그라운드 작업을 사용하는 것을 고려하세요. BullMQ나 Bree와 같은 라이브러리를 사용하여 백그라운드 작업을 관리하고 비동기적으로 처리할 수 있습니다.
웹소켓
실시간 애플리케이션의 경우, Next.js API 라우트에서 웹소켓을 사용할 수 있습니다. Socket.IO나 ws와 같은 라이브러리를 사용하면 클라이언트와 서버 간의 영구적인 연결을 쉽게 설정할 수 있습니다.
GraphQL
데이터를 가져오는 더 유연하고 효율적인 방법이 필요하다면 GraphQL 사용을 고려하세요. Apollo Server나 Yoga와 같은 라이브러리를 사용하여 Next.js 애플리케이션에 GraphQL API 엔드포인트를 생성할 수 있습니다.
결론
Next.js API Routes는 Next.js 애플리케이션 내에서 직접 서버리스 백엔드를 구축할 수 있는 강력하고 편리한 방법을 제공합니다. 서버리스 아키텍처의 이점을 활용하여 개발을 단순화하고, 성능을 향상시키며, 비용을 절감할 수 있습니다. 간단한 문의 양식을 만들든 복잡한 전자 상거래 플랫폼을 구축하든, Next.js API Routes는 견고하고 확장 가능한 백엔드를 손쉽게 만드는 데 도움을 줄 수 있습니다. 기본 원리에 대한 확실한 이해와 모범 사례를 적용하면, 이 강력한 도구를 활용하여 효율적이고 안전하며 전 세계적으로 접근 가능한 애플리케이션을 만들 수 있습니다.