🕓 2026-04-14 の記録、2026-05-09 更新。 TS / Python / PHP SDK は public registries で公開中 -
@astroway/sdk(npm)、astroway(PyPI)、astroway/sdk(Packagist)。詳細は changelog を参照。
占星術アプリを作りたい。どこから始める?このガイドでは、API の選定からプロダクトのデプロイまで、判断ポイントとコードをすべて紹介する。
アーキテクチャの検討
Section titled “アーキテクチャの検討”コードを書く前に決めよう:計算はクライアント側か、サーバー側か?
クライアント(例:ブラウザ上の Swiss Ephemeris WASM):
- メリット:リクエストごとのコストなし、オフラインで動作、完全なコントロール
- デメリット:ユーザーごとに約 2 MB の WASM、複雑なライセンス(Swiss Ephemeris の商用利用には制約がある)、計算コードのメンテナンスは自分持ち
サーバーサイド API(AstroWay、Prokerala など):
- メリット:クライアント側の依存なし、アプリの bundle が小さい、モバイルで高速、ライセンスの悩みなし
- デメリット:リクエストごとのクレジット消費、「コールド」リクエストでのレイテンシ
ほとんどのアプリではサーバーサイド API が勝つ。モバイルユーザーは 2 MB の WASM を待ってくれないし、ライセンスの細かい問題に関わる価値はない。それに、最新の API は同一リクエストを無料でキャッシュする(AstroWay は X-Cache: HIT ヘッダー付きの 5 分キャッシュを提供)。
API の選定
Section titled “API の選定”重要なポイント:
- 精度:内部で Swiss Ephemeris を使っているものを探そう(±1 秒角)。API が明言していなければ、問い合わせてみよう。
- カバレッジ:ネイタル + シナストリー + トランジットが最低ライン。プログレッションやリターンは次のレベル。レアな技法(レクティフィケーション、Human Design)は差別化要素。
- SDK の品質:型付きの TypeScript / Python SDK は何時間も節約してくれる。
- 料金モデル:credit-based は per-request よりも混合負荷でスケールしやすい。
このチュートリアルでは AstroWay API を使う。4 つすべてを満たすからだ(777 エンドポイント、Swiss Ephemeris、型付き SDK、credit-based)。
ステップ 1:バックエンドを立てる
Section titled “ステップ 1:バックエンドを立てる”ブラウザから占星術 API を直接叩いてはいけない - API キーが漏洩する。必ず自分のバックエンド経由にしよう。
mkdir my-astrology-appcd my-astrology-appnpm init -ynpm install express @astroway/sdk zodnpm install -D typescript @types/express @types/node tsxsrc/server.ts を作成:
import express from 'express';import { Astroway } from '@astroway/sdk';import { z } from 'zod';
const app = express();app.use(express.json());
const client = new Astroway({ apiKey: process.env.ASTROWAY_API_KEY!,});
const BirthInput = z.object({ date: z.string(), time: z.string(), timezoneOffset: z.number(), latitude: z.number(), longitude: z.number(),});
app.post('/api/chart', async (req, res) => { const parsed = BirthInput.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: parsed.error });
try { const chart = await client.chart.compute({ ...parsed.data, houseSystem: 'P', }); res.json(chart); } catch (err) { res.status(500).json({ error: String(err) }); }});
app.listen(3000, () => console.log('http://localhost:3000'));起動:
ASTROWAY_API_KEY=aw_test_... npx tsx src/server.tscurl で確認:
curl -X POST http://localhost:3000/api/chart \ -H "Content-Type: application/json" \ -d '{"date":"1990-07-14","time":"14:30:00","timezoneOffset":3,"latitude":50.4501,"longitude":30.5234}'完全な JSON のネイタルチャートが返ってくる。
ステップ 2:フロントエンド
Section titled “ステップ 2:フロントエンド”どんなフレームワークでも OK。例として、シンプルなフォームを持つ React を使う:
import { useState } from 'react';
type Chart = { planets: { name: string; longitude: number; isRetrograde: boolean }[]; aspects: { planet1: string; planet2: string; type: { name: string }; orb: number }[];};
// Planets come with ecliptic longitude, not a sign: the sign is that longitude// divided by 30. Work it out on your side, it is cheaper than another call.const SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'];const signOf = (longitude: number) => SIGNS[Math.floor(longitude / 30) % 12];
export function NatalForm() { const [chart, setChart] = useState<Chart | null>(null);
async function handleSubmit(formData: FormData) { const body = Object.fromEntries(formData.entries()); const res = await fetch('/api/chart', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: body.date, time: body.time + ':00', timezoneOffset: Number(body.tz), latitude: Number(body.lat), longitude: Number(body.lng), }), }); setChart(await res.json()); }
return ( <div> <form action={handleSubmit}> <input name="date" type="date" required /> <input name="time" type="time" required /> <input name="tz" type="number" placeholder="Timezone offset (e.g. 3)" required /> <input name="lat" type="number" step="0.0001" placeholder="Latitude" required /> <input name="lng" type="number" step="0.0001" placeholder="Longitude" required /> <button>Build chart</button> </form>
{chart && ( <div> <h2>Planets</h2> <ul> {chart.planets.map(p => ( <li key={p.name}>{p.name}: {signOf(p.longitude)} {(p.longitude % 30).toFixed(2)}°{p.isRetrograde ? ' R' : ''}</li> ))} </ul> <h2>Aspects</h2> <ul> {chart.aspects.map((a, i) => ( <li key={i}>{a.planet1} {a.type.name} {a.planet2} (orb {a.orb.toFixed(2)}°)</li> ))} </ul> </div> )} </div> );}ステップ 3:シナストリー
Section titled “ステップ 3:シナストリー”相性診断は次に obvious な機能だ。src/server.ts に追加:
const SynastryInput = z.object({ chart1: BirthInput, chart2: BirthInput,});
app.post('/api/synastry', async (req, res) => { const parsed = SynastryInput.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: parsed.error });
const result = await client.synastry.compute(parsed.data); res.json({ score: result.compatibility.score, label: result.compatibility.label, aspects: result.crossAspects, });});これだけ。1 つのエンドポイント、1 回の呼び出しで 50 クレジット、0〜100 のスコアと cross-aspects を返す。
ステップ 4:デイリーホロスコープ
Section titled “ステップ 4:デイリーホロスコープ”コンテンツによるエンゲージメントには、デイリーホロスコープが must have だ。AstroWay は実際のトランジットデータから生成する:
// The daily horoscope is keyed by sign and date, not by the birth chart.const DailyInput = z.object({ sign: z.enum(['aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo', 'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces']), date: z.string(),});
app.post('/api/horoscope/daily', async (req, res) => { const parsed = DailyInput.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: parsed.error });
const horoscope = await client.horoscope.daily({ ...parsed.data, language: 'uk' }); res.json({ text: horoscope.horoscope, disclaimer: horoscope.disclaimer, // must be shown in the UI! });});重要:AI 生成コンテンツには disclaimer が含まれており、(Terms of Service に基づき)ユーザーへの表示が義務付けられている。削除しないこと。
ステップ 5:キャッシュ
Section titled “ステップ 5:キャッシュ”デイリーホロスコープは、同じ誕生日で似たネイタルチャートを持つユーザー全員で同一だ。積極的にキャッシュしよう:
import { LRUCache } from 'lru-cache';
const cache = new LRUCache<string, any>({ max: 1000, ttl: 86400 * 1000 });
app.post('/api/horoscope/daily', async (req, res) => { const key = JSON.stringify(req.body); const cached = cache.get(key); if (cached) return res.json(cached);
const horoscope = await client.horoscope.daily(req.body); cache.set(key, horoscope); res.json(horoscope);});AstroWay にも組み込みの 5 分間サーバーキャッシュ(X-Cache: HIT)があるが、長寿命コンテンツには独自のキャッシュが必要だ。
ステップ 6:デプロイ
Section titled “ステップ 6:デプロイ”どのプラットフォームでも OK。クイックスタートなら Vercel か Railway:
# vercel.json{ "functions": { "src/server.ts": { "runtime": "@vercel/node" } } }プラットフォームのダッシュボードで env ASTROWAY_API_KEY を設定しよう。
ここまで来たら、ローンチ前に月間のクレジット消費量を見積もろう:
credits/month = DAU × (avg_charts_per_user × 20 + avg_synastries × 50 + avg_horoscopes × 20) × 30100 DAU で、1 ユーザーあたり 1 日にチャート 1 回 + ホロスコープ 1 回 + シナストリー 0.3 回の場合:
100 × (20 + 20 + 0.3 × 50) × 30 = 165,000 credits/monthこれは Starter プラン の $19/月(200K クレジット)で収まる。
次のステップ
Section titled “次のステップ”成長に合わせて追加したい高度な機能:
- トランジットのオーバーレイ:
/v1/transitsで現在の惑星位置をネイタルチャートに重ねて表示 - プログレッション:
/v1/progressionsで心理的成長の弧を描く - Solar return:
/v1/solar-returnで「誕生日からの 1 年の予測」 - Human Design:Human Design API でまったく新しい機能領域が開ける
- アストロカートグラフィー:
/v1/acgでユーザーが住むのに適した場所をマッピング
どの機能もゼロから実装するのは大変だが、AstroWay なら API 1 コールで済む。
- 無料の API キーを取得:10 000 クレジット / 月
- 完全な API reference:全 777 エンドポイント
- Birth Chart API:チャートエンドポイントの詳細ドキュメント
Solar Fireと同じSwiss Ephemeris - 4行のコードで。
無料のキー(カード不要)。最初の支払いまでに月5,000回の呼び出し。