返回

next.js API 路由源码深度解读

前端

前言

在上一篇文章中,我们了解了 next.js 的 API 路由功能。在本篇文章中,我们将深入解析 next.js API 路由相关的源码,看看源码中是否有什么文档中没有的内容。

API 路由概述

API 路由是 next.js 提供的一种用于构建 RESTful API 的功能。API 路由允许我们使用 JavaScript 函数来处理 HTTP 请求,并返回 JSON 数据。API 路由的定义和使用方式与页面路由非常相似,只需要在 pages/api 目录下创建 JavaScript 文件即可。

源码分析

next.js 的 API 路由相关代码主要位于 packages/next 目录下的 server/api-routes 文件夹中。该文件夹包含了三个主要的 JavaScript 文件:

  • api-routes.js:这个文件是 API 路由的入口文件,它负责加载和处理 API 路由请求。
  • route.js:这个文件定义了 API 路由的类,它包含了 API 路由的基本功能,如解析 HTTP 请求,返回 JSON 数据等。
  • utils.js:这个文件包含了一些用于处理 API 路由的实用函数。

api-routes.js

api-routes.js 文件是 API 路由的入口文件,它负责加载和处理 API 路由请求。这个文件首先会从 pages/api 目录下加载所有的 JavaScript 文件,然后将这些文件中的导出函数注册为 API 路由。

const fs = require('fs')
const path = require('path')
const chalk = require('chalk')

module.exports = async function loadApiRoutes(app, pagePaths, config) {
  // 读取 pages/api 目录下的所有 JavaScript 文件
  const apiPagePaths = pagePaths.filter((pagePath) =>
    pagePath.includes('/pages/api/')
  )
  await Promise.all(
    apiPagePaths.map(async (apiPagePath) => {
      // 加载 JavaScript 文件
      let page = require(apiPagePath)

      // 如果导出的不是函数,则抛出错误
      if (typeof page !== 'function') {
        throw new Error(
          `Invalid API route: ${apiPagePath}. API routes must export a function.`
        )
      }

      // 注册 API 路由
      const route = new Route(apiPagePath, page, config)
      app.use(route.getRequestHandler())
    })
  )

  console.log(chalk.green('API routes loaded'))
}

route.js

route.js 文件定义了 API 路由的类,它包含了 API 路由的基本功能,如解析 HTTP 请求,返回 JSON 数据等。

class Route {
  constructor(apiPagePath, page, config) {
    this.apiPagePath = apiPagePath
    this.page = page
    this.config = config

    // 解析 API 路由的路径
    const path = apiPagePath.replace(/\.js$/, '').slice('/pages/api'.length)

    // 如果路径以斜杠开头,则移除斜杠
    if (path.startsWith('/')) {
      path = path.slice(1)
    }

    // 如果路径以下划线开头,则将其视为私有路由
    this.isPrivate = path.startsWith('_')

    // 如果路径以方括号开头,则将其视为动态路由
    this.isDynamic = path.includes('[')

    // 如果路径以星号开头,则将其视为捕获所有路由
    this.isCatchAll = path.startsWith('*')

    // 解析 API 路由的请求方法
    this.methods = []
    for (const method of ['GET', 'POST', 'PUT', 'DELETE']) {
      if (this.page[method]) {
        this.methods.push(method)
      }
    }

    // 创建 API 路由的请求处理函数
    this.requestHandler = this.createRequestHandler()
  }

  // 创建 API 路由的请求处理函数
  createRequestHandler() {
    return async (req, res, next) => {
      // 解析 HTTP 请求
      const { method, url, body } = req

      // 如果请求方法不匹配,则返回 405 错误
      if (!this.methods.includes(method)) {
        return res.status(405).json({ error: 'Method Not Allowed' })
      }

      // 如果是私有路由,则检查授权
      if (this.isPrivate && !req.headers.authorization) {
        return res.status(401).json({ error: 'Unauthorized' })
      }

      // 如果是动态路由,则解析动态参数
      const params = this.parseDynamicParams(url)

      // 如果是捕获所有路由,则将剩余的路径参数作为参数
      if (this.isCatchAll) {
        params._path = url.slice(1)
      }

      // 调用 API 路由的请求处理函数
      try {
        const result = await this.page[method](req, res, params)
        res.json(result)
      } catch (error) {
        console.error(error)
        res.status(500).json({ error: 'Internal Server Error' })
      }
    }
  }

  // 解析动态路由的参数
  parseDynamicParams(url) {
    // 将 URL 路径拆分为各个部分
    const parts = url.split('/')

    // 从 URL 路径中移除 API 路由的路径
    parts.shift()
    parts.shift()

    // 查找动态参数
    const params = {}
    for (let i = 0; i < parts.length; i++) {
      const part = parts[i]
      if (part.startsWith('[')) {
        const paramName = part.slice(1, -1)
        params[paramName] = parts[i + 1]
        i++
      }
    }

    return params
  }
}

utils.js

utils.js 文件包含了一些用于处理 API 路由的实用函数。

// 检查请求头是否包含授权信息
function hasAuthorizationHeader(req) {
  return req.headers.authorization && req.headers.authorization.startsWith('Bearer ')
}

// 从请求头中提取授权令牌
function getAuthorizationToken(req) {
  return req.headers.authorization.slice('Bearer '.length)
}

// 将对象转换为 JSON 字符串
function jsonify(obj) {
  return JSON.stringify(obj, null, 2)
}

总结

通过对 next.js API 路由源码的分析,我们了解了 API 路由的工作原理,掌握了 API 路由的高级用法,并发现了文档中未提及的隐藏功能。这些知识可以帮助我们更好地利用 next.js API 路由,构建出更强大、更灵活的 RESTful API。