{"id":"8ab73d2b-8502-4715-a517-4fde35c22b87","shortId":"aCsncR","kind":"skill","title":"expo-api-routes","tagline":"Guidelines for creating API routes in Expo Router with EAS Hosting","description":"## When to Use API Routes\n\nUse API routes when you need:\n\n- **Server-side secrets** — API keys, database credentials, or tokens that must never reach the client\n- **Database operations** — Direct database queries that shouldn't be exposed\n- **Third-party API proxies** — Hide API keys when calling external services (OpenAI, Stripe, etc.)\n- **Server-side validation** — Validate data before database writes\n- **Webhook endpoints** — Receive callbacks from services like Stripe or GitHub\n- **Rate limiting** — Control access at the server level\n- **Heavy computation** — Offload processing that would be slow on mobile\n\n## When NOT to Use API Routes\n\nAvoid API routes when:\n\n- **Data is already public** — Use direct fetch to public APIs instead\n- **No secrets required** — Static data or client-safe operations\n- **Real-time updates needed** — Use WebSockets or services like Supabase Realtime\n- **Simple CRUD** — Consider Firebase, Supabase, or Convex for managed backends\n- **File uploads** — Use direct-to-storage uploads (S3 presigned URLs, Cloudflare R2)\n- **Authentication only** — Use Clerk, Auth0, or Firebase Auth instead\n\n## File Structure\n\nAPI routes live in the `app` directory with `+api.ts` suffix:\n\n```\napp/\n  api/\n    hello+api.ts          → GET /api/hello\n    users+api.ts          → /api/users\n    users/[id]+api.ts     → /api/users/:id\n  (tabs)/\n    index.tsx\n```\n\n## Basic API Route\n\n```ts\n// app/api/hello+api.ts\nexport function GET(request: Request) {\n  return Response.json({ message: \"Hello from Expo!\" });\n}\n```\n\n## HTTP Methods\n\nExport named functions for each HTTP method:\n\n```ts\n// app/api/items+api.ts\nexport function GET(request: Request) {\n  return Response.json({ items: [] });\n}\n\nexport async function POST(request: Request) {\n  const body = await request.json();\n  return Response.json({ created: body }, { status: 201 });\n}\n\nexport async function PUT(request: Request) {\n  const body = await request.json();\n  return Response.json({ updated: body });\n}\n\nexport async function DELETE(request: Request) {\n  return new Response(null, { status: 204 });\n}\n```\n\n## Dynamic Routes\n\n```ts\n// app/api/users/[id]+api.ts\nexport function GET(request: Request, { id }: { id: string }) {\n  return Response.json({ userId: id });\n}\n```\n\n## Request Handling\n\n### Query Parameters\n\n```ts\nexport function GET(request: Request) {\n  const url = new URL(request.url);\n  const page = url.searchParams.get(\"page\") ?? \"1\";\n  const limit = url.searchParams.get(\"limit\") ?? \"10\";\n\n  return Response.json({ page, limit });\n}\n```\n\n### Headers\n\n```ts\nexport function GET(request: Request) {\n  const auth = request.headers.get(\"Authorization\");\n\n  if (!auth) {\n    return Response.json({ error: \"Unauthorized\" }, { status: 401 });\n  }\n\n  return Response.json({ authenticated: true });\n}\n```\n\n### JSON Body\n\n```ts\nexport async function POST(request: Request) {\n  const { email, password } = await request.json();\n\n  if (!email || !password) {\n    return Response.json({ error: \"Missing fields\" }, { status: 400 });\n  }\n\n  return Response.json({ success: true });\n}\n```\n\n## Environment Variables\n\nUse `process.env` for server-side secrets:\n\n```ts\n// app/api/ai+api.ts\nexport async function POST(request: Request) {\n  const { prompt } = await request.json();\n\n  const response = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,\n    },\n    body: JSON.stringify({\n      model: \"gpt-4\",\n      messages: [{ role: \"user\", content: prompt }],\n    }),\n  });\n\n  const data = await response.json();\n  return Response.json(data);\n}\n```\n\nSet environment variables:\n\n- **Local**: Create `.env` file (never commit)\n- **EAS Hosting**: Use `eas env:create` or Expo dashboard\n\n## CORS Headers\n\nAdd CORS for web clients:\n\n```ts\nconst corsHeaders = {\n  \"Access-Control-Allow-Origin\": \"*\",\n  \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE, OPTIONS\",\n  \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n};\n\nexport function OPTIONS() {\n  return new Response(null, { headers: corsHeaders });\n}\n\nexport function GET() {\n  return Response.json({ data: \"value\" }, { headers: corsHeaders });\n}\n```\n\n## Error Handling\n\n```ts\nexport async function POST(request: Request) {\n  try {\n    const body = await request.json();\n    // Process...\n    return Response.json({ success: true });\n  } catch (error) {\n    console.error(\"API error:\", error);\n    return Response.json({ error: \"Internal server error\" }, { status: 500 });\n  }\n}\n```\n\n## Testing Locally\n\nStart the development server with API routes:\n\n```bash\nnpx expo serve\n```\n\nThis starts a local server at `http://localhost:8081` with full API route support.\n\nTest with curl:\n\n```bash\ncurl http://localhost:8081/api/hello\ncurl -X POST http://localhost:8081/api/users -H \"Content-Type: application/json\" -d '{\"name\":\"Test\"}'\n```\n\n## Deployment to EAS Hosting\n\n### Prerequisites\n\n```bash\nnpm install -g eas-cli\neas login\n```\n\n### Deploy\n\n```bash\neas deploy\n```\n\nThis builds and deploys your API routes to EAS Hosting (Cloudflare Workers).\n\n### Environment Variables for Production\n\n```bash\n# Create a secret\neas env:create --name OPENAI_API_KEY --value sk-xxx --environment production\n\n# Or use the Expo dashboard\n```\n\n### Custom Domain\n\nConfigure in `eas.json` or Expo dashboard.\n\n## EAS Hosting Runtime (Cloudflare Workers)\n\nAPI routes run on Cloudflare Workers. Key limitations:\n\n### Missing/Limited APIs\n\n- **No Node.js filesystem** — `fs` module unavailable\n- **No native Node modules** — Use Web APIs or polyfills\n- **Limited execution time** — 30 second timeout for CPU-intensive tasks\n- **No persistent connections** — WebSockets require Durable Objects\n- **fetch is available** — Use standard fetch for HTTP requests\n\n### Use Web APIs Instead\n\n```ts\n// Use Web Crypto instead of Node crypto\nconst hash = await crypto.subtle.digest(\n  \"SHA-256\",\n  new TextEncoder().encode(\"data\")\n);\n\n// Use fetch instead of node-fetch\nconst response = await fetch(\"https://api.example.com\");\n\n// Use Response/Request (already available)\nreturn new Response(JSON.stringify(data), {\n  headers: { \"Content-Type\": \"application/json\" },\n});\n```\n\n### Database Options\n\nSince filesystem is unavailable, use cloud databases:\n\n- **Cloudflare D1** — SQLite at the edge\n- **Turso** — Distributed SQLite\n- **PlanetScale** — Serverless MySQL\n- **Supabase** — Postgres with REST API\n- **Neon** — Serverless Postgres\n\nExample with Turso:\n\n```ts\n// app/api/users+api.ts\nimport { createClient } from \"@libsql/client/web\";\n\nconst db = createClient({\n  url: process.env.TURSO_URL!,\n  authToken: process.env.TURSO_AUTH_TOKEN!,\n});\n\nexport async function GET() {\n  const result = await db.execute(\"SELECT * FROM users\");\n  return Response.json(result.rows);\n}\n```\n\n## Calling API Routes from Client\n\n```ts\n// From React Native components\nconst response = await fetch(\"/api/hello\");\nconst data = await response.json();\n\n// With body\nconst response = await fetch(\"/api/users\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({ name: \"John\" }),\n});\n```\n\n## Common Patterns\n\n### Authentication Middleware\n\n```ts\n// utils/auth.ts\nexport async function requireAuth(request: Request) {\n  const token = request.headers.get(\"Authorization\")?.replace(\"Bearer \", \"\");\n\n  if (!token) {\n    throw new Response(JSON.stringify({ error: \"Unauthorized\" }), {\n      status: 401,\n      headers: { \"Content-Type\": \"application/json\" },\n    });\n  }\n\n  // Verify token...\n  return { userId: \"123\" };\n}\n\n// app/api/protected+api.ts\nimport { requireAuth } from \"../../utils/auth\";\n\nexport async function GET(request: Request) {\n  const { userId } = await requireAuth(request);\n  return Response.json({ userId });\n}\n```\n\n### Proxy External API\n\n```ts\n// app/api/weather+api.ts\nexport async function GET(request: Request) {\n  const url = new URL(request.url);\n  const city = url.searchParams.get(\"city\");\n\n  const response = await fetch(\n    `https://api.weather.com/v1/current?city=${city}&key=${process.env.WEATHER_API_KEY}`\n  );\n\n  return Response.json(await response.json());\n}\n```\n\n## Rules\n\n- NEVER expose API keys or secrets in client code\n- ALWAYS validate and sanitize user input\n- Use proper HTTP status codes (200, 201, 400, 401, 404, 500)\n- Handle errors gracefully with try/catch\n- Keep API routes focused — one responsibility per endpoint\n- Use TypeScript for type safety\n- Log errors server-side for debugging\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.","tags":["expo","api","routes","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding"],"capabilities":["skill","source-sickn33","skill-expo-api-routes","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/expo-api-routes","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34793 github stars · SKILL.md body (8,829 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-24T00:50:54.169Z","embedding":null,"createdAt":"2026-04-18T21:36:51.302Z","updatedAt":"2026-04-24T00:50:54.169Z","lastSeenAt":"2026-04-24T00:50:54.169Z","tsv":"'-256':730 '-4':430 '/../utils/auth':904 '/api/hello':197,838 '/api/users':200,204,849 '/v1/chat/completions':413 '/v1/current?city=$':946 '1':324 '10':329 '123':898 '200':977 '201':260,978 '204':286 '30':689 '400':380,979 '401':352,888,980 '404':981 '500':545,982 '8081':566 '8081/api/hello':578 '8081/api/users':583 'access':90,472,477,487 'access-control-allow-head':486 'access-control-allow-method':476 'access-control-allow-origin':471 'add':463 'allow':474,479,489 'alreadi':117,749 'alway':966 'api':3,8,19,22,31,56,59,109,112,124,182,193,209,424,535,553,569,615,635,661,670,683,715,786,825,921,950,959,989 'api.example.com':746 'api.openai.com':412 'api.openai.com/v1/chat/completions':411 'api.ts':190,195,199,203,213,236,292,396,795,900,924 'api.weather.com':945 'api.weather.com/v1/current?city=$':944 'app':187,192 'app/api/ai':395 'app/api/hello':212 'app/api/items':235 'app/api/protected':899 'app/api/users':290,794 'app/api/weather':923 'application/json':420,588,760,856,893 'ask':1041 'async':246,262,276,361,398,517,811,868,906,926 'auth':178,342,346,808 'auth0':175 'authent':171,355,863 'author':344,421,494,876 'authtoken':806 'avail':706,750 'avoid':111 'await':253,269,369,405,409,438,525,727,744,816,836,841,847,913,942,954 'backend':157 'bash':555,575,597,607,626 'basic':208 'bearer':422,878 'bodi':252,258,268,274,358,426,524,844,857 'boundari':1049 'build':611 'call':62,824 'callback':80 'catch':532 'citi':937,939,947 'clarif':1043 'clear':1016 'clerk':174 'cli':603 'client':42,133,467,828,964 'client-saf':132 'cloud':768 'cloudflar':169,620,659,665,770 'code':965,976 'commit':451 'common':861 'compon':833 'comput':96 'configur':650 'connect':699 'consid':150 'console.error':534 'const':251,267,315,320,325,341,366,403,407,436,469,523,725,742,800,814,834,839,845,873,911,931,936,940 'content':418,434,492,586,758,854,891 'content-typ':417,491,585,757,853,890 'control':89,473,478,488 'convex':154 'cor':461,464 'corshead':470,503,512 'cpu':694 'cpu-intens':693 'creat':7,257,447,457,627,632 'createcli':797,802 'credenti':34 'criteria':1052 'crud':149 'crypto':720,724 'crypto.subtle.digest':728 'curl':574,576,579 'custom':648 'd':589 'd1':771 'dashboard':460,647,655 'data':73,115,130,437,442,509,734,755,840 'databas':33,43,46,75,761,769 'db':801 'db.execute':817 'debug':1007 'delet':278,484 'deploy':592,606,609,613 'describ':1020 'develop':550 'direct':45,120,162 'direct-to-storag':161 'directori':188 'distribut':777 'domain':649 'durabl':702 'dynam':287 'ea':14,452,455,594,602,604,608,618,630,656 'eas-c':601 'eas.json':652 'edg':775 'email':367,372 'encod':733 'endpoint':78,995 'env':448,456,631 'environ':385,444,622,641,1032 'environment-specif':1031 'error':349,376,513,533,536,537,540,543,885,984,1002 'etc':67 'exampl':790 'execut':687 'expert':1037 'expo':2,11,224,459,557,646,654 'expo-api-rout':1 'export':214,227,237,245,261,275,293,310,336,360,397,495,504,516,810,867,905,925 'expos':52,958 'extern':63,920 'fetch':121,410,704,709,736,741,745,837,848,943 'field':378 'file':158,180,449 'filesystem':673,764 'firebas':151,177 'focus':991 'fs':674 'full':568 'function':215,229,238,247,263,277,294,311,337,362,399,496,505,518,812,869,907,927 'g':600 'get':196,216,239,295,312,338,481,506,813,908,928 'github':86 'gpt':429 'grace':985 'guidelin':5 'h':584 'handl':306,514,983 'hash':726 'header':334,416,462,490,502,511,756,852,889 'heavi':95 'hello':194,222 'hide':58 'host':15,453,595,619,657 'http':225,232,711,974 'id':202,205,291,298,299,304 'import':796,901 'index.tsx':207 'input':971,1046 'instal':599 'instead':125,179,716,721,737 'intens':695 'intern':541 'item':244 'john':860 'json':357 'json.stringify':427,754,858,884 'keep':988 'key':32,60,425,636,667,948,951,960 'level':94 'libsql/client/web':799 'like':83,145 'limit':88,326,328,333,668,686,1008 'live':184 'local':446,547,562 'localhost':565,577,582 'log':1001 'login':605 'manag':156 'match':1017 'messag':221,431 'method':226,233,414,480,850 'middlewar':864 'miss':377,1054 'missing/limited':669 'mobil':104 'model':428 'modul':675,680 'must':38 'mysql':781 'name':228,590,633,859 'nativ':678,832 'need':26,140 'neon':787 'never':39,450,957 'new':282,317,499,731,752,882,933 'node':679,723,740 'node-fetch':739 'node.js':672 'npm':598 'npx':556 'null':284,501 'object':703 'offload':97 'one':992 'openai':65,634 'oper':44,135 'option':485,497,762 'origin':475 'output':1026 'page':321,323,332 'paramet':308 'parti':55 'password':368,373 'pattern':862 'per':994 'permiss':1047 'persist':698 'planetscal':779 'polyfil':685 'post':248,363,400,415,482,519,581,851 'postgr':783,789 'prerequisit':596 'presign':167 'process':98,527 'process.env':388 'process.env.openai':423 'process.env.turso':804,807 'process.env.weather':949 'product':625,642 'prompt':404,435 'proper':973 'proxi':57,919 'public':118,123 'put':264,483 'queri':47,307 'r2':170 'rate':87 'reach':40 'react':831 'real':137 'real-tim':136 'realtim':147 'receiv':79 'replac':877 'request':217,218,240,241,249,250,265,266,279,280,296,297,305,313,314,339,340,364,365,401,402,520,521,712,871,872,909,910,915,929,930 'request.headers.get':343,875 'request.json':254,270,370,406,526 'request.url':319,935 'requir':128,701,1045 'requireauth':870,902,914 'respons':283,408,500,743,753,835,846,883,941,993 'response.json':220,243,256,272,302,331,348,354,375,382,439,441,508,529,539,822,842,917,953,955 'response/request':748 'rest':785 'result':815 'result.rows':823 'return':219,242,255,271,281,301,330,347,353,374,381,440,498,507,528,538,751,821,896,916,952 'review':1038 'role':432 'rout':4,9,20,23,110,113,183,210,288,554,570,616,662,826,990 'router':12 'rule':956 'run':663 'runtim':658 's3':166 'safe':134 'safeti':1000,1048 'sanit':969 'scope':1019 'second':690 'secret':30,127,393,629,962 'select':818 'serv':558 'server':28,69,93,391,542,551,563,1004 'server-sid':27,68,390,1003 'serverless':780,788 'servic':64,82,144 'set':443 'sha':729 'shouldn':49 'side':29,70,392,1005 'simpl':148 'sinc':763 'sk':639 'sk-xxx':638 'skill':1011 'skill-expo-api-routes' 'slow':102 'source-sickn33' 'specif':1033 'sqlite':772,778 'standard':708 'start':548,560 'static':129 'status':259,285,351,379,544,887,975 'stop':1039 'storag':164 'string':300 'stripe':66,84 'structur':181 'substitut':1029 'success':383,530,1051 'suffix':191 'supabas':146,152,782 'support':571 'tab':206 'task':696,1015 'test':546,572,591,1035 'textencod':732 'third':54 'third-parti':53 'throw':881 'time':138,688 'timeout':691 'token':36,809,874,880,895 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'treat':1024 'tri':522 'true':356,384,531 'try/catch':987 'ts':211,234,289,309,335,359,394,468,515,717,793,829,865,922 'turso':776,792 'type':419,493,587,759,855,892,999 'typescript':997 'unauthor':350,886 'unavail':676,766 'updat':139,273 'upload':159,165 'url':168,316,318,803,805,932,934 'url.searchparams.get':322,327,938 'use':18,21,108,119,141,160,173,387,454,644,681,707,713,718,735,747,767,972,996,1009 'user':198,201,433,820,970 'userid':303,897,912,918 'utils/auth.ts':866 'valid':71,72,967,1034 'valu':510,637 'variabl':386,445,623 'verifi':894 'web':466,682,714,719 'webhook':77 'websocket':142,700 'worker':621,660,666 'would':100 'write':76 'x':580 'xxx':640","prices":[{"id":"261b382a-c5d8-4b34-b499-7f29ff2b011e","listingId":"8ab73d2b-8502-4715-a517-4fde35c22b87","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T21:36:51.302Z"}],"sources":[{"listingId":"8ab73d2b-8502-4715-a517-4fde35c22b87","source":"github","sourceId":"sickn33/antigravity-awesome-skills/expo-api-routes","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/expo-api-routes","isPrimary":false,"firstSeenAt":"2026-04-18T21:36:51.302Z","lastSeenAt":"2026-04-24T00:50:54.169Z"}],"details":{"listingId":"8ab73d2b-8502-4715-a517-4fde35c22b87","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"expo-api-routes","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34793,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-24T00:28:59Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"fc903e49ae077e168c8314c5116b78c417d31b62","skill_md_path":"skills/expo-api-routes/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/expo-api-routes"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"expo-api-routes","license":"MIT","description":"Guidelines for creating API routes in Expo Router with EAS Hosting"},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/expo-api-routes"},"updatedAt":"2026-04-24T00:50:54.169Z"}}