Skip to content

api

在 nextjs 中可以直接编写接口请求,提供类似后端的 api 功能,一般定义路由如下,在 api 文件夹中定义。

在 api 文件夹中定义 route.ts 文件,在该文件中定义接口路由,如下所示:

typescript
export async function GET(request: Request) {}

支持以下 HTTP 方法支持:GET、POST、PUT、PATCH、DELETE、HEAD 和 OPTIONS。如果调用不受支持的方法,Next.js 将返回响应 405 Method Not Allowed。

缓存

GET 当方法与对象一起使用时,默认情况下会缓存路由处理程序 Response。

tsx
export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/...', {
    headers: {
      'Content-Type': 'application/json',
      'API-Key': process.env.DATA_API_KEY
    }
  })
  const data = await res.json()

  return Response.json({ data })
}

清除缓存

  • Request 将对象和方法一起使用 GET。
  • 使用任何其他 HTTP 方法
  • cookies 或 headers 包含特定值
  • 配置时手动指定动态模式
tsx
export async function GET(request: Request) {
  const res = await fetch('https://data.mongodb-api.com/...', {
    headers: {
      'Content-Type': 'application/json',
      'API-Key': process.env.DATA_API_KEY
    }
  })
  const data = await res.json()

  return Response.json({ data })
}

使用 POST 来清除

tsx
export async function POST() {
  const res = await fetch('https://data.mongodb-api.com/...', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'API-Key': process.env.DATA_API_KEY
    },
    body: JSON.stringify({ time: new Date().toISOString() })
  })

  const data = await res.json()

  return Response.json(data)
}

route.js 和 page.js 不能位于同一个文件夹中,否则会出现错误。

路线结果
app/page.jsapp/route.js冲突
app/page.jsapp/api/route.js有效的
app/[user]/page.jsapp/api/route.js有效的

例子

使用配置缓存验证

tsx
export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/...', {
    next: { revalidate: 60 } // Revalidate every 60 seconds
  })
  const data = await res.json()

  return Response.json(data)
}

使用 cookies

tsx
import { cookies } from 'next/headers'

export async function GET(request: Request) {
  const cookieStore = cookies()
  const token = cookieStore.get('token')

  return new Response('Hello, Next.js!', {
    status: 200,
    headers: { 'Set-Cookie': `token=${token.value}` }
  })
}

也可以通过 web api 上读取 cookie

tsx
import { type NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
  const token = request.cookies.get('token')
}

使用 headers 来读取请求头信息

tsx
import { headers } from 'next/headers'

export async function GET(request: Request) {
  const headersList = headers()
  const referer = headersList.get('referer')

  return new Response('Hello, Next.js!', {
    status: 200,
    headers: { referer: referer }
  })
}

也可以使用 next 提供的 api 来获取请求头信息

tsx
import { type NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
  const requestHeaders = new Headers(request.headers)
}

重定向

tsx
import { redirect } from 'next/navigation'

export async function GET(request: Request) {
  redirect('https://nextjs.org/')
}

获取动态路线段参数

tsx
export async function GET(request: Request, { params }: { params: { slug: string } }) {
  const slug = params.slug // 'a', 'b', or 'c'
}
路线示例网址params
app/items/[slug]/route.js/items/a{ slug: 'a' }
app/items/[slug]/route.js/items/b{ slug: 'b' }
app/items/[slug]/route.js/items/c{ slug: 'c' }

获取 URL 查询参数

tsx
import { type NextRequest } from 'next/server'

export function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams
  const query = searchParams.get('query')
  // query is "hello" for /api/search?query=hello
}

流媒体获取

tsx
import { Configuration, OpenAIApi } from 'openai-edge'
import { OpenAIStream, StreamingTextResponse } from 'ai'

export const runtime = 'edge'

const apiConfig = new Configuration({
  apiKey: process.env.OPENAI_API_KEY!
})

const openai = new OpenAIApi(apiConfig)

export async function POST(req: Request) {
  // Extract the `messages` from the body of the request
  const { messages } = await req.json()

  // Request the OpenAI API for the response based on the prompt
  const response = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    stream: true,
    messages: messages,
    max_tokens: 500,
    temperature: 0.7,
    top_p: 1,
    frequency_penalty: 1,
    presence_penalty: 1
  })

  // Convert the response into a friendly text-stream
  const stream = OpenAIStream(response)

  // Respond with the stream
  return new StreamingTextResponse(stream)
}

也可以用底层 web api 获取

tsx
// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
function iteratorToStream(iterator: any) {
  return new ReadableStream({
    async pull(controller) {
      const { value, done } = await iterator.next()

      if (done) {
        controller.close()
      } else {
        controller.enqueue(value)
      }
    }
  })
}

function sleep(time: number) {
  return new Promise((resolve) => {
    setTimeout(resolve, time)
  })
}

const encoder = new TextEncoder()

async function* makeIterator() {
  yield encoder.encode('<p>One</p>')
  await sleep(200)
  yield encoder.encode('<p>Two</p>')
  await sleep(200)
  yield encoder.encode('<p>Three</p>')
}

export async function GET() {
  const iterator = makeIterator()
  const stream = iteratorToStream(iterator)

  return new Response(stream)
}

获取请求正文

tsx
export async function POST(request: Request) {
  const res = await request.json()
  return Response.json({ res })
}

获取请求表单

tsx
export async function POST(request: Request) {
  const formData = await request.formData()
  const name = formData.get('name')
  const email = formData.get('email')
  return Response.json({ name, email })
}

设置跨域

tsx
export async function GET(request: Request) {
  return new Response('Hello, Next.js!', {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization'
    }
  })
}

请求也可以返回非页面 ui 的内容

tsx
export async function GET() {
  return new Response(`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
 
<channel>
  <title>Next.js Documentation</title>
  <link>https://nextjs.org/docs</link>
  <description>The React Framework for the Web</description>
</channel>
 
</rss>`)
}

中间件

中间件和其他的中间件原理类似,允许您在请求完成之前运行代码。在项目根目录中的 middleware.ts 来定义中间件。

tsx
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}

// See "Matching Paths" below to learn more
export const config = {
  matcher: '/about/:path*'
}

匹配路径顺序

  1. headers 从 next.config.js
  2. redirects 从 next.config.js
  3. 中间件(rewrites、redirects 等)
  4. beforeFiles( rewrites) 来自 next.config.js
  5. 文件系统路由(public/、_next/static/、pages/、app/等)
  6. afterFiles( rewrites) 来自 next.config.js
  7. 动态路由 ( /blog/[slug])
  8. fallback( rewrites) 来自 next.config.js

匹配器

matcher 可以让中间件在特定的路径上运行。

tsx
export const config = {
  matcher: '/about/:path*'
}

也可以匹配单个路径或者多个路径

tsx
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*']
}

还可以支持反向选择,排除部分路径

tsx
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)'
  ]
}

使用条件判断使用

tsx
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/about')) {
    return NextResponse.rewrite(new URL('/about-2', request.url))
  }

  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.rewrite(new URL('/dashboard/user', request.url))
  }
}

NextResponse api 的使用

  • 可以 redirect 到不同的 url
  • 可以 rewrite 到不同的 url
  • 设置响应的 cookie
  • 设置响应的 header
tsx
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Assume a "Cookie:nextjs=fast" header to be present on the incoming request
  // Getting cookies from the request using the `RequestCookies` API
  let cookie = request.cookies.get('nextjs')
  console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
  const allCookies = request.cookies.getAll()
  console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]

  request.cookies.has('nextjs') // => true
  request.cookies.delete('nextjs')
  request.cookies.has('nextjs') // => false

  // Setting cookies on the response using the `ResponseCookies` API
  const response = NextResponse.next()
  response.cookies.set('vercel', 'fast')
  response.cookies.set({
    name: 'vercel',
    value: 'fast',
    path: '/'
  })
  cookie = response.cookies.get('vercel')
  console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
  // The outgoing response will have a `Set-Cookie:vercel=fast;path=/test` header.

  return response
}

设置 header

tsx
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Clone the request headers and set a new header `x-hello-from-middleware1`
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-hello-from-middleware1', 'hello')

  // You can also set request headers in NextResponse.rewrite
  const response = NextResponse.next({
    request: {
      // New request headers
      headers: requestHeaders
    }
  })

  // Set a new response header `x-hello-from-middleware2`
  response.headers.set('x-hello-from-middleware2', 'hello')
  return response
}

响应结果

tsx
import { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'

// Limit the middleware to paths starting with `/api/`
export const config = {
  matcher: '/api/:function*'
}

export function middleware(request: NextRequest) {
  // Call our authentication function to check the request
  if (!isAuthenticated(request)) {
    // Respond with JSON indicating an error message
    return Response.json({ success: false, message: 'authentication failed' }, { status: 401 })
  }
}

中间件的高级处理

在 v13.1Next.js 中,为中间件引入了两个附加标志,skipMiddlewareUrlNormalize 并 skipTrailingSlashRedirect 处理高级用例。

skipTrailingSlashRedirect 允许禁用 Next.js 默认重定向以添加或删除尾部斜杠,允许中间件内部进行自定义处理,这可以允许维护某些路径的尾部斜杠,但不允许维护其他路径的尾部斜杠,从而允许更轻松的增量迁移。

tsx
module.exports = {
  skipTrailingSlashRedirect: true
}
tsx
const legacyPrefixes = ['/docs', '/blog']

export default async function middleware(req) {
  const { pathname } = req.nextUrl

  if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
    return NextResponse.next()
  }

  // apply trailing slash handling
  if (
    !pathname.endsWith('/') &&
    !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
  ) {
    req.nextUrl.pathname += '/'
    return NextResponse.redirect(req.nextUrl)
  }
}

skipMiddlewareUrlNormalize 允许禁用 URL 规范化 Next.js 的作用,以使直接访问和客户端转换的处理相同。在某些高级情况下,您需要使用解锁的原始 URL 进行完全控制。

tsx
module.exports = {
  skipMiddlewareUrlNormalize: true
}
tsx
export default async function middleware(req) {
  const { pathname } = req.nextUrl

  // GET /_next/data/build-id/hello.json

  console.log(pathname)
  // with the flag this now /_next/data/build-id/hello.json
  // without the flag this would be normalized to /hello
}

小结

通过 next 中编写 api 可以实现后端的接口请求,然后通过中间件,可以在请求的过程中进行一些额外的处理,来实现一些功能。

如有转载或 CV 的请标注本站原文地址