Back to Expo & Mobile

expo-api-routes

ExpoAPI RoutesExpo RouterEAS HostingBackend DevelopmentMobileServerlessSecurity
⭐ 2.1kπŸ“„ MITπŸ•’ 2026-06-16Source β†—

Install this skill

npx skills add expo/skills

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

Expo API routes allow developers to build server-side logic directly within an Expo project directory using a file-system based routing structure. By defining files with the +api.ts suffix, developers create backend endpoints that execute in a controlled environment, separate from the mobile client. This architecture facilitates secure interaction with sensitive APIs, database operations, and environment-protected services without exposing secrets to the application bundle. It follows standard Request and Response patterns, making it compatible with familiar web-standard APIs. This integrated approach simplifies cross-platform development by unifying client-side UI and server-side logic in a single codebase, specifically targeted for mobile environments running on Expo Application Services.

When to Use This Skill

  • β€’Masking API keys for third-party services like OpenAI or Stripe
  • β€’Executing database queries that require administrative permissions
  • β€’Offloading resource-intensive data processing from the mobile device
  • β€’Receiving and verifying webhooks from external payment providers

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • β€œCreate an API route for my Expo app
  • β€œHow to handle POST requests in Expo router
  • β€œHide OpenAI API key in Expo
  • β€œBuild a backend endpoint in my Expo project
  • β€œSet up server-side logic using +api.ts

Pro Tips

  • πŸ’‘Always use environment variables for secrets (`process.env.EXPO_PUBLIC_MY_VAR`) and configure them correctly for your EAS build to prevent sensitive data from being bundled client-side.
  • πŸ’‘Structure your API routes logically within the `app/+api` directory, organizing by resource or feature to maintain a scalable and maintainable codebase.
  • πŸ’‘Implement comprehensive error handling and logging within your API routes to monitor performance, debug issues, and respond gracefully to unexpected situations in production.

What this skill does

  • β€’Defining server-side endpoints using file-system routing
  • β€’Processing HTTP methods including GET, POST, PUT, and DELETE
  • β€’Managing secure server-side secrets via environment variables
  • β€’Handling request parsing for JSON bodies, query parameters, and headers
  • β€’Implementing custom CORS headers for cross-origin web requests

When not to use it

  • βœ•Handling high-frequency real-time updates or WebSocket communication
  • βœ•Exposing public data that could be fetched directly from a client
  • βœ•Managing complex user authentication flows better suited for dedicated Auth providers

Example workflow

  1. Create an app/api/data+api.ts file
  2. Export a POST function to handle incoming requests
  3. Access process.env variables inside the handler
  4. Return data using the Response.json helper
  5. Deploy the project to EAS Hosting to enable the backend
  6. Call the endpoint from the mobile client using fetch

Prerequisites

  • –Expo Router installed
  • –EAS CLI configured for deployment
  • –Node.js environment

Pitfalls & limitations

  • !API routes only function when deployed to a server environment via EAS
  • !Environment variables are server-side only and remain inaccessible to mobile client code
  • !Direct file uploads are inefficient; use presigned URLs for storage instead

FAQ

Where do I store my API keys?
Store secrets in your project environment variables. Access them on the server using process.env, which ensures they are never bundled into the mobile app.
Can I use these routes for real-time chat?
No, API routes are REST-based. For real-time updates, use WebSocket-based services or dedicated providers like Supabase Realtime.
How do I test my routes before deploying?
Use npx expo serve to run a local development server, then test your endpoints using tools like curl or Postman.

How it compares

Unlike manual backend setups like Express or separate microservices, Expo API routes keep your backend logic co-located with your frontend, reducing context switching and simplifying project structure.

Source & trust

⭐ 2.1k starsπŸ“„ MITπŸ•’ Updated 2026-06-16
πŸ“„ Full skill instructions β€” original source: expo/skills
## When to Use API Routes

Use API routes when you need:

- **Server-side secrets** β€” API keys, database credentials, or tokens that must never reach the client
- **Database operations** β€” Direct database queries that shouldn't be exposed
- **Third-party API proxies** β€” Hide API keys when calling external services (OpenAI, Stripe, etc.)
- **Server-side validation** β€” Validate data before database writes
- **Webhook endpoints** β€” Receive callbacks from services like Stripe or GitHub
- **Rate limiting** β€” Control access at the server level
- **Heavy computation** β€” Offload processing that would be slow on mobile

## When NOT to Use API Routes

Avoid API routes when:

- **Data is already public** β€” Use direct fetch to public APIs instead
- **No secrets required** β€” Static data or client-safe operations
- **Real-time updates needed** β€” Use WebSockets or services like Supabase Realtime
- **Simple CRUD** β€” Consider Firebase, Supabase, or Convex for managed backends
- **File uploads** β€” Use direct-to-storage uploads (S3 presigned URLs, Cloudflare R2)
- **Authentication only** β€” Use Clerk, Auth0, or Firebase Auth instead

## File Structure

API routes live in the app directory with +api.ts suffix:

app/
api/
hello+api.ts β†’ GET /api/hello
users+api.ts β†’ /api/users
users/[id]+api.ts β†’ /api/users/:id
(tabs)/
index.tsx


## Basic API Route

// app/api/hello+api.ts
export function GET(request: Request) {
return Response.json({ message: "Hello from Expo!" });
}


## HTTP Methods

Export named functions for each HTTP method:

// app/api/items+api.ts
export function GET(request: Request) {
return Response.json({ items: [] });
}

export async function POST(request: Request) {
const body = await request.json();
return Response.json({ created: body }, { status: 201 });
}

export async function PUT(request: Request) {
const body = await request.json();
return Response.json({ updated: body });
}

export async function DELETE(request: Request) {
return new Response(null, { status: 204 });
}


## Dynamic Routes

// app/api/users/[id]+api.ts
export function GET(request: Request, { id }: { id: string }) {
return Response.json({ userId: id });
}


## Request Handling

### Query Parameters

export function GET(request: Request) {
const url = new URL(request.url);
const page = url.searchParams.get("page") ?? "1";
const limit = url.searchParams.get("limit") ?? "10";

return Response.json({ page, limit });
}


### Headers

export function GET(request: Request) {
const auth = request.headers.get("Authorization");

if (!auth) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}

return Response.json({ authenticated: true });
}


### JSON Body

export async function POST(request: Request) {
const { email, password } = await request.json();

if (!email || !password) {
return Response.json({ error: "Missing fields" }, { status: 400 });
}

return Response.json({ success: true });
}


## Environment Variables

Use process.env for server-side secrets:

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

const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: Bearer ${process.env.OPENAI_API_KEY},
},
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: prompt }],
}),
});

const data = await response.json();
return Response.json(data);
}


Set environment variables:

- **Local**: Create .env file (never commit)
- **EAS Hosting**: Use eas env:create or Expo dashboard

## CORS Headers

Add CORS for web clients:

const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};

export function OPTIONS() {
return new Response(null, { headers: corsHeaders });
}

export function GET() {
return Response.json({ data: "value" }, { headers: corsHeaders });
}


## Error Handling

export async function POST(request: Request) {
try {
const body = await request.json();
// Process...
return Response.json({ success: true });
} catch (error) {
console.error("API error:", error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}
}


## Testing Locally

Start the development server with API routes:

npx expo serve


This starts a local server at http://localhost:8081 with full API route support.

Test with curl:

curl http://localhost:8081/api/hello
curl -X POST http://localhost:8081/api/users -H "Content-Type: application/json" -d '{"name":"Test"}'


## Deployment to EAS Hosting

### Prerequisites

npm install -g eas-cli
eas login


### Deploy

eas deploy


This builds and deploys your API routes to EAS Hosting (Cloudflare Workers).

### Environment Variables for Production

# Create a secret
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production

# Or use the Expo dashboard


### Custom Domain

Configure in eas.json or Expo dashboard.

## EAS Hosting Runtime (Cloudflare Workers)

API routes run on Cloudflare Workers. Key limitations:

### Missing/Limited APIs

- **No Node.js filesystem** β€” fs module unavailable
- **No native Node modules** β€” Use Web APIs or polyfills
- **Limited execution time** β€” 30 second timeout for CPU-intensive tasks
- **No persistent connections** β€” WebSockets require Durable Objects
- **fetch is available** β€” Use standard fetch for HTTP requests

### Use Web APIs Instead

// Use Web Crypto instead of Node crypto
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode("data")
);

// Use fetch instead of node-fetch
const response = await fetch("https://api.example.com");

// Use Response/Request (already available)
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});


### Database Options

Since filesystem is unavailable, use cloud databases:

- **Cloudflare D1** β€” SQLite at the edge
- **Turso** β€” Distributed SQLite
- **PlanetScale** β€” Serverless MySQL
- **Supabase** β€” Postgres with REST API
- **Neon** β€” Serverless Postgres

Example with Turso:

// app/api/users+api.ts
import { createClient } from "@libsql/client/web";

const db = createClient({
url: process.env.TURSO_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});

export async function GET() {
const result = await db.execute("SELECT * FROM users");
return Response.json(result.rows);
}


## Calling API Routes from Client

// From React Native components
const response = await fetch("/api/hello");
const data = await response.json();

// With body
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "John" }),
});


## Common Patterns

### Authentication Middleware

// utils/auth.ts
export async function requireAuth(request: Request) {
const token = request.headers.get("Authorization")?.replace("Bearer ", "");

if (!token) {
throw new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}

// Verify token...
return { userId: "123" };
}

// app/api/protected+api.ts
import { requireAuth } from "../../utils/auth";

export async function GET(request: Request) {
const { userId } = await requireAuth(request);
return Response.json({ userId });
}


### Proxy External API

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

const response = await fetch(
https://api.weather.com/v1/current?city=${city}&key=${process.env.WEATHER_API_KEY}
);

return Response.json(await response.json());
}


## Rules

- NEVER expose API keys or secrets in client code
- ALWAYS validate and sanitize user input
- Use proper HTTP status codes (200, 201, 400, 401, 404, 500)
- Handle errors gracefully with try/catch
- Keep API routes focused β€” one responsibility per endpoint
- Use TypeScript for type safety
- Log errors server-side for debugging

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/expo-api-routes/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/expo/skills/expo-api-routes/SKILL.md
  • Cursor: ~/.cursor/skills/expo/skills/expo-api-routes/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/expo/skills/expo-api-routes/SKILL.md

πŸš€ Install with CLI:
npx skills add expo/skills

Read the Master Guide: Mastering Agent Skills β†’

Recommended Rules

View more rules β†’

Recommended Workflows

View more workflows β†’

Recommended MCP Servers

View more MCP servers β†’

Take It Further

Maximize your productivity with these powerful resources

πŸ“‹

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
πŸ“–

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid expo & mobile issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under Expo & Mobile and is published by Expo Team, maintained in expo/skills.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.