한국어

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 사용의 이점

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 함수는 두 개의 인수를 받습니다:

이 객체들을 사용하여 다양한 유형의 요청을 처리하고, 요청 본문에서 데이터를 읽고, 응답 헤더를 설정하고, 다양한 유형의 응답을 보낼 수 있습니다.

다양한 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는 다음을 포함한 다양한 데이터베이스와 원활하게 통합됩니다:

다음은 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_URIMONGODB_DB 환경 변수를 설정해야 합니다. 이 변수들은 .env.local 파일(또는 프로덕션을 위한 호스팅 제공업체의 환경 변수 설정)에 정의되어야 합니다. MONGODB_URI는 MongoDB 데이터베이스 연결 문자열을 포함하고, MONGODB_DB는 데이터베이스 이름을 지정합니다.

인증 및 인가

API 라우트를 보호하는 것은 보안에 매우 중요합니다. Next.js API Routes는 다음을 포함한 다양한 인증 및 인가 기술을 사용하여 보호할 수 있습니다:

다음은 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 라우트 핸들러를 재사용 가능한 함수로 감싸 유사한 기능을 구현할 수 있습니다. 이를 통해 다음과 같은 작업을 수행할 수 있습니다:

다음은 간단한 로깅 미들웨어를 만드는 방법의 예입니다:

// 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 응답을 차단해서는 안 되는 장기 실행 작업의 경우, 백그라운드 작업을 사용하는 것을 고려하세요. 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는 견고하고 확장 가능한 백엔드를 손쉽게 만드는 데 도움을 줄 수 있습니다. 기본 원리에 대한 확실한 이해와 모범 사례를 적용하면, 이 강력한 도구를 활용하여 효율적이고 안전하며 전 세계적으로 접근 가능한 애플리케이션을 만들 수 있습니다.