server

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

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

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

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

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

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

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

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

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

Server Routes

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

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

예시:

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

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

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

Server Middleware

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

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

미들웨어 핸들러는 아무것도 반환해서는 안 되며(요청을 종료하거나 응답해서도 안 됩니다), 요청 컨텍스트를 검사하거나 확장하거나 에러를 던지는 역할만 해야 합니다.

예시:

server/middleware/log.ts
export default defineEventHandler((event) => {
  console.log('New request: ' + getRequestURL(event))
})
server/middleware/auth.ts
export default defineEventHandler((event) => {
  event.context.auth = { user: 123 }
})

Server Plugins

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

예시:

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

Server Utilities

서버 라우트는 편리한 헬퍼 세트를 제공하는 h3js/h3에 의해 구동됩니다.

Read more in 사용 가능한 H3 Request Helpers.

~/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 }
    }
  })

Server Types

server/ 디렉터리는 app/ 디렉터리와 다른 컨텍스트에서 실행되므로, 자동 임포트 및 기타 타입이 다릅니다.

기본적으로 Nuxt 4는 server/ 폴더를 포함하는 프로젝트 레퍼런스를 가진 tsconfig.json을 생성하여 정확한 타입을 보장합니다.

Recipes

Route Parameters

서버 라우트는 /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!를 얻을 수 있습니다.

Matching HTTP Method

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

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 요청 처리
})

Catch-all Route

캐치올(catch-all) 라우트는 폴백(fallback) 라우트 처리를 위해 유용합니다.

예를 들어, ~/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`
})

Body Handling

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

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

app/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를 사용하면, readBody405 Method Not Allowed HTTP 에러를 던집니다.

Query Parameters

예시 쿼리 /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를 사용할 수 있습니다.

Error Handling

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

잡히지 않은 모든 에러는 500 Internal Server Error HTTP 에러를 반환합니다.

다른 에러 코드를 반환하려면 createError로 예외를 던지면 됩니다:

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

  if (!Number.isInteger(id)) {
    throw createError({
      statusCode: 400,
      statusMessage: 'ID should be an integer',
    })
  }
  return 'All good'
})

Status Codes

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

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

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

Runtime Config

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를 인자로 넘기는 것은 선택 사항이지만, 서버 라우트에서 런타임에 환경 변수에 의해 덮어써진 런타임 설정을 가져오기 위해서는 넘겨주는 것이 권장됩니다.

Request Cookies

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

  return { cookies }
})

Forwarding Context & Headers

기본적으로 서버 라우트에서 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

Awaiting Promises After Response

서버 요청을 처리할 때, 응답을 클라이언트로 보내는 것을 막지 않고 비동기 작업(예: 캐싱, 로깅 등)을 수행해야 할 수 있습니다. 이때 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'
})

Advanced Usage

Nitro Config

nuxt.config에서 nitro 키를 사용해 Nitro 설정을 직접 지정할 수 있습니다.

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

Nested Router

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)

Sending Streams

이 기능은 실험적 기능이며 모든 환경에서 사용할 수 있습니다.
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'))
})

Sending Redirect

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

Legacy Handler or Middleware

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

Server Storage

Nitro는 크로스 플랫폼 스토리지 레이어를 제공합니다. 추가 스토리지 마운트 포인트를 설정하려면 nitro.storage 또는 server plugins를 사용할 수 있습니다.

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 Storage Layer에 대해 더 알아보기.

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

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)
})