Complete Tutorial • 4000+ Words • Beginner Friendly

Build a Complete Astrology App

Step-by-Step Guide with React, Python Flask, and Mobile Integration

What We\'re Building

We\'ll create a full-featured astrology app with these capabilities:

  • Birth chart calculator with planetary positions
  • Kundali matching for relationships
  • Daily horoscope generator
  • AI-powered astrology chatbot
  • User authentication and profile management
  • Mobile-responsive design

Tech Stack

Frontend

  • • React 18 / Next.js 14
  • • TailwindCSS for styling
  • • Axios for API calls
  • • React Query for caching

Backend

  • • Python Flask API
  • • SQLite/PostgreSQL
  • • JWT authentication
  • • Vedika API integration

Prerequisites

  • Basic knowledge of JavaScript/React
  • Basic Python experience
  • Node.js 18+ and Python 3.8+ installed
  • Code editor (VS Code recommended)
  • Vedika API key (get free at vedika.io/dashboard)

Step 1: Project Setup

Create Project Structure

# Create main project directory
mkdir astrology-app
cd astrology-app

# Create frontend (React)
npx create-next-app@latest frontend
cd frontend
npm install axios react-query @tanstack/react-query

# Create backend (Flask)
cd ..
mkdir backend
cd backend
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install flask flask-cors flask-jwt-extended requests python-dotenv

Environment Variables

Create .env files in both frontend and backend:

backend/.env:

VEDIKA_API_KEY=your_vedika_api_key_here
VEDIKA_BASE_URL=https://api.vedika.io
SECRET_KEY=your_secret_key_for_jwt
DATABASE_URL=sqlite:///astrology.db

frontend/.env.local:

NEXT_PUBLIC_API_URL=http://localhost:5000

Step 2: React Frontend

Create Birth Chart Component

frontend/components/BirthChartCalculator.jsx:

import { useState } from 'react';
import axios from 'axios';

export default function BirthChartCalculator() {
    const [formData, setFormData] = useState({
        name: '',
        date: '',
        time: '',
        city: ''
    });
    const [chart, setChart] = useState(null);
    const [loading, setLoading] = useState(false);

    const handleSubmit = async (e) => {
        e.preventDefault();
        setLoading(true);

        try {
            // Get coordinates from city name
            const geocode = await axios.get(
                `https://nominatim.openstreetmap.org/search?q=${formData.city}&format=json`
            );

            const location = geocode.data[0];

            // Call our backend API
            const response = await axios.post(
                `${process.env.NEXT_PUBLIC_API_URL}/api/birth-chart`,
                {
                    datetime: `${formData.date}T${formData.time}:00`,
                    latitude: parseFloat(location.lat),
                    longitude: parseFloat(location.lon),
                    timezone: '+05:30' // IST; derive the offset from the city for other places
                }
            );

            setChart(response.data);
        } catch (error) {
            console.error('Error:', error);
            alert('Failed to generate chart');
        } finally {
            setLoading(false);
        }
    };

    return (
        <div className="max-w-4xl mx-auto p-6">
            <h1 className="text-3xl font-bold mb-6">Birth Chart Calculator</h1>

            <form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow mb-6">
                <div className="grid md:grid-cols-2 gap-4">
                    <div>
                        <label className="block mb-2 font-semibold">Name</label>
                        <input
                            type="text"
                            value={formData.name}
                            onChange={(e) => setFormData({...formData, name: e.target.value})}
                            className="w-full border p-2 rounded"
                            required
                        />
                    </div>

                    <div>
                        <label className="block mb-2 font-semibold">Birth Date</label>
                        <input
                            type="date"
                            value={formData.date}
                            onChange={(e) => setFormData({...formData, date: e.target.value})}
                            className="w-full border p-2 rounded"
                            required
                        />
                    </div>

                    <div>
                        <label className="block mb-2 font-semibold">Birth Time</label>
                        <input
                            type="time"
                            value={formData.time}
                            onChange={(e) => setFormData({...formData, time: e.target.value})}
                            className="w-full border p-2 rounded"
                            required
                        />
                    </div>

                    <div>
                        <label className="block mb-2 font-semibold">Birth City</label>
                        <input
                            type="text"
                            value={formData.city}
                            onChange={(e) => setFormData({...formData, city: e.target.value})}
                            className="w-full border p-2 rounded"
                            placeholder="New Delhi, India"
                            required
                        />
                    </div>
                </div>

                <button
                    type="submit"
                    className="mt-4 bg-indigo-600 text-white px-6 py-3 rounded-lg hover:bg-indigo-700 font-semibold"
                    disabled={loading}
                >
                    {loading ? 'Calculating...' : 'Generate Birth Chart'}
                </button>
            </form>

            {chart && (
                <div className="bg-white p-6 rounded-lg shadow">
                    <h2 className="text-2xl font-bold mb-4">Your Birth Chart</h2>

                    <div className="grid md:grid-cols-2 gap-6">
                        <div>
                            <h3 className="font-bold mb-2 text-lg">Planetary Positions</h3>
                            <ul className="space-y-2">
                                {chart.planets.map((planet) => (
                                    <li key={planet.name} className="flex justify-between">
                                        <span className="font-semibold">{planet.name}:</span>
                                        <span>{planet.sign} ({planet.degree.toFixed(2)}°)</span>
                                    </li>
                                ))}
                            </ul>
                        </div>

                        <div>
                            <h3 className="font-bold mb-2 text-lg">Houses</h3>
                            <ul className="space-y-2">
                                {chart.houses.slice(0, 4).map((house) => (
                                    <li key={house.number} className="flex justify-between">
                                        <span className="font-semibold">House {house.number}:</span>
                                        <span>{house.sign}</span>
                                    </li>
                                ))}
                            </ul>
                        </div>
                    </div>

                    <div className="mt-6">
                        <h3 className="font-bold mb-2 text-lg">Signs</h3>
                        <div className="bg-yellow-50 p-3 rounded">
                            <p>Ascendant: {chart.ascendant.sign}</p>
                            <p>Sun sign: {chart.sunSign}</p>
                            <p>Moon sign: {chart.moonSign}</p>
                            <p>Moon nakshatra: {chart.nakshatra}</p>
                        </div>
                    </div>
                </div>
            )}
        </div>
    );
}

Create AI Chatbot Component

frontend/components/AstrologyChatbot.jsx:

import { useState, useRef, useEffect } from 'react';
import axios from 'axios';

export default function AstrologyChatbot({ birthData }) {
    const [messages, setMessages] = useState([
        { role: 'assistant', content: 'Ask me anything about your birth chart!' }
    ]);
    const [input, setInput] = useState('');
    const [loading, setLoading] = useState(false);
    const messagesEnd = useRef(null);

    const handleSend = async () => {
        if (!input.trim()) return;

        const userMessage = { role: 'user', content: input };
        setMessages([...messages, userMessage]);
        setInput('');
        setLoading(true);

        try {
            const response = await axios.post(
                `${process.env.NEXT_PUBLIC_API_URL}/api/ai-chat`,
                {
                    birth_data: birthData,
                    question: input
                }
            );

            const aiMessage = {
                role: 'assistant',
                content: response.data.response
            };

            setMessages(prev => [...prev, aiMessage]);
        } catch (error) {
            console.error('Error:', error);
            setMessages(prev => [...prev, {
                role: 'assistant',
                content: 'Sorry, I encountered an error. Please try again.'
            }]);
        } finally {
            setLoading(false);
        }
    };

    useEffect(() => {
        messagesEnd.current?.scrollIntoView({ behavior: 'smooth' });
    }, [messages]);

    return (
        <div className="bg-white rounded-lg shadow p-4 max-w-2xl mx-auto">
            <h2 className="text-2xl font-bold mb-4">AI Astrology Assistant</h2>

            <div className="h-96 overflow-y-auto mb-4 space-y-3">
                {messages.map((msg, idx) => (
                    <div
                        key={idx}
                        className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
                    >
                        <div
                            className={`max-w-xs md:max-w-md p-3 rounded-lg ${
                                msg.role === 'user'
                                    ? 'bg-indigo-600 text-white'
                                    : 'bg-gray-100 text-gray-900'
                            }`}
                        >
                            {msg.content}
                        </div>
                    </div>
                ))}
                <div ref={messagesEnd} />
            </div>

            <div className="flex gap-2">
                <input
                    type="text"
                    value={input}
                    onChange={(e) => setInput(e.target.value)}
                    onKeyPress={(e) => e.key === 'Enter' && handleSend()}
                    placeholder="Ask: When will I get married?"
                    className="flex-1 border p-2 rounded"
                    disabled={loading}
                />
                <button
                    onClick={handleSend}
                    className="bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700"
                    disabled={loading}
                >
                    {loading ? '...' : 'Send'}
                </button>
            </div>
        </div>
    );
}

Step 3: Flask Backend

Create Main API Server

backend/app.py:

from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
import os
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
CORS(app)

VEDIKA_API_KEY = os.getenv('VEDIKA_API_KEY')
VEDIKA_BASE_URL = os.getenv('VEDIKA_BASE_URL')

@app.route('/api/birth-chart', methods=['POST'])
def birth_chart():
    """Generate birth chart"""
    data = request.json

    try:
        # Call Vedika API
        response = requests.post(
            f'{VEDIKA_BASE_URL}/v2/astrology/birth-chart',
            json=data,
            headers={
                'Authorization': f'Bearer {VEDIKA_API_KEY}',
                'Content-Type': 'application/json'
            }
        )

        response.raise_for_status()
        return jsonify(response.json()['data'])

    except requests.exceptions.RequestException as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/kundali-matching', methods=['POST'])
def kundali_matching():
    """Match two kundalis"""
    data = request.json

    try:
        response = requests.post(
            f'{VEDIKA_BASE_URL}/v2/astrology/kundali-matching',
            json=data,
            headers={
                'Authorization': f'Bearer {VEDIKA_API_KEY}',
                'Content-Type': 'application/json'
            }
        )

        response.raise_for_status()
        return jsonify(response.json()['data'])

    except requests.exceptions.RequestException as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/ai-chat', methods=['POST'])
def ai_chat():
    """AI chatbot for astrology queries"""
    data = request.json

    try:
        response = requests.post(
            f'{VEDIKA_BASE_URL}/api/v1/astrology/query',
            json={'question': data['question'], 'birthDetails': data['birth_data']},
            headers={
                'Authorization': f'Bearer {VEDIKA_API_KEY}',
                'Content-Type': 'application/json'
            }
        )

        response.raise_for_status()
        return jsonify(response.json())

    except requests.exceptions.RequestException as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/daily-horoscope/<sign>', methods=['GET'])
def daily_horoscope(sign):
    """Get daily horoscope for zodiac sign"""
    try:
        response = requests.get(
            f'{VEDIKA_BASE_URL}/v2/astrology/horoscope/{sign}',
            headers={
                'Authorization': f'Bearer {VEDIKA_API_KEY}',
                'Content-Type': 'application/json'
            }
        )

        response.raise_for_status()
        return jsonify(response.json()['data'])

    except requests.exceptions.RequestException as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Step 4: Mobile Integration

React Native Version

For mobile apps, use React Native with similar components:

npx react-native init AstrologyAppMobile
cd AstrologyAppMobile
npm install axios @react-navigation/native

Flutter Alternative

// lib/services/vedika_api.dart
import 'package:http/http.dart' as http;
import 'dart:convert';

class VedikaAPI {
  final String apiKey;
  final String baseUrl = 'https://api.vedika.io';

  VedikaAPI(this.apiKey);

  Future<Map<String, dynamic>> getBirthChart(Map<String, dynamic> birthData) async {
    final response = await http.post(
      Uri.parse('$baseUrl/v2/astrology/birth-chart'),
      headers: {
        'Authorization': 'Bearer $apiKey',
        'Content-Type': 'application/json'
      },
      body: jsonEncode(birthData)
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body)['data'];
    } else {
      throw Exception('Failed to load birth chart');
    }
  }
}

Step 5: Deployment

Deploy Frontend to Vercel

# Install Vercel CLI
npm i -g vercel

# Deploy
cd frontend
vercel --prod

Deploy Backend to Heroku

# Create Procfile
echo "web: gunicorn app:app" > Procfile

# Install gunicorn
pip install gunicorn
pip freeze > requirements.txt

# Deploy to Heroku
heroku create astrology-api-backend
git init
git add .
git commit -m "Initial commit"
heroku git:remote -a astrology-api-backend
git push heroku main

Production Best Practices

  • Use environment variables for API keys
  • Implement rate limiting
  • Add caching layer (Redis)
  • Set up monitoring (Sentry)
  • Use CDN for static assets
  • Implement user authentication
  • Add analytics tracking

Ready to Build Your App?

FREE Sandbox for development. Plans from $12/month