Build a Horoscope App
Daily, weekly, and monthly predictions
App Features
Daily Horoscope
Fresh predictions every day
Weekly Forecast
Plan your week ahead
Monthly Overview
Long-term trends
Push Notifications
Morning reminders
Personalized
Based on birth chart
Social Sharing
Share with friends
Backend API (Node.js)
// server/routes/horoscope.ts
import express from 'express';
import { VedikaClient } from '@anthropic/vedika-sdk';
import { cache } from '../services/cache';
const router = express.Router();
const vedika = new VedikaClient();
const ZODIAC_SIGNS = [
'aries', 'taurus', 'gemini', 'cancer',
'leo', 'virgo', 'libra', 'scorpio',
'sagittarius', 'capricorn', 'aquarius', 'pisces'
];
// Daily horoscope for all signs (cached)
router.get('/daily', async (req, res) => {
const today = new Date().toISOString().split('T')[0];
const cacheKey = `horoscope:daily:${today}`;
// Try cache first
const cached = await cache.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}
// Fetch all signs in parallel
const predictions = await Promise.all(
ZODIAC_SIGNS.map(async sign => {
const horoscope = await vedika.dailyHoroscope({ sign, date: today });
return {
sign,
prediction: horoscope.prediction,
luckyNumber: horoscope.luckyNumber,
luckyColor: horoscope.luckyColor,
mood: horoscope.mood,
compatibility: horoscope.compatibility
};
})
);
const result = { date: today, predictions };
// Cache until midnight
const secondsUntilMidnight = getSecondsUntilMidnight();
await cache.set(cacheKey, JSON.stringify(result), secondsUntilMidnight);
res.json(result);
});
// Single sign daily horoscope
router.get('/daily/:sign', async (req, res) => {
const { sign } = req.params;
const today = new Date().toISOString().split('T')[0];
if (!ZODIAC_SIGNS.includes(sign.toLowerCase())) {
return res.status(400).json({ error: 'Invalid zodiac sign' });
}
const cacheKey = `horoscope:daily:${today}:${sign}`;
const cached = await cache.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}
const horoscope = await vedika.dailyHoroscope({
sign: sign.toLowerCase(),
date: today
});
await cache.set(cacheKey, JSON.stringify(horoscope), getSecondsUntilMidnight());
res.json(horoscope);
});
// Weekly horoscope
router.get('/weekly/:sign', async (req, res) => {
const { sign } = req.params;
const weekStart = getWeekStart();
const horoscope = await vedika.weeklyHoroscope({
sign: sign.toLowerCase(),
weekStart
});
res.json(horoscope);
});
// Monthly horoscope
router.get('/monthly/:sign', async (req, res) => {
const { sign } = req.params;
const month = new Date().toISOString().slice(0, 7); // YYYY-MM
const horoscope = await vedika.monthlyHoroscope({
sign: sign.toLowerCase(),
month
});
res.json(horoscope);
});
// Personalized horoscope based on birth chart
router.get('/personalized', async (req, res) => {
const user = req.user; // Authenticated user
if (!user.birthDetails) {
return res.status(400).json({
error: 'Birth details required for personalized horoscope'
});
}
const today = new Date().toISOString().split('T')[0];
const [chart, panchang, transits] = await Promise.all([
vedika.birthChart(user.birthDetails),
vedika.panchang({
date: today,
latitude: user.birthDetails.latitude,
longitude: user.birthDetails.longitude
}),
vedika.currentTransits(user.birthDetails)
]);
// Generate personalized prediction using AI
const prediction = await vedika.query({
question: 'What should I focus on today based on current planetary transits?',
birthDetails: user.birthDetails
});
res.json({
date: today,
sunSign: chart.sunSign,
moonSign: chart.moonSign,
ascendant: chart.ascendant,
todayPanchang: {
tithi: panchang.tithi.name,
nakshatra: panchang.nakshatra.name,
yoga: panchang.yoga.name
},
activeTransits: transits.filter(t => t.isActive),
prediction: prediction.answer,
advice: prediction.advice
});
});
function getSecondsUntilMidnight() {
const now = new Date();
const midnight = new Date(now);
midnight.setHours(24, 0, 0, 0);
return Math.floor((midnight - now) / 1000);
}
function getWeekStart() {
const now = new Date();
const day = now.getDay();
const diff = now.getDate() - day + (day === 0 ? -6 : 1);
return new Date(now.setDate(diff)).toISOString().split('T')[0];
}
export default router;
React Native App
Home Screen
// screens/HomeScreen.tsx
import React, { useState, useEffect } from 'react';
import {
View, Text, ScrollView, TouchableOpacity,
StyleSheet, RefreshControl, Image
} from 'react-native';
import { useAuth } from '../hooks/useAuth';
import { ZodiacCard } from '../components/ZodiacCard';
const ZODIAC_INFO = {
aries: { icon: require('../assets/aries.png'), color: '#FF6B6B', dates: 'Mar 21 - Apr 19' },
taurus: { icon: require('../assets/taurus.png'), color: '#4ECDC4', dates: 'Apr 20 - May 20' },
gemini: { icon: require('../assets/gemini.png'), color: '#FFE66D', dates: 'May 21 - Jun 20' },
cancer: { icon: require('../assets/cancer.png'), color: '#95E1D3', dates: 'Jun 21 - Jul 22' },
leo: { icon: require('../assets/leo.png'), color: '#F38181', dates: 'Jul 23 - Aug 22' },
virgo: { icon: require('../assets/virgo.png'), color: '#AA96DA', dates: 'Aug 23 - Sep 22' },
libra: { icon: require('../assets/libra.png'), color: '#FCBAD3', dates: 'Sep 23 - Oct 22' },
scorpio: { icon: require('../assets/scorpio.png'), color: '#A8D8EA', dates: 'Oct 23 - Nov 21' },
sagittarius: { icon: require('../assets/sagittarius.png'), color: '#F9ED69', dates: 'Nov 22 - Dec 21' },
capricorn: { icon: require('../assets/capricorn.png'), color: '#B5EAD7', dates: 'Dec 22 - Jan 19' },
aquarius: { icon: require('../assets/aquarius.png'), color: '#C7CEEA', dates: 'Jan 20 - Feb 18' },
pisces: { icon: require('../assets/pisces.png'), color: '#E2F0CB', dates: 'Feb 19 - Mar 20' }
};
export function HomeScreen({ navigation }) {
const { user } = useAuth();
const [horoscopes, setHoroscopes] = useState([]);
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
loadHoroscopes();
}, []);
async function loadHoroscopes() {
const response = await fetch(`${API_URL}/horoscope/daily`);
const data = await response.json();
setHoroscopes(data.predictions);
}
async function onRefresh() {
setRefreshing(true);
await loadHoroscopes();
setRefreshing(false);
}
// Show user's sign first if logged in
const sortedSigns = user?.zodiacSign
? [user.zodiacSign, ...Object.keys(ZODIAC_INFO).filter(s => s !== user.zodiacSign)]
: Object.keys(ZODIAC_INFO);
return (
<ScrollView
style={styles.container}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
<View style={styles.header}>
<Text style={styles.date}>{formatDate(new Date())}</Text>
<Text style={styles.title}>Daily Horoscope</Text>
</View>
{user?.zodiacSign && (
<TouchableOpacity
style={[styles.featured, { backgroundColor: ZODIAC_INFO[user.zodiacSign].color }]}
onPress={() => navigation.navigate('Detail', { sign: user.zodiacSign })}
>
<Image source={ZODIAC_INFO[user.zodiacSign].icon} style={styles.featuredIcon} />
<View style={styles.featuredContent}>
<Text style={styles.featuredSign}>{capitalize(user.zodiacSign)}</Text>
<Text style={styles.featuredPreview}>
{horoscopes.find(h => h.sign === user.zodiacSign)?.prediction.slice(0, 100)}...
</Text>
</View>
</TouchableOpacity>
)}
<View style={styles.grid}>
{sortedSigns.map(sign => (
<ZodiacCard
key={sign}
sign={sign}
info={ZODIAC_INFO[sign]}
horoscope={horoscopes.find(h => h.sign === sign)}
onPress={() => navigation.navigate('Detail', { sign })}
isUserSign={sign === user?.zodiacSign}
/>
))}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F8F9FA' },
header: { padding: 20, paddingTop: 60 },
date: { fontSize: 14, color: '#666', marginBottom: 4 },
title: { fontSize: 28, fontWeight: 'bold', color: '#1A1A2E' },
featured: { margin: 20, borderRadius: 16, padding: 20, flexDirection: 'row' },
featuredIcon: { width: 60, height: 60 },
featuredContent: { flex: 1, marginLeft: 16 },
featuredSign: { fontSize: 24, fontWeight: 'bold', color: '#FFF' },
featuredPreview: { fontSize: 14, color: 'rgba(255,255,255,0.9)', marginTop: 8 },
grid: { flexDirection: 'row', flexWrap: 'wrap', padding: 10 }
});
Detail Screen
// screens/DetailScreen.tsx
import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, StyleSheet, Share } from 'react-native';
import { TabView, SceneMap, TabBar } from 'react-native-tab-view';
export function DetailScreen({ route }) {
const { sign } = route.params;
const [index, setIndex] = useState(0);
const [routes] = useState([
{ key: 'daily', title: 'Today' },
{ key: 'weekly', title: 'This Week' },
{ key: 'monthly', title: 'This Month' }
]);
const [data, setData] = useState({
daily: null,
weekly: null,
monthly: null
});
useEffect(() => {
loadHoroscope('daily');
}, []);
useEffect(() => {
const period = routes[index].key;
if (!data[period]) {
loadHoroscope(period);
}
}, [index]);
async function loadHoroscope(period) {
const response = await fetch(`${API_URL}/horoscope/${period}/${sign}`);
const horoscope = await response.json();
setData(prev => ({ ...prev, [period]: horoscope }));
}
async function shareHoroscope() {
const period = routes[index].key;
const horoscope = data[period];
await Share.share({
message: `${capitalize(sign)} ${period} horoscope: ${horoscope.prediction}\n\nGet your horoscope at MyAstrologyApp`,
title: `${capitalize(sign)} Horoscope`
});
}
const DailyScene = () => (
<HoroscopeContent
horoscope={data.daily}
showExtras={true}
/>
);
const WeeklyScene = () => (
<HoroscopeContent horoscope={data.weekly} />
);
const MonthlyScene = () => (
<HoroscopeContent horoscope={data.monthly} />
);
return (
<View style={styles.container}>
<TabView
navigationState={{ index, routes }}
renderScene={SceneMap({
daily: DailyScene,
weekly: WeeklyScene,
monthly: MonthlyScene
})}
onIndexChange={setIndex}
renderTabBar={props => (
<TabBar
{...props}
style={styles.tabBar}
indicatorStyle={styles.indicator}
labelStyle={styles.tabLabel}
/>
)}
/>
</View>
);
}
function HoroscopeContent({ horoscope, showExtras = false }) {
if (!horoscope) {
return <ActivityIndicator size="large" />;
}
return (
<ScrollView style={styles.content}>
<Text style={styles.prediction}>{horoscope.prediction}</Text>
{showExtras && (
<View style={styles.extras}>
<View style={styles.extraItem}>
<Text style={styles.extraLabel}>Lucky Number</Text>
<Text style={styles.extraValue}>{horoscope.luckyNumber}</Text>
</View>
<View style={styles.extraItem}>
<Text style={styles.extraLabel}>Lucky Color</Text>
<View style={[styles.colorDot, { backgroundColor: horoscope.luckyColor }]} />
</View>
<View style={styles.extraItem}>
<Text style={styles.extraLabel}>Mood</Text>
<Text style={styles.extraValue}>{horoscope.mood}</Text>
</View>
<View style={styles.extraItem}>
<Text style={styles.extraLabel}>Best Match</Text>
<Text style={styles.extraValue}>{horoscope.compatibility}</Text>
</View>
</View>
)}
</ScrollView>
);
}
Push Notifications
// server/services/notifications.ts
import * as admin from 'firebase-admin';
import { VedikaClient } from '@anthropic/vedika-sdk';
import cron from 'node-cron';
const vedika = new VedikaClient();
// Send morning horoscope at 7 AM user's local time
cron.schedule('0 * * * *', async () => {
// Get users where it's 7 AM in their timezone
const users = await db.query(`
SELECT id, fcm_token, zodiac_sign, timezone
FROM users
WHERE fcm_token IS NOT NULL
AND EXTRACT(HOUR FROM NOW() AT TIME ZONE timezone) = 7
`);
for (const user of users.rows) {
try {
const horoscope = await vedika.dailyHoroscope({
sign: user.zodiac_sign
});
await admin.messaging().send({
token: user.fcm_token,
notification: {
title: `Good morning! Your ${capitalize(user.zodiac_sign)} forecast`,
body: horoscope.prediction.slice(0, 100) + '...'
},
data: {
type: 'daily_horoscope',
sign: user.zodiac_sign
},
android: {
notification: {
icon: 'ic_star',
color: '#FFD700'
}
},
apns: {
payload: {
aps: {
badge: 1,
sound: 'default'
}
}
}
});
} catch (error) {
console.error(`Failed to send notification to ${user.id}:`, error);
}
}
});
// Client-side notification handling
// React Native
import messaging from '@react-native-firebase/messaging';
async function requestPermission() {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
const token = await messaging().getToken();
await fetch(`${API_URL}/user/fcm-token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token })
});
}
}
messaging().onMessage(async remoteMessage => {
// Handle foreground notification
Alert.alert(
remoteMessage.notification.title,
remoteMessage.notification.body
);
});
messaging().setBackgroundMessageHandler(async remoteMessage => {
// Handle background notification
console.log('Background message:', remoteMessage);
});
iOS Home Screen Widget
// HoroscopeWidget.swift
import WidgetKit
import SwiftUI
struct HoroscopeEntry: TimelineEntry {
let date: Date
let sign: String
let prediction: String
let luckyNumber: Int
}
struct Provider: TimelineProvider {
func getTimeline(in context: Context, completion: @escaping (Timeline<HoroscopeEntry>) -> Void) {
Task {
let sign = UserDefaults.standard.string(forKey: "userZodiacSign") ?? "aries"
let horoscope = try await fetchHoroscope(sign: sign)
let entry = HoroscopeEntry(
date: Date(),
sign: sign,
prediction: horoscope.prediction,
luckyNumber: horoscope.luckyNumber
)
// Refresh at midnight
let midnight = Calendar.current.startOfDay(for: Date().addingTimeInterval(86400))
let timeline = Timeline(entries: [entry], policy: .after(midnight))
completion(timeline)
}
}
}
struct HoroscopeWidgetView: View {
let entry: HoroscopeEntry
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(entry.sign)
.resizable()
.frame(width: 24, height: 24)
Text(entry.sign.capitalized)
.font(.headline)
}
Text(entry.prediction)
.font(.caption)
.lineLimit(3)
Spacer()
HStack {
Text("Lucky #")
.font(.caption2)
.foregroundColor(.secondary)
Text("\(entry.luckyNumber)")
.font(.caption)
.bold()
}
}
.padding()
}
}
@main
struct HoroscopeWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "HoroscopeWidget", provider: Provider()) { entry in
HoroscopeWidgetView(entry: entry)
}
.configurationDisplayName("Daily Horoscope")
.description("Your daily zodiac prediction")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
Monetization Strategies
Ad-Supported Free Tier
- - Daily horoscope with ads
- - Banner ads between sections
- - Rewarded video for weekly forecast
Premium Subscription
- - Ad-free experience
- - Personalized predictions
- - Weekly & monthly forecasts
- - Push notifications
Premium Reports
- - Yearly forecast: $9.99
- - Full birth chart: $19.99
- - Compatibility report: $14.99
AI Chat Credits
- - 5 free questions/month
- - $4.99 for 50 questions
- - Unlimited with subscription