API Routes (Маршруты API)

Примеры

Полезно знать: Если вы используете App Router, вместо API Routes можно использовать Server Components или Route Handlers.

API Routes предоставляют решение для создания публичного API с помощью Next.js.

Любой файл внутри папки pages/api сопоставляется с /api/* и будет обрабатываться как конечная точка API, а не как страница. Эти файлы собираются только на стороне сервера и не увеличивают размер клиентского бандла.

Например, следующий API Route возвращает JSON-ответ с кодом состояния 200:

import type { NextApiRequest, NextApiResponse } from 'next'

type ResponseData = {
  message: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  res.status(200).json({ message: 'Hello from Next.js!' })
}
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello from Next.js!' })
}

Полезно знать:

  • API Routes не указывают заголовки CORS, что означает, что по умолчанию они работают только в пределах одного источника. Это поведение можно изменить, обернув обработчик запроса с помощью CORS request helpers.
  • API Routes нельзя использовать с static exports. Однако Route Handlers в App Router поддерживают это.
  • API Routes будут зависеть от конфигурации pageExtensions в next.config.js.

Параметры

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  // ...
}

HTTP-методы

Для обработки различных HTTP-методов в API Route можно использовать req.method в обработчике запроса:

import type { NextApiRequest, NextApiResponse } from 'next'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'POST') {
    // Обработка POST-запроса
  } else {
    // Обработка других HTTP-методов
  }
}
export default function handler(req, res) {
  if (req.method === 'POST') {
    // Обработка POST-запроса
  } else {
    // Обработка других HTTP-методов
  }
}

Вспомогательные функции запроса

API Routes предоставляют встроенные вспомогательные функции для разбора входящего запроса (req):

  • req.cookies - Объект, содержащий cookies, отправленные с запросом. По умолчанию {}
  • req.query - Объект, содержащий строку запроса. По умолчанию {}
  • req.body - Объект, содержащий тело запроса, разобранное по content-type, или null, если тело не было отправлено

Пользовательская конфигурация

Каждый API Route может экспортировать объект config для изменения конфигурации по умолчанию:

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '1mb',
    },
  },
  // Указывает максимально допустимое время выполнения функции (в секундах)
  maxDuration: 5,
}

bodyParser включен по умолчанию. Если вы хотите обрабатывать тело как Stream или с помощью raw-body, можно отключить его:

export const config = {
  api: {
    bodyParser: false,
  },
}

Один из случаев отключения автоматического bodyParsing — проверка сырого тела запроса вебхука, например от GitHub.

bodyParser.sizeLimit — максимальный допустимый размер разбираемого тела в любом формате, поддерживаемом bytes:

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '500kb',
    },
  },
}

externalResolver — явный флаг, указывающий серверу, что маршрут обрабатывается внешним резолвером, например express или connect. Включение этой опции отключает предупреждения о неразрешенных запросах.

export const config = {
  api: {
    externalResolver: true,
  },
}

responseLimit включен по умолчанию и выдает предупреждение, если тело ответа API Route превышает 4MB.

Если вы не используете Next.js в serverless-среде и понимаете последствия для производительности, можно отключить этот лимит:

export const config = {
  api: {
    responseLimit: false,
  },
}

responseLimit также может принимать число байтов или строку в формате, поддерживаемом bytes, например 1000, '500kb' или '3mb'. Это значение будет максимальным размером ответа перед появлением предупреждения. По умолчанию 4MB.

export const config = {
  api: {
    responseLimit: '8mb',
  },
}

Вспомогательные функции ответа

Объект Server Response (часто сокращается до res) включает набор вспомогательных методов, похожих на Express.js, для улучшения опыта разработки.

Доступные вспомогательные функции:

  • res.status(code) - Устанавливает код состояния. code должен быть допустимым HTTP-статусом
  • res.json(body) - Отправляет JSON-ответ. body должен быть сериализуемым объектом
  • res.send(body) - Отправляет HTTP-ответ. body может быть строкой, объектом или Buffer
  • res.redirect([status,] path) - Перенаправляет на указанный путь или URL. status должен быть допустимым HTTP-статусом. Если не указан, по умолчанию используется "307" "Временное перенаправление".
  • res.revalidate(urlPath) - Ревалидация страницы по запросу с использованием getStaticProps. urlPath должен быть строкой.

Установка кода состояния ответа

При отправке ответа клиенту можно установить код состояния.

Следующий пример устанавливает код состояния 200 (OK) и возвращает JSON-ответ с сообщением:

import type { NextApiRequest, NextApiResponse } from 'next'

type ResponseData = {
  message: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  res.status(200).json({ message: 'Hello from Next.js!' })
}
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello from Next.js!' })
}

Отправка JSON-ответа

При отправке ответа клиенту можно отправить JSON-ответ, который должен быть сериализуемым объектом. В реальном приложении может потребоваться уведомить клиента о статусе запроса.

Следующий пример отправляет JSON-ответ с кодом 200 (OK) и результатом асинхронной операции. Используется блок try-catch для обработки возможных ошибок:

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const result = await someAsyncOperation()
    res.status(200).json({ result })
  } catch (err) {
    res.status(500).json({ error: 'failed to load data' })
  }
}
export default async function handler(req, res) {
  try {
    const result = await someAsyncOperation()
    res.status(200).json({ result })
  } catch (err) {
    res.status(500).json({ error: 'failed to load data' })
  }
}

Отправка HTTP-ответа

Отправка HTTP-ответа работает аналогично JSON-ответу. Разница в том, что тело ответа может быть строкой, объектом или Buffer.

Следующий пример отправляет HTTP-ответ с кодом 200 (OK) и результатом асинхронной операции:

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const result = await someAsyncOperation()
    res.status(200).send({ result })
  } catch (err) {
    res.status(500).send({ error: 'failed to fetch data' })
  }
}
export default async function handler(req, res) {
  try {
    const result = await someAsyncOperation()
    res.status(200).send({ result })
  } catch (err) {
    res.status(500).send({ error: 'failed to fetch data' })
  }
}

Перенаправление на указанный путь или URL

Например, после отправки формы может потребоваться перенаправить клиента на указанный путь или URL.

Следующий пример перенаправляет клиента на / при успешной отправке формы:

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { name, message } = req.body

  try {
    await handleFormInputAsync({ name, message })
    res.redirect(307, '/')
  } catch (err) {
    res.status(500).send({ error: 'Failed to fetch data' })
  }
}
export default async function handler(req, res) {
  const { name, message } = req.body

  try {
    await handleFormInputAsync({ name, message })
    res.redirect(307, '/')
  } catch (err) {
    res.status(500).send({ error: 'failed to fetch data' })
  }
}

Добавление TypeScript-типов

Можно сделать API Routes более типобезопасными, импортируя типы NextApiRequest и NextApiResponse из next, а также типизировав данные ответа:

import type { NextApiRequest, NextApiResponse } from 'next'

type ResponseData = {
  message: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  res.status(200).json({ message: 'Hello from Next.js!' })
}

Полезно знать: Тело NextApiRequest имеет тип any, так как клиент может отправить любые данные. Перед использованием следует проверить тип/структуру тела во время выполнения.

Динамические API Routes

API Routes поддерживают динамические маршруты и следуют тем же правилам именования файлов, что и pages/.

import type { NextApiRequest, NextApiResponse } from 'next'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { pid } = req.query
  res.end(`Post: ${pid}`)
}
export default function handler(req, res) {
  const { pid } = req.query
  res.end(`Post: ${pid}`)
}

Теперь запрос к /api/post/abc вернет текст: Post: abc.

Catch all API routes

API Routes можно расширить для обработки всех путей, добавив три точки (...) внутри скобок. Например:

  • pages/api/post/[...slug].js соответствует /api/post/a, а также /api/post/a/b, /api/post/a/b/c и т.д.

Полезно знать: Можно использовать имена, отличные от slug, например: [...param]

Сопоставленные параметры будут переданы как query-параметр (slug в примере) и всегда будут массивом. Путь /api/post/a будет иметь следующий объект query:

{ "slug": ["a"] }

А для /api/post/a/b и других соответствующих путей новые параметры будут добавлены в массив:

{ "slug": ["a", "b"] }

Пример:

import type { NextApiRequest, NextApiResponse } from 'next'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { slug } = req.query
  res.end(`Post: ${slug.join(', ')}`)
}
export default function handler(req, res) {
  const { slug } = req.query
  res.end(`Post: ${slug.join(', ')}`)
}

Теперь запрос к /api/post/a/b/c вернет текст: Post: a, b, c.

Опциональные catch all API routes

Catch all маршруты можно сделать опциональными, заключив параметр в двойные скобки ([[...slug]]).

Например, pages/api/post/[[...slug]].js будет соответствовать /api/post, /api/post/a, /api/post/a/b и т.д.

Основное отличие между catch all и опциональными catch all маршрутами в том, что опциональные соответствуют и маршруту без параметра (/api/post в примере).

Объекты query выглядят так:

{ } // GET `/api/post` (пустой объект)
{ "slug": ["a"] } // `GET /api/post/a` (массив с одним элементом)
{ "slug": ["a", "b"] } // `GET /api/post/a/b` (массив с несколькими элементами)

Особенности

  • Предопределенные API Routes имеют приоритет над динамическими, а динамические — над catch all. Примеры:
    • pages/api/post/create.js - Соответствует /api/post/create
    • pages/api/post/[pid].js - Соответствует /api/post/1, /api/post/abc, но не /api/post/create
    • pages/api/post/[...slug].js - Соответствует /api/post/1/2, /api/post/a/b/c, но не /api/post/create, /api/post/abc

Потоковые ответы

Хотя Pages Router поддерживает потоковые ответы с API Routes, рекомендуется постепенно переходить на App Router и использовать Route Handlers, если вы используете Next.js 14+.

Пример потокового ответа из API Route с writeHead:

pages/api/hello.js
import { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': "no-store",
  })
  let i = 0
  while (i < 10) {
    res.write(`data: ${i}\n\n`)
    i++
    await new Promise((resolve) => setTimeout(resolve, 1000))
  }
  res.end()
}