AI個人開発の技術スタック

モバイルアプリをExpoで作る

ExpoはReact Nativeを土台に、iOSとAndroidを1つのコードベースで作れるツールキットです。Webと同じJavaScript/TypeScriptの延長で始められ、AIコーディングエージェントとの反復開発とも相性が良い選択肢です。

Webの技術でiOS・Androidを両方作る

Expoは、React Nativeでのモバイル開発をコマンド1つで始められるように整えたツールキットです。画面遷移にはExpo Router、通知やカメラなどの端末機能には公式のライブラリ群が用意されており、ネイティブのコードをほとんど書かずにアプリの骨格を組み立てられます。ホットリロードで変更をすぐ端末に反映できるため、AIエージェントに実装させてはその場で確認する、というループを短く回せます。

app.config.tsとeas.jsonが土台になる

アプリのID、名前、EASのプロジェクトIDはapp.config.tsにまとめます。

import { ExpoConfig } from "expo/config";

const config: ExpoConfig = {
  name: "my-app",
  slug: "my-app",
  version: "1.0.0",
  ios: {
    bundleIdentifier: "com.example.myapp",
  },
  android: {
    package: "com.example.myapp",
  },
  extra: {
    eas: {
      projectId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    },
  },
};

export default config;

ビルドの種類ごとの設定はeas.jsonにプロファイルとして分けます。

{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal"
    },
    "production": {
      "autoIncrement": true
    }
  }
}

手元での動作確認はdevelopment、社内配布はpreview、ストア提出はproductionというように使い分けます。

EAS Build/Updateには無料枠がある

アプリストアに提出できる形式(.ipa/.apk)へのビルドは、EAS Buildというクラウドサービスで行います。無料プランでもAndroid・iOSそれぞれ月15回までのビルドが含まれ、個人開発の検証段階であれば十分な回数です。

eas build --profile production --platform all

もう一つの仕組みがEAS Updateです。JavaScript側の変更(画面の修正やロジックの変更)は、ストア審査を経ずに配信できます。

eas update --branch production --message "fix login bug"

無料プランでは月間アクティブユーザー1,000人までが対象です。ただし、ネイティブコードや権限まわりの変更はEAS Updateの対象外で、通常のストア審査が必要になります。

ストア公開には年会費・登録料がかかる

アプリをストアで公開するには、開発者登録が必要です。Apple Developer Programは年99ドル、Google Playは買い切りで25ドルです。初年度は合わせて124ドル程度、2年目以降はAppleの年99ドルのみが継続してかかります。これはエンジンやフレームワークの費用とは別に発生する固定費として見込んでおきます。

バックエンドはGoのAPIに接続する

Expoで作るアプリ本体は画面と端末側の処理を担当し、データベースへは直接つながりません。この手引のGoのAPI(モバイルアプリのAPIにGoを選ぶ理由)で紹介している構成のように、認証・権限チェック・DB操作を行うAPIサーバーを別途用意し、ExpoアプリはHTTPSでそのAPIとだけ通信します。

const res = await fetch("https://api.example.com/v1/items", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

if (!res.ok) {
  throw new Error("failed to fetch items");
}

const items = await res.json();

認証トークンは、ログイン時にGoのAPI側から発行してもらい、端末内のセキュアストレージ(expo-secure-store)に保存します。アプリのコードに接続情報やDBの認証情報を直接埋め込まないという、APIサーバーを間に置く理由そのものです。

プッシュ通知もAPI経由で送る

プッシュ通知は、端末側でExpoのプッシュトークンを取得し、それをGoのAPIへ登録しておく流れが基本です。

import * as Notifications from "expo-notifications";

const { status } = await Notifications.requestPermissionsAsync();
if (status !== "granted") {
  return;
}

const token = (await Notifications.getExpoPushTokenAsync()).data;

await fetch("https://api.example.com/v1/devices", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ pushToken: token }),
});

通知を送りたいタイミングになったら、GoのAPI側からExpoのプッシュ通知サービスへリクエストします。通知の送信元をアプリではなくAPIサーバーに寄せることで、誰にいつ何を送ったかの記録や、送信のレート制御もサーバー側で一元管理できます。

KPIもGoのAPIへ送って自分のDBに貯める

初回起動、登録完了、課金など、継続率や転換率を見るためのイベントも、専用のSaaSを導入せず、同じGoのAPIに送って自分のPostgreSQL(またはD1)に貯める方法があります。

async function trackEvent(name: string, props?: Record<string, unknown>) {
  fetch("https://api.example.com/v1/events", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name, props, ts: Date.now() }),
  }).catch(() => {});
}

trackEvent("signup_completed");

失敗しても画面の操作をブロックしないよう、送信は投げっぱなしにします。溜まったイベントはDB側でSQLを書いて、DAU・継続率・登録完了率などのKPIを自分で集計します。

ATT同意が不要な場合が多い:自分のAPIへ送るだけの一人称データ収集は、他社アプリをまたいだ広告目的のトラッキングではないため、iOSのApp Tracking Transparency(ATT)同意プロンプトの対象外になる場合がほとんどです。広告SDKや他社製の分析SDKを組み込む場合は、その都度ATTの対象になるかを個別に確認してください。

構成の選定から相談したい方へ

Expoアプリのバックエンド構築から、Cloud Run・自前VPSでの運用まで対応します。

運用について相談する