xref: /expo/docs/pages/preview/api-routes.mdx (revision 75d433ca)
1---
2title: API Routes
3description: Learn how to create server functions with Expo Router.
4---
5
6> **warning** This feature is still experimental. Available from Expo SDK 50 and Expo Router v3.
7
8import { FileTree } from '~/ui/components/FileTree';
9import { Step } from '~/ui/components/Step';
10import { Terminal } from '~/ui/components/Snippet';
11import { Collapsible } from '~/ui/components/Collapsible';
12
13Expo Router enables you to write server code for all platforms, right in your **app** directory.
14
15```json app.json
16{
17  "web": {
18    "bundler": "metro",
19    /* @info Output a dynamic server. */
20    "output": "server"
21    /* @end */
22  }
23}
24```
25
26Server features require a custom Node.js server. Most hosting providers support Node.js, including [Netlify](#netlify), [Cloudflare](https://www.cloudflare.com/), and [Vercel](https://vercel.com).
27
28## What are API Routes
29
30API Routes are functions that are executed when a route is matched. They can be used to handle sensitive data, such as API keys securely, or implement custom server logic. API Routes should be executed in a [WinterCG](https://wintercg.org/)-compliant environment.
31
32API Routes are defined by creating files in the **app** directory with the `+api.js` extension. For example, the following route handler is executed when the route `/hello` is matched.
33
34<FileTree files={['app/index.js', ['app/hello+api.ts', 'API Route']]} />
35
36## Create an API route
37
38<Step label="1">
39
40An API route is created in the **app** directory. For example, add the following route handler. It is executed when the route `/hello` is matched.
41
42```js app/hello+api.ts
43import { ExpoRequest, ExpoResponse } from 'expo-router/server';
44
45export function GET(request: ExpoRequest) {
46  return ExpoResponse.json({ hello: 'world' });
47}
48```
49
50You 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`.
51
52</Step>
53
54<Step label="2">
55
56Start the development server with Expo CLI:
57
58<Terminal cmd={['$ npx expo']} />
59
60</Step>
61
62<Step label="3">
63
64You can make a network request to the route to access the data. Run the following command to test the route:
65
66<Terminal cmd={['$ curl http://localhost:8081/hello']} />
67
68You can also make a request from the client code:
69
70```js app/index.js
71import { Button } from 'react-native';
72
73async function fetchHello() {
74  const response = await fetch('/hello');
75  const data = await response.json();
76  alert('Hello ' + data.hello);
77}
78
79export default function App() {
80  return <Button onPress={() => fetchHello()} title="Fetch hello" />;
81}
82```
83
84This won't work by default on native as `/hello` does not provide an origin URL. You can configure the origin URL in the app config file. It can be a mock URL in development. For example:
85
86```json app.json
87{
88  "plugins": [
89    [
90      "expo-router",
91      {
92        /* @info The URL where the API routes are hosted. */
93        "origin": "https://evanbacon.dev/"
94        /* @end */
95      }
96    ]
97  ]
98}
99```
100
101</Step>
102
103<Step label="4">
104
105Deploy the website and server to a [hosting provider](#deployment) to access the routes in production on both native and web.
106
107</Step>
108
109## Requests
110
111Requests use the global `ExpoRequest` object. It is a subclass of the standard [`Request`](https://fetch.spec.whatwg.org/#request) object and provides additional functionality such as the `expoUrl` object &mdash; a `URL` that has access to query parameters according to the Expo Router file convention.
112
113```ts app/blog/[post]+api.ts
114import { ExpoRequest, ExpoResponse } from 'expo-router/server';
115
116export async function GET(request: ExpoRequest, { post }: Record<string, string>) {
117  // const postId = request.expoUrl.searchParams.get('post')
118  // fetch data for `post`
119  return ExpoResponse.json({ ... });
120}
121```
122
123### Request body
124
125Use the `request.json()` function to access the request body. It automatically parses the body and returns the result.
126
127```ts app/validate+api.ts
128import { ExpoRequest, ExpoResponse } from 'expo-router/server';
129
130export async function POST(request: ExpoRequest) {
131  const body = await request.json();
132
133  return ExpoResponse.json({ ... });
134}
135```
136
137## Response
138
139Responses use the global `ExpoResponse` object. It is a subclass of the standard [`Response`](https://fetch.spec.whatwg.org/#response) object and provides additional functionality such as the `ExpoResponse.json` &mdash; an initializer which automatically stringifies the response body and returns status `200`.
140
141```ts app/demo+api.ts
142import { ExpoRequest, ExpoResponse } from 'expo-router/server';
143
144export function GET() {
145  return ExpoResponse.json({ hello: 'universe' });
146}
147```
148
149## Errors
150
151You can respond to server errors by using the `ExpoResponse` object.
152
153```ts app/blog/[post].ts
154import { ExpoRequest, ExpoResponse } from 'expo-router/server';
155
156export async function GET(request: ExpoRequest, { post }: Record<string, string>) {
157  if (!post) {
158    return new ExpoResponse('No post found', {
159      status: 404,
160      headers: {
161        'Content-Type': 'text/plain',
162      },
163    });
164  }
165  // fetch data for `post`
166  return ExpoResponse.json({ ... });
167}
168```
169
170Making 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`.
171
172## Bundling
173
174API Routes are bundled with Expo CLI and [Metro bundler](/guides/customizing-metro). They have access to all of the language features as your client code:
175
176- [TypeScript](/guides/typescript) &mdash; types and [**tsconfig.json** paths](/guides/typescript#path-aliases).
177- [Environment variables](/guides/environment-variables) &mdash; server routes have access to all environment variables, not just the ones prefixed with `EXPO_PUBLIC_`.
178- Node.js standard library &mdash; ensure that you are using the correct version of Node.js locally for your server environment.
179- **babel.config.js** and **metro.config.js** support &mdash; settings work across both client and server code.
180
181{/* TODO: SSR, redirects, middleware, and so on. */}
182
183## Security
184
185> **warning** While in beta, server code may leak into the client bundle. This won't happen in the stable release.
186
187Route 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.
188
189- 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.js**).
190- If the secret is in a **&lt;...&gt;+api.js** file, it is not included in the client bundle. It applies to all files that are imported in the route handler.
191- The secret stripping takes place in `expo/metro-config` and requires it to be used in the **metro.config.js**.
192
193## Deployment
194
195> **warning** This is experimental and subject to breaking changes. We have no continuous tests against this configuration.
196
197Every 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.
198
199Before deploying to these providers, it may be good to be familiar with the basics of [`npx expo export`](/more/expo-cli#exporting) command:
200
201- **/dist** is the default export directory for Expo CLI.
202- Files in **/public** are copied to **/dist** on export.
203- The `@expo/server` package is included with `expo` and delegates requests to the server routes.
204- `@expo/server` does **not** inflate environment variables from **.env** files. They are expected to load either by the hosting provider or the user.
205- Metro is not included in the server.
206
207### Express
208
209<Step label="1">
210
211Install the required dependencies:
212
213<Terminal cmd={['$ npm i -D express compression morgan']} />
214
215</Step>
216
217<Step label="2">
218
219Export the website for production:
220
221<Terminal cmd={['$ npx expo export -p web']} />
222
223</Step>
224
225<Step label="3">
226
227Write a server entry file that serves the static files and delegates requests to the server routes:
228
229```js server.js
230#!/usr/bin/env node
231
232const path = require('path');
233const { createRequestHandler } = require('@expo/server/adapter/express');
234
235const express = require('express');
236const compression = require('compression');
237const morgan = require('morgan');
238
239const BUILD_DIR = path.join(process.cwd(), 'dist');
240
241const app = express();
242
243app.use(compression());
244
245// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
246app.disable('x-powered-by');
247
248process.env.NODE_ENV = 'production';
249
250app.use(
251  // Prevent access to expo functions as these may
252  // contain sensitive information.
253  [/^\/_expo\/functions($|\/)/, '/'],
254  express.static(BUILD_DIR, {
255    maxAge: '1h',
256    extensions: ['html'],
257  })
258);
259
260app.use(morgan('tiny'));
261
262app.all(
263  '*',
264  createRequestHandler({
265    build: BUILD_DIR,
266  })
267);
268const port = process.env.PORT || 3000;
269
270app.listen(port, () => {
271  console.log(`Express server listening on port ${port}`);
272});
273```
274
275</Step>
276
277<Step label="4">
278
279Start the server with `node` command:
280
281<Terminal cmd={['$ node server.js']} />
282
283</Step>
284
285### Netlify
286
287> **warning** This is experimental and subject to breaking changes. We have no continuous tests against this configuration.
288
289<Step label="1">
290
291Create a server entry file. All requests will be delegated through this middleware. The exact file location is important.
292
293```js netlify/functions/server.js
294const { createRequestHandler } = require('@expo/server/adapter/netlify');
295
296const handler = createRequestHandler({
297  /* @info Points to the root `dist/` (output) folder */
298  build: require('path').join(__dirname, '../../dist'),
299  /* @end */
300  mode: process.env.NODE_ENV,
301});
302
303module.exports = { handler };
304```
305
306</Step>
307
308<Step label="2">
309
310Create a Netlify configuration file at the root of your project to redirect all requests to the server function.
311
312```yaml netlify.toml
313[build]
314  command = "expo export -p web"
315  functions = "netlify/functions"
316  publish = "dist"
317
318[[redirects]]
319  from = "/*"
320  to = "/.netlify/functions/server"
321  status = 404
322
323[functions]
324  # Include everything to ensure dynamic routes can be used.
325  included_files = ["dist/**/*"]
326
327[[headers]]
328  for = "/dist/_expo/functions/*"
329  [headers.values]
330    # Set to 60 seconds as an example.
331    "Cache-Control" = "public, max-age=60, s-maxage=60"
332```
333
334</Step>
335
336<Step label="3">
337
338After you have created the configuration files, you can build the website and functions with Expo CLI:
339
340<Terminal cmd={['$ npx expo export -p web']} />
341
342</Step>
343
344<Step label="4">
345
346Deploy to Netlify with the [Netlify CLI](https://docs.netlify.com/cli/get-started/).
347
348<Terminal
349  cmd={[
350    '# Install the Netlify CLI globally if needed.',
351    '$ npm install netlify-cli -g',
352    '# Deploy the website.',
353    '$ netlify deploy',
354  ]}
355/>
356
357You can now visit your website at the URL provided by Netlify CLI. Running `netlify deploy --prod` will publish to the production URL.
358
359</Step>
360
361<Step label="5">
362
363If 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.
364
365</Step>
366