eas-hosting

Par expo · skills

Service EAS (payant). Déployez des sites web Expo et des routes API Expo Router sur EAS Hosting — exportez le bundle web, exécutez `eas deploy` pour les URLs de production et de preview de PR, gérez les secrets d'environnement et les domaines personnalisés, et travaillez dans le runtime Cloudflare Workers. Couvre également la création de routes API (handlers `+api.ts`, méthodes HTTP, gestion des requêtes, CORS). À utiliser lors du déploiement d'une application web Expo ou de routes API, de la configuration d'EAS Hosting, ou de la configuration des environnements d'hébergement et des domaines. Non applicable aux builds natifs ou aux publications sur les stores — utilisez le skill `eas-app-stores` pour cela.

npx skills add https://github.com/expo/skills --skill eas-hosting

Hébergement EAS

Service EAS - des coûts s'appliquent. EAS Hosting est un produit payant d'Expo Application Services avec des limites de tier gratuit ; les déploiements en production utilisent l'allocation de requêtes et de bande passante de votre plan. Voir https://expo.dev/pricing. La création de routes API et l'export du bundle web sont gratuits et open source, et vous pouvez auto-héberger la sortie du serveur exporté au lieu d'utiliser EAS Hosting.

EAS Hosting déploie votre application web Expo et vos routes API sur l'edge managé d'Expo (Cloudflare Workers). Exportez le bundle web avec npx expo export -p web et déployez-le avec eas deploy - la même commande déploie toute route API Expo Router groupée avec. Cette skill couvre le déploiement d'un site web, la création de routes API, et le runtime d'hébergement ; voir la section Deployment ci-dessous pour le workflow de déploiement.

Quand Utiliser les Routes API

Utilisez les routes API quand vous avez besoin :

  • De secrets côté serveur — Clés API, identifiants de base de données, ou tokens qui ne doivent jamais atteindre le client
  • D'opérations de base de données — Requêtes directes à la base de données qui ne doivent pas être exposées
  • De proxies pour API tierces — Masquer les clés API lors d'appels à des services externes (OpenAI, Stripe, etc.)
  • De validation côté serveur — Valider les données avant les écritures en base
  • De webhooks — Recevoir des callbacks de services comme Stripe ou GitHub
  • De limitation de débit — Contrôler l'accès au niveau du serveur
  • De calculs lourds — Externaliser les traitements qui seraient lents sur mobile

Quand NE PAS Utiliser les Routes API

Évitez les routes API quand :

  • Les données sont déjà publiques — Utilisez fetch direct vers les API publiques
  • Aucun secret requis — Données statiques ou opérations sûres pour le client
  • Les mises à jour en temps réel sont nécessaires — Utilisez WebSockets ou des services comme Supabase Realtime
  • Simple CRUD — Envisagez Firebase, Supabase, ou Convex pour les backends managés
  • Uploads de fichiers — Utilisez les uploads directs vers le stockage (URLs pré-signées S3, Cloudflare R2)
  • Authentification uniquement — Utilisez Clerk, Auth0, ou Firebase Auth

Structure des Fichiers

Les routes API vivent dans le répertoire app avec le suffixe +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

Route API de Base

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

Méthodes HTTP

Exportez des fonctions nommées pour chaque méthode 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 });
}

Routes Dynamiques

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

Gestion des Requêtes

Paramètres de Requête

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 });
}

Corps JSON

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 });
}

Variables d'Environnement

Utilisez process.env pour les secrets côté serveur :

// 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);
}

Définissez les variables d'environnement :

  • Local : Créez un fichier .env (ne commitez jamais)
  • EAS Hosting : Utilisez eas env:create ou le dashboard Expo

Headers CORS

Ajoutez CORS pour les clients 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: "value" }, { headers: corsHeaders });
}

Gestion des Erreurs

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

Test Local

Démarrez le serveur de développement avec les routes API :

npx expo serve

Cela démarre un serveur local à http://localhost:8081 avec le support complet des routes API.

Testez avec curl :

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

Déploiement vers EAS Hosting

Prérequis

npm install -g eas-cli
eas login

Déployer

Le déploiement expédie votre bundle web et toute route API Expo Router ensemble - eas deploy gère les deux. L'export s'exécute qu'il y ait un site web complet, un backend routes-API-uniquement, ou les deux.

# Exporter le bundle web (inclut toute route API)
npx expo export -p web

# Déployer une preview (URL style PR)
npx eas-cli@latest deploy

# Déployer en production
npx eas-cli@latest deploy --prod

Tout atterrit sur EAS Hosting (Cloudflare Workers).

Variables d'Environnement pour la Production

# Créer un secret
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production

# Ou utilisez le dashboard Expo

Domaine Personnalisé

Configurez dans eas.json ou le dashboard Expo.

Automatiser avec EAS Workflows

Déployez le site web (et les routes API) à chaque push sur main avec un workflow type: deploy :

.eas/workflows/deploy.yml

name: Deploy

on:
  push:
    branches:
      - main

# https://docs.expo.dev/eas/workflows/syntax/#deploy
jobs:
  deploy_web:
    type: deploy
    params:
      prod: true

Les preview deploys pour les pull requests utilisent le même type de job avec prod: false :

name: Web PR Preview

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  preview:
    type: deploy
    params:
      prod: false

Pour créer ou valider le YAML de workflow au-delà de ces exemples, utilisez la skill eas-workflows.

Runtime EAS Hosting (Cloudflare Workers)

Les routes API s'exécutent sur Cloudflare Workers. Limitations clés :

APIs Manquantes/Limitées

  • Pas de filesystem Node.js — Module fs indisponible
  • Pas de modules Node natifs — Utilisez Web APIs ou polyfills
  • Temps d'exécution limité — Timeout de 30 secondes pour les tâches gourmandes en CPU
  • Pas de connexions persistantes — WebSockets nécessitent Durable Objects
  • fetch est disponible — Utilisez fetch standard pour les requêtes HTTP

Utilisez Web APIs à la Place

// Utilisez Web Crypto au lieu de Node crypto
const hash = await crypto.subtle.digest(
  "SHA-256",
  new TextEncoder().encode("data")
);

// Utilisez fetch au lieu de node-fetch
const response = await fetch("https://api.example.com");

// Utilisez Response/Request (déjà disponibles)
return new Response(JSON.stringify(data), {
  headers: { "Content-Type": "application/json" },
});

Options de Base de Données

Puisque le filesystem n'est pas disponible, utilisez des bases de données cloud :

  • Cloudflare D1 — SQLite à l'edge
  • Turso — SQLite distribué
  • PlanetScale — MySQL sans serveur
  • Supabase — Postgres avec REST API
  • Neon — Postgres sans serveur

Exemple avec 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);
}

Appeler les Routes API depuis le Client

// Depuis les composants React Native
const response = await fetch("/api/hello");
const data = await response.json();

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

Patterns Courants

Middleware d'Authentification

// 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" },
    });
  }

  // Vérifier le 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 pour API Externe

// 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());
}

Règles

  • NE JAMAIS exposer les clés API ou secrets dans le code client
  • TOUJOURS valider et nettoyer les données utilisateur
  • Utiliser les codes de statut HTTP appropriés (200, 201, 400, 401, 404, 500)
  • Gérer les erreurs gracieusement avec try/catch
  • Garder les routes API focalisées — une responsabilité par endpoint
  • Utiliser TypeScript pour la sécurité des types
  • Logger les erreurs côté serveur pour le débogage

Skills similaires