EAS Hosting
Serviço EAS – custos aplicam-se. EAS Hosting é um produto pago do Expo Application Services com limites de nível gratuito; deploys de produção usam a cota de requisições e largura de banda do seu plano. Veja https://expo.dev/pricing. Criar rotas de API e exportar o pacote web são gratuitos e de código aberto, e você pode hospedar a saída exportada do servidor por conta própria em vez de usar o EAS Hosting.
O EAS Hosting implanta seu app web Expo e rotas de API na borda gerenciada da Expo (Cloudflare Workers). Exporte o pacote web com npx expo export -p web e envie-o com eas deploy – o mesmo comando implanta quaisquer rotas de API do Expo Router empacotadas junto. Esta skill cobre a implantação de um site, a criação de rotas de API e o runtime de hospedagem; veja a seção de Implantação abaixo para o fluxo de deploy.
Quando Usar Rotas de API
Use rotas de API quando precisar de:
- Segredos do lado do servidor — Chaves de API, credenciais de banco de dados ou tokens que nunca devem chegar ao cliente
- Operações de banco de dados — Consultas diretas ao banco que não devem ser expostas
- Proxies de APIs de terceiros — Ocultar chaves de API ao chamar serviços externos (OpenAI, Stripe, etc.)
- Validação do lado do servidor — Validar dados antes de escrever no banco
- Endpoints de webhook — Receber callbacks de serviços como Stripe ou GitHub
- Limitação de taxa — Controlar o acesso no nível do servidor
- Computação pesada — Descarregar processamento que seria lento no dispositivo móvel
Quando NÃO Usar Rotas de API
Evite rotas de API quando:
- Os dados já são públicos — Use fetch direto para APIs públicas
- Não há segredos necessários — Dados estáticos ou operações seguras para o cliente
- Atualizações em tempo real são necessárias — Use WebSockets ou serviços como Supabase Realtime
- CRUD simples — Considere Firebase, Supabase ou Convex para backends gerenciados
- Upload de arquivos — Use uploads diretos para armazenamento (URLs pré-assinadas do S3, Cloudflare R2)
- Apenas autenticação — Use Clerk, Auth0 ou Firebase Auth
Estrutura de Arquivos
As rotas de API ficam no diretório app com o sufixo +api.ts:
app/
api/
hello+api.ts → GET /api/hello
users+api.ts → /api/users
users/[id]+api.ts → /api/users/:id
(tabs)/
index.tsx
Rota de API Básica
// app/api/hello+api.ts
export function GET(request: Request) {
return Response.json({ message: "Olá do Expo!" });
}
Métodos HTTP
Exporte funções nomeadas para cada método HTTP:
// 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 });
}
Rotas Dinâmicas
// app/api/users/[id]+api.ts
export function GET(request: Request, { id }: { id: string }) {
return Response.json({ userId: id });
}
Manipulação de Requisições
Parâmetros de Consulta
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 });
}
Cabeçalhos
export function GET(request: Request) {
const auth = request.headers.get("Authorization");
if (!auth) {
return Response.json({ error: "Não autorizado" }, { status: 401 });
}
return Response.json({ authenticated: true });
}
Corpo JSON
export async function POST(request: Request) {
const { email, password } = await request.json();
if (!email || !password) {
return Response.json({ error: "Campos ausentes" }, { status: 400 });
}
return Response.json({ success: true });
}
Variáveis de Ambiente
Use process.env para segredos do lado do servidor:
// 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);
}
Defina variáveis de ambiente:
- Local: Crie o arquivo
.env(nunca commitar) - EAS Hosting: Use
eas env:createou o painel da Expo
Cabeçalhos CORS
Adicione CORS para clientes web:
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: "valor" }, { headers: corsHeaders });
}
Tratamento de Erros
export async function POST(request: Request) {
try {
const body = await request.json();
// Processar...
return Response.json({ success: true });
} catch (error) {
console.error("Erro na API:", error);
return Response.json({ error: "Erro interno do servidor" }, { status: 500 });
}
}
Teste Local
Inicie o servidor de desenvolvimento com rotas de API:
npx expo serve
Isso inicia um servidor local em http://localhost:8081 com suporte completo a rotas de API.
Teste com curl:
curl http://localhost:8081/api/hello
curl -X POST http://localhost:8081/api/users -H "Content-Type: application/json" -d '{"name":"Teste"}'
Implantação no EAS Hosting
Pré-requisitos
npm install -g eas-cli
eas login
Implantar
Fazer o deploy envia seu pacote web e quaisquer rotas de API do Expo Router juntos – eas deploy lida com ambos. A exportação é executada independentemente de você ter um site completo, um backend apenas com rotas de API, ou ambos.
# Exportar o pacote web (inclui quaisquer rotas de API)
npx expo export -p web
# Implantar preview (URL estilo PR)
npx eas-cli@latest deploy
# Implantar em produção
npx eas-cli@latest deploy --prod
Tudo vai para o EAS Hosting (Cloudflare Workers).
Variáveis de Ambiente para Produção
# Criar um segredo
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production
# Ou use o painel da Expo
Domínio Personalizado
Configure em eas.json ou no painel da Expo.
Automatizar com EAS Workflows
Implante o site (e rotas de API) a cada push para a branch main com um workflow type: deploy:
.eas/workflows/deploy.yml
name: Implantar
on:
push:
branches:
- main
# https://docs.expo.dev/eas/workflows/syntax/#deploy
jobs:
deploy_web:
type: deploy
params:
prod: true
Deploys de preview para pull requests usam o mesmo tipo de job com prod: false:
name: Preview Web de PR
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
type: deploy
params:
prod: false
Para criar ou validar YAML de workflows além destes exemplos, use a skill eas-workflows.
Runtime do EAS Hosting (Cloudflare Workers)
As rotas de API são executadas no Cloudflare Workers. Principais limitações:
APIs Ausentes/Limitadas
- Sem sistema de arquivos Node.js — módulo
fsindisponível - Sem módulos Node nativos — Use APIs Web ou polyfills
- Tempo de execução limitado — Timeout de 30 segundos para tarefas intensivas de CPU
- Sem conexões persistentes — WebSockets exigem Durable Objects
- fetch está disponível — Use fetch padrão para requisições HTTP
Use APIs Web
// Use Web Crypto em vez de Node crypto
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode("dados")
);
// Use fetch em vez de node-fetch
const response = await fetch("https://api.example.com");
// Use Response/Request (já disponíveis)
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
Opções de Banco de Dados
Como o sistema de arquivos não está disponível, use bancos de dados em nuvem:
- Cloudflare D1 — SQLite na borda
- Turso — SQLite distribuído
- PlanetScale — MySQL serverless
- Supabase — Postgres com API REST
- Neon — Postgres serverless
Exemplo com 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);
}
Chamando Rotas de API a partir do Cliente
// De componentes React Native
const response = await fetch("/api/hello");
const data = await response.json();
// Com corpo
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "João" }),
});
Padrões Comuns
Middleware de Autenticação
// 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: "Não autorizado" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// Verificar 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 de API Externa
// 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());
}
Regras
- NUNCA exponha chaves de API ou segredos no código do cliente
- SEMPRE valide e sanitize a entrada do usuário
- Use códigos de status HTTP adequados (200, 201, 400, 401, 404, 500)
- Trate erros graciosamente com try/catch
- Mantenha as rotas de API focadas — uma responsabilidade por endpoint
- Use TypeScript para segurança de tipos
- Registre erros no lado do servidor para depuração
Enviando Feedback
Se você encontrar erros, informações enganosas ou desatualizadas nesta skill, reporte para que a Expo possa melhorar:
npx --yes @expo/agent-cli@latest feedback --category skills --subject "eas-hosting" "<feedback acionável>"
Envie apenas quando tiver algo específico e acionável para reportar. Inclua o máximo de contexto relevante possível.
Se um agente falhar repetidamente em uma tarefa Expo ou o usuário tiver que intervir, envie um breve relatório da tarefa, resultado esperado e o que deu errado usando --category evals e o pacote Expo relevante ou comando como --subject.