🕓 2026-04-14에 기록, 2026-05-09에 업데이트됨. TS / Python / PHP SDK가 이제 public registry에 올라와 있어 -
@astroway/sdk(npm),astroway(PyPI),astroway/sdk(Packagist). 자세한 내용은 changelog에서 확인해.
점성술 앱을 만들고 싶어. 어디서부터 시작할까? 이 가이드에서는 API 선택부터 실제 작동하는 제품 배포까지, 결정 사항과 코드를 다뤄.
아키텍처 질문
섹션 제목: “아키텍처 질문”코드를 작성하기 전에 정해야 할 것: 계산을 클라이언트에서 할까, 서버에서 할까?
클라이언트 (예: 브라우저의 Swiss Ephemeris WASM):
- 장점: 요청당 비용 없음, 오프라인 동작, 완전한 제어
- 단점: 사용자마다 ~2MB WASM, 복잡한 라이선스 (Swiss Ephemeris의 상업적 사용에는 제한이 있음), 계산 코드를 직접 유지보수해야 함
서버 API (AstroWay, Prokerala 등):
- 장점: 클라이언트 의존성 없음, 작은 앱 bundle, 모바일에서 빠름, 라이선스 골칫거리 없음
- 단점: 요청당 크레딧 비용, “콜드” 요청 시 지연 시간
대부분의 앱에서는 서버 API가 이긴다. 모바일 사용자는 2MB WASM을 기다려주지 않고, 라이선스 미묘한 문제와 씨름할 필요도 없으며, 최신 API는 동일한 요청을 무료로 캐시해 (AstroWay는 X-Cache: HIT header와 함께 5분 캐시를 제공해).
API 선택
섹션 제목: “API 선택”중요한 것들:
- 정확도: “내부적으로” Swiss Ephemeris를 쓰는지 찾아봐 (±1 각초). API가 이를 명시하지 않으면 물어봐.
- 커버리지: 네이탈 + 시나스트리 + 트랜짓이 최소한. 프로그레션과 리턴은 다음 단계. 희귀한 기법 (렉티피케이션, Human Design)은 차별화 요소야.
- SDK 품질: 타입이 지정된 TypeScript와 Python SDK는 몇 시간을 절약해줘.
- 요금 모델: credit-based는 혼합 워크로드에서 per-request보다 확장성이 좋아.
이 튜토리얼에서는 AstroWay API를 사용할 거야. 네 가지 모두를 갖추고 있거든 (777개 endpoint, Swiss Ephemeris, 타입 지정 SDK, credit-based).
1단계: backend 띄우기
섹션 제목: “1단계: backend 띄우기”브라우저에서 점성술 API를 직접 호출하지 마 - API 키가 유출돼. 항상 자체 backend를 통해 호출해.
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단계: Frontend
섹션 제목: “2단계: Frontend”어떤 프레임워크든 괜찮아. 예시로 간단한 폼이 있는 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단계: 시나스트리
섹션 제목: “3단계: 시나스트리”궁합은 다음으로 자연스러운 기능이야. 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, });});끝. endpoint 하나, 호출당 50 크레딧, 0–100 점수와 cross-aspects를 반환해.
4단계: 데일리 운세
섹션 제목: “4단계: 데일리 운세”콘텐츠 engagement를 위해 데일리 운세는 필수야. 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단계: 캐싱
섹션 제목: “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단계: 배포
섹션 제목: “6단계: 배포”어떤 플랫폼이든 괜찮아. 빠른 시작이라면 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개 + 시나스트리 0.3개를 사용한다면:
100 × (20 + 20 + 0.3 × 50) × 30 = 165,000 credits/month이건 Starter 플랜으로 $19/월 (200K 크레딧)이야.
다음 단계
섹션 제목: “다음 단계”성장하면서 추가할 만한 고급 기능들:
- 트랜짓 오버레이:
/v1/transits는 네이탈 차트 위에 현재 행성 위치를 보여줘 - 프로그레션:
/v1/progressions으로 심리적 발달의 아크를 표현 - Solar return:
/v1/solar-return으로 “생일부터 1년 예측” - Human Design: Human Design API는 완전히 새로운 기능 영역을 열어줘
- 아스트로카토그래피:
/v1/acg는 사용자가 어디에 살면 좋을지 매핑해줘
각 기능은 처음부터 구현하기엔 무겁지만 - AstroWay에서는 API 호출 한 번이야.
시작하기
섹션 제목: “시작하기”- 무료 API 키 받기: 월 10 000 크레딧
- 전체 API reference: 모든 777개 endpoint
- Birth Chart API: 차트 endpoint 상세 문서
Solar Fire에 사용된 것과 동일한 Swiss Ephemeris - 단 4줄의 코드로.
카드 없이 무료 키. 첫 결제 전까지 월 5,000 API 호출.