server

server/ 디렉토리는 애플리케이션에 API 및 서버 핸들러를 등록하는 데 사용됩니다.

Nuxt는 이 디렉토리 내의 파일을 자동으로 스캔하여 API 및 서버 핸들러를 Hot Module Replacement(HMR) 지원과 함께 등록합니다.

Directory structure
-| server/
---| api/
-----| hello.ts      # /api/hello
---| routes/
-----| bonjour.ts    # /bonjour
---| middleware/
-----| log.ts        # 모든 요청을 로그

각 파일은 defineEventHandler() 또는 eventHandler()(별칭)로 정의된 기본 함수를 내보내야 합니다.

핸들러는 JSON 데이터를 직접 반환하거나, Promise를 반환하거나, event.node.res.end()를 사용하여 응답을 보낼 수 있습니다.

server/api/hello.ts
export default defineEventHandler((event) => {
  return {
    hello: 'world'
  }
})

이제 페이지와 컴포넌트에서 이 API를 범용적으로 호출할 수 있습니다:

pages/index.vue
<script setup lang="ts">
const { data } = await useFetch('/api/hello')
</script>

<template>
  <pre>{{ data }}</pre>
</template>

서버 라우트

~/server/api 내부의 파일은 라우트에서 자동으로 /api 접두사가 붙습니다.

/api 접두사 없이 서버 라우트를 추가하려면, ~/server/routes 디렉토리에 넣으세요.

예시:

server/routes/hello.ts
export default defineEventHandler(() => 'Hello World!')

위 예시를 기준으로, /hello 라우트는 http://localhost:3000/hello에서 접근할 수 있습니다.

현재 서버 라우트는 pages와 같은 동적 라우트의 전체 기능을 지원하지 않는다는 점에 유의하세요.

서버 미들웨어

Nuxt는 ~/server/middleware 내의 모든 파일을 자동으로 읽어 프로젝트의 서버 미들웨어로 생성합니다.

미들웨어 핸들러는 모든 다른 서버 라우트보다 먼저 모든 요청에서 실행되어 헤더를 추가하거나 확인하고, 요청을 로그하거나, 이벤트의 요청 객체를 확장할 수 있습니다.

미들웨어 핸들러는 아무것도 반환하거나(또는 요청을 닫거나 응답하지 않고) 요청 컨텍스트를 검사하거나 확장하거나 오류를 던지기만 해야 합니다.

예시:

server/middleware/log.ts
export default defineEventHandler((event) => {
  console.log('새로운 요청: ' + getRequestURL(event))
})
server/middleware/auth.ts
export default defineEventHandler((event) => {
  event.context.auth = { user: 123 }
})

서버 플러그인

Nuxt는 ~/server/plugins 디렉토리 내의 모든 파일을 자동으로 읽어 Nitro 플러그인으로 등록합니다. 이를 통해 Nitro의 런타임 동작을 확장하고 라이프사이클 이벤트에 후킹할 수 있습니다.

예시:

server/plugins/nitroPlugin.ts
export default defineNitroPlugin((nitroApp) => {
  console.log('Nitro 플러그인', nitroApp)
})
Read more in Nitro 플러그인.

서버 유틸리티

서버 라우트는 unjs/h3에 의해 구동되며, 유용한 헬퍼 세트를 제공합니다.

Read more in 사용 가능한 H3 요청 헬퍼.

~/server/utils 디렉토리 내에 직접 헬퍼를 추가할 수도 있습니다.

예를 들어, 원래 핸들러를 감싸고 최종 응답을 반환하기 전에 추가 작업을 수행하는 커스텀 핸들러 유틸리티를 정의할 수 있습니다.

예시:

server/utils/handler.ts
import type { EventHandler, EventHandlerRequest } from 'h3'

export const defineWrappedResponseHandler = <T extends EventHandlerRequest, D> (
  handler: EventHandler<T, D>
): EventHandler<T, D> =>
  defineEventHandler<T>(async event => {
    try {
      // 라우트 핸들러 전에 무언가를 수행
      const response = await handler(event)
      // 라우트 핸들러 후에 무언가를 수행
      return { response }
    } catch (err) {
      // 오류 처리
      return { err }
    }
  })

서버 타입

이 기능은 Nuxt >= 3.5부터 사용할 수 있습니다.

IDE에서 'nitro'와 'vue'의 자동 임포트 구분을 명확히 하려면, 다음과 같은 내용을 가진 ~/server/tsconfig.json을 추가할 수 있습니다:

server/tsconfig.json
{
  "extends": "../.nuxt/tsconfig.server.json"
}

현재 이 값들은 타입 체크(nuxt typecheck) 시에는 적용되지 않지만, IDE에서 더 나은 타입 힌트를 얻을 수 있습니다.

레시피

라우트 파라미터

서버 라우트는 파일 이름에 대괄호를 사용하여 /api/hello/[name].ts와 같이 동적 파라미터를 사용할 수 있으며, event.context.params를 통해 접근할 수 있습니다.

server/api/hello/[name].ts
export default defineEventHandler((event) => {
  const name = getRouterParam(event, 'name')

  return `Hello, ${name}!`
})
또는, 런타임 및 타입 안전성을 위해 Zod와 같은 스키마 검증기와 함께 getValidatedRouterParams를 사용할 수 있습니다.

이제 /api/hello/nuxt에서 이 API를 범용적으로 호출하여 Hello, nuxt!를 얻을 수 있습니다.

HTTP 메서드 매칭

핸들러 파일 이름에 .get, .post, .put, .delete 등을 접미사로 붙여 요청의 HTTP 메서드와 매칭할 수 있습니다.

server/api/test.get.ts
export default defineEventHandler(() => 'Test get handler')
server/api/test.post.ts
export default defineEventHandler(() => 'Test post handler')

위 예시를 기준으로, /test를 다음과 같이 요청하면:

  • GET 메서드: Test get handler 반환
  • POST 메서드: Test post handler 반환
  • 그 외의 메서드: 405 오류 반환

또한, 디렉토리 내에 index.[method].ts를 사용하여 코드를 다르게 구조화할 수 있습니다. 이는 API 네임스페이스를 만들 때 유용합니다.

export default defineEventHandler((event) => {
  // `api/foo` 엔드포인트의 GET 요청 처리
})

캐치올 라우트

캐치올 라우트는 폴백 라우트 처리를 위해 유용합니다.

예를 들어, ~/server/api/foo/[...].ts라는 파일을 만들면, /api/foo/bar/baz와 같이 어떤 라우트 핸들러와도 일치하지 않는 모든 요청에 대한 캐치올 라우트가 등록됩니다.

server/api/foo/[...].ts
export default defineEventHandler((event) => {
  // 라우트 경로를 얻으려면 event.context.path: '/api/foo/bar/baz'
  // 라우트 세그먼트를 얻으려면 event.context.params._: 'bar/baz'
  return `Default foo handler`
})

~/server/api/foo/[...slug].ts와 같이 캐치올 라우트에 이름을 지정하고, event.context.params.slug를 통해 접근할 수 있습니다.

server/api/foo/[...slug].ts
export default defineEventHandler((event) => {
  // 라우트 세그먼트를 얻으려면 event.context.params.slug: 'bar/baz'
  return `Default foo handler`
})

바디 처리

server/api/submit.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  return { body }
})
또는, 런타임 및 타입 안전성을 위해 Zod와 같은 스키마 검증기와 함께 readValidatedBody를 사용할 수 있습니다.

이제 다음과 같이 이 API를 범용적으로 호출할 수 있습니다:

app.vue
<script setup lang="ts">
async function submit() {
  const { body } = await $fetch('/api/submit', {
    method: 'post',
    body: { test: 123 }
  })
}
</script>
POST 메서드로 요청 바디를 받을 수 있도록 파일 이름에 submit.post.ts를 사용합니다. GET 요청에서 readBody를 사용하면 405 Method Not Allowed HTTP 오류가 발생합니다.

쿼리 파라미터

샘플 쿼리 /api/query?foo=bar&baz=qux

server/api/query.get.ts
export default defineEventHandler((event) => {
  const query = getQuery(event)

  return { a: query.foo, b: query.baz }
})
또는, 런타임 및 타입 안전성을 위해 Zod와 같은 스키마 검증기와 함께 getValidatedQuery를 사용할 수 있습니다.

오류 처리

오류가 발생하지 않으면, 상태 코드 200 OK가 반환됩니다.

처리되지 않은 모든 오류는 500 Internal Server Error HTTP 오류를 반환합니다.

다른 오류 코드를 반환하려면, createError를 사용하여 예외를 던지세요:

server/api/validation/[id].ts
export default defineEventHandler((event) => {
  const id = parseInt(event.context.params.id) as number

  if (!Number.isInteger(id)) {
    throw createError({
      statusCode: 400,
      statusMessage: 'ID는 정수여야 합니다',
    })
  }
  return 'All good'
})

상태 코드

다른 상태 코드를 반환하려면, setResponseStatus 유틸리티를 사용하세요.

예를 들어, 202 Accepted를 반환하려면

server/api/validation/[id].ts
export default defineEventHandler((event) => {
  setResponseStatus(event, 202)
})

런타임 설정

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig(event)

  const repo = await $fetch('https://api.github.com/repos/nuxt/nuxt', {
    headers: {
      Authorization: `token ${config.githubToken}`
    }
  })

  return repo
})
useRuntimeConfigevent를 인자로 전달하는 것은 선택 사항이지만, 서버 라우트에서 환경 변수로 덮어쓴 런타임 설정을 얻으려면 전달하는 것이 권장됩니다.

요청 쿠키

server/api/cookies.ts
export default defineEventHandler((event) => {
  const cookies = parseCookies(event)

  return { cookies }
})

컨텍스트 및 헤더 전달

기본적으로, 서버 라우트에서 fetch 요청을 할 때 들어오는 요청의 헤더나 요청 컨텍스트는 전달되지 않습니다. 서버 라우트에서 fetch 요청을 할 때 요청 컨텍스트와 헤더를 전달하려면 event.$fetch를 사용할 수 있습니다.

server/api/forward.ts
export default defineEventHandler((event) => {
  return event.$fetch('/api/forwarded')
})
전달되지 않아야 하는 헤더요청에 포함되지 않습니다. 예를 들어 다음과 같은 헤더가 해당됩니다: transfer-encoding, connection, keep-alive, upgrade, expect, host, accept

응답 후 Promise 대기

서버 요청을 처리할 때, 클라이언트에 대한 응답을 차단하지 않고 비동기 작업(예: 캐싱, 로깅 등)을 수행해야 할 수 있습니다. 이럴 때 event.waitUntil을 사용하여 응답을 지연시키지 않고 백그라운드에서 Promise를 대기할 수 있습니다.

event.waitUntil 메서드는 핸들러가 종료되기 전에 대기할 Promise를 받아, 응답이 전송된 후에도 작업이 완료되도록 보장합니다. 이는 런타임 프로바이더와 통합되어, 응답이 전송된 후 비동기 작업을 처리하는 네이티브 기능을 활용할 수 있습니다.

server/api/background-task.ts
const timeConsumingBackgroundTask = async () => {
  await new Promise((resolve) => setTimeout(resolve, 1000))
};

export default eventHandler((event) => {
  // 응답을 차단하지 않고 백그라운드 작업 예약
  event.waitUntil(timeConsumingBackgroundTask())

  // 즉시 클라이언트에 응답 전송
  return 'done'
});

고급 사용법

Nitro 설정

니트로 설정을 직접 지정하려면 nuxt.confignitro 키를 사용할 수 있습니다.

이것은 고급 옵션입니다. 커스텀 설정은 프로덕션 배포에 영향을 줄 수 있으며, Nitro가 Nuxt의 semver-minor 버전에서 업그레이드될 때 설정 인터페이스가 변경될 수 있습니다.
nuxt.config.ts
export default defineNuxtConfig({
  // https://nitro.build/config
  nitro: {}
})
Read more in Docs > Guide > Concepts > Server Engine.

중첩 라우터

server/api/hello/[...slug].ts
import { createRouter, defineEventHandler, useBase } from 'h3'

const router = createRouter()

router.get('/test', defineEventHandler(() => 'Hello World'))

export default useBase('/api/hello', router.handler)

스트림 전송

이 기능은 실험적이며 모든 환경에서 사용할 수 있습니다.
server/api/foo.get.ts
import fs from 'node:fs'
import { sendStream } from 'h3'

export default defineEventHandler((event) => {
  return sendStream(event, fs.createReadStream('/path/to/file'))
})

리디렉션 전송

server/api/foo.get.ts
export default defineEventHandler(async (event) => {
  await sendRedirect(event, '/path/redirect/to', 302)
})

레거시 핸들러 또는 미들웨어

server/api/legacy.ts
export default fromNodeMiddleware((req, res) => {
  res.end('레거시 핸들러')
})
레거시 지원은 unjs/h3를 사용하여 가능합니다. 하지만 레거시 핸들러는 최대한 피하는 것이 좋습니다.
server/middleware/legacy.ts
export default fromNodeMiddleware((req, res, next) => {
  console.log('레거시 미들웨어')
  next()
})
async이거나 Promise를 반환하는 레거시 미들웨어와 next() 콜백을 절대 함께 사용하지 마세요.

서버 스토리지

Nitro는 크로스 플랫폼 스토리지 레이어를 제공합니다. 추가 스토리지 마운트 포인트를 구성하려면 nitro.storage 또는 서버 플러그인을 사용할 수 있습니다.

Redis 스토리지 추가 예시:

nitro.storage 사용:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    storage: {
      redis: {
        driver: 'redis',
        /* redis 커넥터 옵션 */
        port: 6379, // Redis 포트
        host: "127.0.0.1", // Redis 호스트
        username: "", // Redis >= 6 필요
        password: "",
        db: 0, // 기본값 0
        tls: {} // tls/ssl
      }
    }
  }
})

API 핸들러에서:

server/api/storage/test.ts
export default defineEventHandler(async (event) => {
  // 모든 키 나열
  const keys = await useStorage('redis').getKeys()

  // 키 설정
  await useStorage('redis').setItem('foo', 'bar')

  // 키 삭제
  await useStorage('redis').removeItem('foo')

  return {}
})
Nitro 스토리지 레이어에 대해 더 알아보기.

또는, 서버 플러그인과 런타임 설정을 사용하여 스토리지 마운트 포인트를 만들 수 있습니다:

import redisDriver from 'unstorage/drivers/redis'

export default defineNitroPlugin(() => {
  const storage = useStorage()

  // 런타임 설정 또는 기타 소스에서 자격 증명을 동적으로 전달
  const driver = redisDriver({
      base: 'redis',
      host: useRuntimeConfig().redis.host,
      port: useRuntimeConfig().redis.port,
      /* 기타 redis 커넥터 옵션 */
    })

  // 드라이버 마운트
  storage.mount('redis', driver)
})