API 路由

了解如何使用 Expo Router 创建服务器端点。


Expo Router 使你能够在应用目录中为所有平台编写安全的服务器代码。

¥Expo Router enables you to write secure server code for all platforms, right in your app directory.

app/hello+api.ts
export function GET(request: Request) {
  return Response.json({ hello: 'world' });
}

服务器功能需要自定义服务器,可以部署到 EAS 或大多数 其他托管服务提供商

¥Server features require a custom server, which can be deployed to EAS or most other hosting providers.

Watch: Expo Router API Routes Handle Requests & Stream Data
Watch: Expo Router API Routes Handle Requests & Stream Data

什么是 API 路由

¥What are API Routes

API 路由是在路由匹配时在服务器上执行的函数。它们可用于安全地处理敏感数据(例如 API 密钥),或实现自定义服务器逻辑(例如交换访问令牌的授权代码)。API 路由应在符合 WinterCG 的环境中执行。

¥API Routes are functions that are executed on a server when a route is matched. They can be used to handle sensitive data, such as API keys securely, or implement custom server logic, such as exchanging auth codes for access tokens. API Routes should be executed in a WinterCG-compliant environment.

在 Expo 中,API 路由通过在 app 目录中创建带有 +api.ts 扩展名的文件来定义。例如,当路由 /hello 匹配时,将执行以下 API 路由。

¥In Expo, API Routes are defined by creating files in the app directory with the +api.ts extension. For example, the following API route is executed when the route /hello is matched.

app
index.tsx
hello+api.tsAPI Route

创建 API 路由

¥Create an API route

1

确保你的项目正在使用服务器输出,这将配置导出和生产版本以生成服务器包以及客户端包。

¥Ensure your project is using server output, this will configure the export and production builds to generate a server bundle as well as the client bundle.

app.json
{
"web": {
  "output": "server"
}
}

2

在应用目录中创建 API 路由。例如,添加以下路由处理程序。当路由 /hello 匹配时执行。

¥An API route is created in the app directory. For example, add the following route handler. It is executed when the route /hello is matched.

app/hello+api.ts
export function GET(request: Request) {
return Response.json({ hello: 'world' });
}

你可以从服务器路由导出以下任意函数 GETPOSTPUTPATCHDELETEHEADOPTIONS。当匹配到相应的 HTTP 方法时,该函数就会执行。不支持的方法将自动返回 405: Method not allowed

¥You can export any of the following functions GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS from a server route. The function executes when the corresponding HTTP method is matched. Unsupported methods will automatically return 405: Method not allowed.

3

使用 Expo CLI 启动开发服务器:

¥Start the development server with Expo CLI:

Terminal
npx expo

4

你可以向路由发出网络请求来访问数据。运行以下命令来测试路由:

¥You can make a network request to the route to access the data. Run the following command to test the route:

Terminal
curl http://localhost:8081/hello

你还可以从客户端代码发出请求:

¥You can also make a request from the client code:

app/index.tsx
import { Button } from 'react-native';

async function fetchHello() {
const response = await fetch('/hello');
const data = await response.json();
alert('Hello ' + data.hello);
}

export default function App() {
return <Button onPress={() => fetchHello()} title="Fetch hello" />;
}

相对获取请求在开发过程中自动相对于开发服务器原点进行获取,并且可以在生产中使用 app.json 中的 origin 字段进行配置:

¥Relative fetch requests automatically fetch relative to the dev server origin in development, and can be configured in production using the origin field in the app.json:

app.json
{
"plugins": [
  [
    "expo-router",
    {
      "origin": "https://evanbacon.dev/"
    }
  ]
]
}

可以通过设置 EXPO_UNSTABLE_DEPLOY_SERVER=1 环境变量在 EAS 构建期间自动配置此 URL。这将触发版本化的服务器部署,自动将原点设置为预览部署 URL。

¥This URL can be automatically configured during EAS Builds by setting the EXPO_UNSTABLE_DEPLOY_SERVER=1 environment variable. This will trigger a versioned server deployment which sets the origin to a preview deploy URL automatically.

5

将网站和服务器部署到 托管提供商,以在原生和 Web 上访问生产中的路由。

¥Deploy the website and server to a hosting provider to access the routes in production on both native and web.

API 路由文件名不能具有特定于平台的扩展名。例如,hello+api.web.ts 将不起作用。

要求

¥Requests

请求使用全局、标准 Request 对象。

¥Requests use the global, standard Request object.

app/blog/[post]+api.ts
export async function GET(request: Request, { post }: Record<string, string>) {
  // const postId = new URL(request.url).searchParams.get('post')
  // fetch data for 'post'
  return Response.json({ ... });
}

请求正文

¥Request body

使用 request.json() 函数访问请求正文。它自动解析正文并返回结果。

¥Use the request.json() function to access the request body. It automatically parses the body and returns the result.

app/validate+api.ts
export async function POST(request: Request) {
  const body = await request.json();

  return Response.json({ ... });
}

重置导航

¥Request query parameters

可以通过解析请求 URL 来访问查询参数:

¥Query parameters can be accessed by parsing the request URL:

app/endpoint+api.ts
export async function GET(request: Request) {
  const url = new URL(request.url);
  const post = url.searchParams.get('post');

  // fetch data for 'post'
  return Response.json({ ... });
}

响应

¥Response

响应使用全局标准 Response 对象。

¥Responses use the global, standard Response object.

app/demo+api.ts
export function GET() {
  return Response.json({ hello: 'universe' });
}

错误

¥Errors

你可以使用 Response 对象来响应服务器错误。

¥You can respond to server errors by using the Response object.

app/blog/[post].ts
import { Request, Response } from 'expo-router/server';

export async function GET(request: Request, { post }: Record<string, string>) {
  if (!post) {
    return new Response('No post found', {
      status: 404,
      headers: {
        'Content-Type': 'text/plain',
      },
    });
  }
  // fetch data for `post`
  return Response.json({ ... });
}

使用未定义的方法发出请求将自动返回 405: Method not allowed。如果请求过程中抛出错误,会自动返回 500: Internal server error

¥Making requests with an undefined method will automatically return 405: Method not allowed. If an error is thrown during the request, it will automatically return 500: Internal server error.

打包

¥Bundling

API 路由与 Expo CLI 和 Metro 打包器 打包在一起。他们可以访问所有语言功能作为你的客户端代码:

¥API Routes are bundled with Expo CLI and Metro bundler. They have access to all of the language features as your client code:

  • TypeScript — 类型和 tsconfig.json 路径

    ¥TypeScript — types and tsconfig.json paths.

  • 环境变量 — 服务器路由可以访问所有环境变量,而不仅仅是前缀为 EXPO_PUBLIC_ 的变量。

    ¥Environment variables — server routes have access to all environment variables, not just the ones prefixed with EXPO_PUBLIC_.

  • Node.js 标准库 — 确保你在本地为你的服务器环境使用正确版本的 Node.js。

    ¥Node.js standard library — ensure that you are using the correct version of Node.js locally for your server environment.

  • babel.config.js 和 Metro.config.js 支持 — 设置可跨客户端和服务器代码工作。

    ¥babel.config.js and metro.config.js support — settings work across both client and server code.

安全

¥Security

路由处理程序在与客户端代码隔离的沙盒环境中执行。这意味着你可以安全地将敏感数据存储在路由处理程序中,而无需将其暴露给客户端。

¥Route handlers are executed in a sandboxed environment that is isolated from the client code. It means you can safely store sensitive data in the route handlers without exposing it to the client.

  • 导入带有密钥的代码的客户端代码包含在客户端打包包中。它适用于应用目录中的所有文件,即使它们不是路由处理程序文件(例如以 +api.ts 为后缀的文件)。

    ¥Client code that imports code with a secret is included in the client bundle. It applies to all files in the app directory even though they are not a route handler file (such as suffixed with +api.ts).

  • 如果密钥位于 <...>+api.ts 文件中,则它不包含在客户端包中。它适用于在路由处理程序中导入的所有文件。

    ¥If the secret is in a <...>+api.ts file, it is not included in the client bundle. It applies to all files that are imported in the route handler.

  • 秘密剥离发生在 expo/metro-config 中,并要求在 Metro.config.js 中使用。

    ¥The secret stripping takes place in expo/metro-config and requires it to be used in the metro.config.js.

部署

¥Deployment

当你准备好部署到生产环境时,运行 npx expo export --platform web 以在 dist 目录中创建服务器包。可以使用 npx expo serve(在 Expo SDK 52 及更高版本中可用)在本地测试此服务器,在 Web 浏览器中访问 URL 或创建原生构建,并将 origin 设置为本地服务器 URL。你可以使用 EAS 托管 或其他第三方服务部署服务器以进行生产。

¥When you're ready to deploy to production, run npx expo export --platform web to create the server bundle in the dist directory. This server can be tested locally with npx expo serve (available in Expo SDK 52 and higher), visit the URL in a web browser or create a native build with the origin set to the local server URL. You can deploy the server for production using EAS Hosting or another third-party service.

使用 EAS 立即部署

EAS Hosting 是部署 Expo API 路由和服务器的最佳方式。

原生部署

¥Native deployment

这是从 SDK 52 及以上版本开始的实验性功能。该过程将更加自动化,并在未来的版本中获得更好的支持。

Expo Router 中的服务器功能(API 路由和 React 服务器组件)以指向远程服务器的 window.locationfetch 的原生实现为中心。在开发中,我们会自动指向使用 npx expo start 运行的开发服务器,但为了使生产原生构建正常工作,你需要将服务器部署到安全主机并设置 Expo Router Config 插件的 origin 属性。

¥Server features (API routes, and React Server Components) in Expo Router are centered around native implementations of window.location and fetch which point to the remote server. In development, we automatically point to the dev server running with npx expo start, but for production native builds to work you'll need to deploy the server to a secure host and set the origin property of the Expo Router Config Plugin.

配置后,相对获取请求 fetch('/my-endpoint') 等功能将自动指向服务器原点。

¥When configured, features like relative fetch requests fetch('/my-endpoint') will automatically point to the server origin.

此部署过程可以通过实验自动化,以确保使用 EXPO_UNSTABLE_DEPLOY_SERVER=1 环境变量在原生构建期间正确进行版本控制。

¥This deployment process can experimentally be automated to ensure correct versioning during native builds with the EXPO_UNSTABLE_DEPLOY_SERVER=1 environment variable.

以下是如何配置原生应用以在构建时自动部署和链接版本化生产服务器:

¥Here's how to configure your native app to automatically deploy and link a versioned production server on build:

1

确保 origin 字段未在 app.json 或 expo.extra.router.origin 字段中设置。此外,请确保你没有使用 app.config.js,因为这尚不支持自动链接部署。

¥Ensure the origin field is NOT set in the app.json or in the expo.extra.router.origin field. Also, ensure you aren't using app.config.js as this is not supported with automatically linked deployments yet.

2

首先在本地部署一次,为项目设置 EAS 托管

¥Setup EAS Hosting for the project by deploying once locally first.

Terminal
npx expo export -p web
eas deploy

3

在你的 .env 文件中设置 EXPO_UNSTABLE_DEPLOY_SERVER 环境变量。这将用于在 EAS Build 期间启用实验性服务器部署功能。

¥Set the EXPO_UNSTABLE_DEPLOY_SERVER environment variable in your .env file. This will be used to enable the experimental server deployment functionality during EAS Build.

.env
EXPO_UNSTABLE_DEPLOY_SERVER=1

4

你现在可以开始使用自动服务器部署了!运行 build 命令以启动该过程。

¥You're now ready to use automatic server deployment! Run the build command to start the process.

Terminal
eas build

你还可以使用以下方式在本地运行:

¥You can also run this locally with:

Terminal
# Android
npx expo run:android --variant release

# iOS
npx expo run:ios --configuration Release

有关原生应用的自动服务器部署的说明:

¥Notes about automatic server deployment for native apps:

  • 如果某些设置不正确,EAS Build 的 Bundle JavaScript 阶段可能会发生服务器故障。

    ¥Server failures may occur during the Bundle JavaScript phase of EAS Build if something was not setup correctly.

  • 如果你愿意,你可以在构建应用之前手动部署服务器并设置 origin URL。

    ¥You can manually deploy the server and set the origin URL before building the app if you'd like.

  • 可以使用环境变量 EXPO_NO_DEPLOY=1 强制跳过自动部署。

    ¥Automatic deployment can be force skipped with the environment variable EXPO_NO_DEPLOY=1.

  • 自动部署尚不支持 动态应用配置(app.config.js 和 app.config.ts)文件。

    ¥Automatic deployment does not support dynamic app config (app.config.js and app.config.ts) files yet.

  • 部署中的日志将写入 .expo/logs/deploy.log

    ¥Logs from the deployment will be written to .expo/logs/deploy.log.

  • 部署不会在 EXPO_OFFLINE 模式下运行。

    ¥Deployment will not run in EXPO_OFFLINE mode.

在本地测试原生生产应用

¥Testing the native production app locally

根据本地开发服务器测试生产版本通常很有用,以确保一切都按预期工作。这可以大大加快调试过程。

¥It can often be useful to test the production build against a local dev server to ensure everything is working as expected. This can speed up the debugging process substantially.

1

导出生产服务器:

¥Export the production server:

Terminal
npx expo export

2

在本地托管生产服务器:

¥Host the production server locally:

Terminal
npx expo serve

3

在 app.json 的 origin 字段中设置原点。确保 expo.extra.router.origin 中没有生成的值。这应该是 http://localhost:8081(假设 npx expo serve 在默认端口上运行)。

¥Set the origin in the app.json's origin field. Ensure no generated value is in expo.extra.router.origin. This should be http://localhost:8081 (assuming npx expo serve is running on the default port).

app.json
{
"expo": {
  "plugins": [
    [
      "expo-router",
      {
        "origin": "http://localhost:8081"
      }
    ]
  ]
}
}

部署到生产环境时,请记住删除此 origin 值。

¥Remember to remove this origin value when deploying to production.

4

在模拟器上以发布模式构建应用:

¥Build the app in release mode on to a simulator:

Terminal
EXPO_NO_DEPLOY=1 npx expo run:ios --configuration Release

你现在应该可以看到进入本地服务器的请求。使用 代理人 之类的工具检查模拟器的网络流量并获得更好的洞察力。

¥You should now see requests coming in to the local server. Use a tool like Proxyman to inspect network traffic for the simulator and gain better insight.

你可以使用 --unstable-rebundle 标志实验性地更改 URL 并快速为 iOS 重建。这将用新的替换 app.json 和客户端资源,跳过原生重建。

¥You can experimentally change the URL and quickly rebuild for iOS using the --unstable-rebundle flag. This will swap out the app.json and client assets for new ones, skipping the native rebuild.

例如,你可以运行 eas deploy 以获取新的部署 URL,将其添加到 app.json,然后运行 ​​npx expo run:ios --unstable-rebundle --configuration Release 以使用新 URL 快速重建应用。

¥For example, you can run eas deploy to get a new deployment URL, add it to the app.json, then run npx expo run:ios --unstable-rebundle --configuration Release to quickly rebuild the app with the new URL.

你需要在发送到商店之前进行干净的构建,以确保不存在任何瞬态问题。

¥You will want to make a clean build before sending to the store to ensure no transient issues are present.

托管在第三方服务上

¥Hosting on third-party services

这是实验性的,可能会发生重大变化。我们没有针对此配置进行持续测试。

每个云托管提供商都需要一个自定义适配器来支持 Expo 服务器运行时。以下第三方提供商获得了 Expo 团队的非官方或实验性支持。

¥Every cloud hosting provider needs a custom adapter to support the Expo server runtime. The following third-party providers have unofficial or experimental support from the Expo team.

在部署到这些提供商之前,最好熟悉 npx expo export 命令的基础知识:

¥Before deploying to these providers, it may be good to be familiar with the basics of npx expo export command:

  • dist 是 Expo CLI 的默认导出目录。

    ¥dist is the default export directory for Expo CLI.

  • 公共目录中的文件在导出时被复制到 dist。

    ¥Files in public directory are copied to dist on export.

  • @expo/server 包包含在 expo 中,并将请求委托给服务器路由。

    ¥The @expo/server package is included with expo and delegates requests to the server routes.

  • @expo/server 不会从 .env 文件中增加环境变量。它们预计由托管提供商或用户加载。

    ¥@expo/server does not inflate environment variables from .env files. They are expected to load either by the hosting provider or the user.

  • Metro 不包含在服务器中。

    ¥Metro is not included in the server.

表达

¥Express

1

安装所需的依赖:

¥Install the required dependencies:

Terminal
npm i -D express compression morgan

2

导出网站进行制作:

¥Export the website for production:

Terminal
npx expo export -p web

3

编写一个静态服务文件并将请求委托给服务器路由的服务器入口文件:

¥Write a server entry file that serves the static files and delegates requests to the server routes:

server.ts
#!/usr/bin/env node

const path = require('path');
const { createRequestHandler } = require('@expo/server/adapter/express');

const express = require('express');
const compression = require('compression');
const morgan = require('morgan');

const CLIENT_BUILD_DIR = path.join(process.cwd(), 'dist/client');
const SERVER_BUILD_DIR = path.join(process.cwd(), 'dist/server');

const app = express();

app.use(compression());

// http://express.nodejs.cn/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
app.disable('x-powered-by');

process.env.NODE_ENV = 'production';

app.use(
express.static(CLIENT_BUILD_DIR, {
  maxAge: '1h',
  extensions: ['html'],
})
);

app.use(morgan('tiny'));

app.all(
'*',
createRequestHandler({
  build: SERVER_BUILD_DIR,
})
);
const port = process.env.PORT || 3000;

app.listen(port, () => {
console.log(`Express server listening on port ${port}`);
});

4

使用 node 命令启动服务器:

¥Start the server with node command:

Terminal
node server.ts

Netlify

这是实验性的,可能会发生重大变化。我们没有针对此配置进行持续测试。

1

创建服务器入口文件。所有请求都将通过该中间件进行委托。确切的文件位置很重要。

¥Create a server entry file. All requests will be delegated through this middleware. The exact file location is important.

netlify/functions/server.ts
const { createRequestHandler } = require('@expo/server/adapter/netlify');

const handler = createRequestHandler({
build: require('path').join(__dirname, '../../dist/server'),
});

module.exports = { handler };

2

在项目的根目录创建一个 Netlify 配置文件,以将所有请求重定向到服务器功能。

¥Create a Netlify configuration file at the root of your project to redirect all requests to the server function.

netlify.toml
[build]
command = "expo export -p web"
functions = "netlify/functions"
publish = "dist/client"

[[redirects]]
from = "/*"
to = "/.netlify/functions/server"
status = 404

[functions]
# Include everything to ensure dynamic routes can be used.
included_files = ["dist/server/**/*"]

[[headers]]
for = "/dist/server/_expo/functions/*"
[headers.values]
  # Set to 60 seconds as an example.
  "Cache-Control" = "public, max-age=60, s-maxage=60"

3

创建配置文件后,你可以使用 Expo CLI 构建网站和功能:

¥After you have created the configuration files, you can build the website and functions with Expo CLI:

Terminal
npx expo export -p web

4

使用 Netlify CLI 部署到 Netlify。

¥Deploy to Netlify with the Netlify CLI.

Terminal
# Install the Netlify CLI globally if needed.
npm install netlify-cli -g
# Deploy the website.
netlify deploy

你现在可以通过 Netlify CLI 提供的 URL 访问你的网站。运行 netlify deploy --prod 将发布到生产 URL。

¥You can now visit your website at the URL provided by Netlify CLI. Running netlify deploy --prod will publish to the production URL.

5

如果你使用任何环境变量或 .env 文件,请将它们添加到 Netlify。你可以通过转到站点设置并将它们添加到构建和部署部分来完成此操作。

¥If you're using any environment variables or .env files, add them to Netlify. You can do this by going to the Site settings and adding them to the Build & deploy section.

Vercel

这是实验性的,可能会发生重大变化。我们没有针对此配置进行持续测试。

1

创建服务器入口文件。所有请求都将通过该中间件进行委托。确切的文件位置很重要。

¥Create a server entry file. All requests will be delegated through this middleware. The exact file location is important.

api/index.ts
const { createRequestHandler } = require('@expo/server/adapter/vercel');

module.exports = createRequestHandler({
build: require('path').join(__dirname, '../dist/server'),
});

2

在项目的根目录下创建 Vercel 配置文件 (vercel.json),以将所有请求重定向到服务器函数。

¥Create a Vercel configuration file (vercel.json) at the root of your project to redirect all requests to the server function.

vercel.json
{
"buildCommand": "expo export -p web",
"outputDirectory": "dist/client",
"functions": {
  "api/index.ts": {
    "runtime": "@vercel/node@5.1.8",
    "includeFiles": "dist/server/**"
  }
},
"rewrites": [
  {
    "source": "/(.*)",
    "destination": "/api/index"
  }
]
}

新版本的 vercel.json 不再使用 routesbuilds 配置选项,并自动从 dist/client 输出目录提供你的公共资源。

¥The newer version of the vercel.json does not use routes and builds configuration options anymore, and serves your public assets from the dist/client output directory automatically.

vercel.json
{
"version": 2,
"outputDirectory": "dist",
"builds": [
  {
    "src": "package.json",
    "use": "@vercel/static-build",
    "config": {
      "distDir": "dist/client"
    }
  },
  {
    "src": "api/index.ts",
    "use": "@vercel/node",
    "config": {
      "includeFiles": ["dist/server/**"]
    }
  }
],
"routes": [
  {
    "handle": "filesystem"
  },
  {
    "src": "/(.*)",
    "dest": "/api/index.ts"
  }
]
}

vercel.json 的旧版本需要 @vercel/static-build 运行时来为 dist/client 输出目录中的资源提供服务。

¥The legacy version of the vercel.json needs a @vercel/static-build runtime to serve your assets from the dist/client output directory.

3

注意:此步骤仅适用于旧版 vercel.json 的用户。如果你使用的是 v3,则可以跳过此步骤。

¥Note: This step only applies to users of the legacy version of the vercel.json. If you're using v3, you can skip this step.

创建配置文件后,将 vercel-build 脚本添加到 package.json 文件并将其设置为 expo export -p web

¥After you have created the configuration files, add a vercel-build script to your package.json file and set it to expo export -p web.

4

使用 Vercel CLI 部署到 Vercel。

¥Deploy to Vercel with the Vercel CLI.

Terminal
# Install the Vercel CLI globally if needed.
npm install vercel -g
# Build the website.
vercel build
# Deploy the website.
vercel deploy --prebuilt

你现在可以通过 Vercel CLI 提供的 URL 访问你的网站。

¥You can now visit your website at the URL provided by the Vercel CLI.

已知的限制

¥Known limitations

API Routes beta 版本当前不支持多个已知功能。

¥Several known features are not currently supported in the API Routes beta release.

无动态导入

¥No dynamic imports

API 路由目前的工作方式是将所有代码(不包括 Node.js 内置代码)打包到一个文件中。这意味着你不能使用任何未与服务器打包的外部依赖。例如,无法使用诸如 sharp 之类的包含多个平台二进制文件的库。这将在未来版本中解决。

¥API Routes currently work by bundling all code (minus the Node.js built-ins) into a single file. This means that you cannot use any external dependencies that are not bundled with the server. For example, a library such as sharp, which includes multiple platform binaries, cannot be used. This will be addressed in a future version.

不支持 ESM

¥ESM not supported

当前的打包实现选择更加统一而不是灵活。这意味着原生不支持 ESM 的限制将转移到 API 路由中。所有代码都将被转译为 Common JS (require/module.exports)。但是,无论如何,我们建议你使用 ESM 编写 API 路由。这将在未来版本中解决。

¥The current bundling implementation opts to be more unified than flexible. This means the limitation of native not supporting ESM is carried over to API Routes. All code will be transpiled down to Common JS (require/module.exports). However, we recommend you write API Routes using ESM regardless. This will be addressed in a future version.