Build an Astrology App with Laravel & Vedika API

Build an Astrology App with Laravel and Vedika API
Published: March 13, 2026 | By Vedika Intelligence | Reading time: 15 minutes

Laravel powers more astrology websites than any other PHP framework. Its Eloquent ORM, Blade templating, job queues, and expressive Http client make building a horoscope SaaS product genuinely pleasant — and Vedika API gives you 140+ astrological calculations plus an AI chatbot behind a single REST endpoint.

In this tutorial you will build a production-ready Laravel astrology application from scratch. You'll integrate Vedika API using Laravel's built-in Http facade (Guzzle under the hood), store user birth profiles in Eloquent models, display charts in Blade templates, queue async chart generation jobs, and cache responses in Redis to minimize API costs.

What You'll Build: A Laravel astrology app with Eloquent birth profile models, a dedicated VedikaService class using Laravel Http, controllers for birth chart and horoscope pages, Blade templates, a queued job for async processing, and Redis caching that serves repeated chart requests instantly without hitting the API.

Why Laravel for Astrology Apps?

Eloquent ORM

Model user birth profiles, saved charts, and subscription tiers with clean, readable PHP. Migrations handle schema evolution safely.

Http Facade

Laravel's fluent Http client (wrapping Guzzle) makes Vedika API calls concise: Http::withToken($key)->post($url, $data).

Job Queues

Dispatch birth chart generation as a background job. Web requests return instantly; users get results when ready. No 30-second wait screens.

Cache Facade

Redis-backed Cache::remember() serves identical chart requests from cache. Birth chart data is deterministic — cache it indefinitely.

Prerequisites

Step 1: Project Setup

# Create new Laravel project composer create-project laravel/laravel astrology-app cd astrology-app # Install dependencies (Laravel already ships with Guzzle via Http facade) composer install # Set up environment cp .env.example .env php artisan key:generate

Configure .env

# .env — never commit this file APP_NAME="My Astrology App" APP_ENV=production APP_KEY=base64:... # Vedika API VEDIKA_API_KEY=vk_live_your_key_here VEDIKA_BASE_URL=https://api.vedika.io # Redis for caching and queues REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 CACHE_DRIVER=redis QUEUE_CONNECTION=redis SESSION_DRIVER=redis

Register in config/services.php

// config/services.php return [ // ... other services 'vedika' => [ 'api_key' => env('VEDIKA_API_KEY'), 'base_url' => env('VEDIKA_BASE_URL', 'https://api.vedika.io'), 'timeout' => 45, // AI endpoint can take up to 30s 'cache_chart' => 2592000, // 30 days (deterministic) 'cache_horoscope' => 86400, // 24 hours 'cache_ai' => 3600, // 1 hour ], ];

Step 2: Create the Eloquent Model

# Generate model with migration php artisan make:model BirthProfile -m
// database/migrations/xxxx_create_birth_profiles_table.php public function up(): void { Schema::create('birth_profiles', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->string('birth_datetime'); // ISO: 1990-06-15T14:30:00+05:30 $table->decimal('latitude', 9, 6); $table->decimal('longitude', 9, 6); $table->string('timezone_offset', 6)->default('+05:30'); $table->string('birth_city')->nullable(); $table->timestamps(); }); }
// app/Models/BirthProfile.php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class BirthProfile extends Model { protected $fillable = [ 'user_id', 'birth_datetime', 'latitude', 'longitude', 'timezone_offset', 'birth_city', ]; public function user(): BelongsTo { return $this->belongsTo(User::class); } /** * Format birth datetime as Vedika API expects it. * Combines date/time string with timezone offset. */ public function toVedikaDatetime(): string { return $this->birth_datetime; // Already stored in ISO 8601 with offset } /** * Generate a deterministic cache key for this birth location. * Same datetime + lat + lon always produces the same chart. */ public function chartCacheKey(): string { return 'chart:' . hash('sha256', $this->birth_datetime . $this->latitude . $this->longitude ); } }
php artisan migrate

Step 3: Build the VedikaService

Encapsulate all Vedika API calls in a service class. Register it in the service container so it's injectable into controllers and jobs:

// app/Services/VedikaService.php namespace App\Services; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Http\Client\RequestException; class VedikaService { private string $apiKey; private string $baseUrl; private int $timeout; public function __construct() { $this->apiKey = config('services.vedika.api_key'); $this->baseUrl = config('services.vedika.base_url'); $this->timeout = config('services.vedika.timeout'); } /** * Fetch a Vedic birth chart. Cached for 30 days — results are deterministic. */ public function getBirthChart( string $datetime, float $latitude, float $longitude, string $timezone = '+05:30' ): array { $cacheKey = 'vedika:chart:' . hash('sha256', "$datetime|$latitude|$longitude"); return Cache::remember( $cacheKey, config('services.vedika.cache_chart'), function () use ($datetime, $latitude, $longitude, $timezone) { return Http::withToken($this->apiKey) ->timeout($this->timeout) ->post($this->baseUrl . '/v2/astrology/birth-chart', [ 'datetime' => $datetime, 'latitude' => $latitude, 'longitude' => $longitude, 'timezone' => $timezone, ]) ->throw() ->json(); } ); } /** * Fetch a daily horoscope for a zodiac sign. Cached for 24 hours. */ public function getDailyHoroscope(string $sign): array { $today = now()->toDateString(); $cacheKey = "vedika:horoscope:{$sign}:{$today}"; return Cache::remember( $cacheKey, config('services.vedika.cache_horoscope'), function () use ($sign) { return Http::withToken($this->apiKey) ->timeout(15) ->get($this->baseUrl . "/v2/western/horoscope/{$sign}") ->throw() ->json(); } ); } /** * Ask Vedika AI a natural language astrology question. * Cached for 1 hour per unique question + birth data combination. */ public function queryAI( string $question, string $datetime, float $latitude, float $longitude, string $timezone = '+05:30', string $language = 'en' ): array { $cacheKey = 'vedika:ai:' . hash('sha256', "$question|$datetime|$latitude|$longitude|$language" ); return Cache::remember( $cacheKey, config('services.vedika.cache_ai'), function () use ($question, $datetime, $latitude, $longitude, $timezone, $language) { return Http::withToken($this->apiKey) ->timeout($this->timeout) ->post($this->baseUrl . '/api/v1/astrology/query', [ 'question' => $question, 'language' => $language, 'birthDetails' => [ 'datetime' => $datetime, 'latitude' => $latitude, 'longitude' => $longitude, 'timezone' => $timezone, ], ]) ->throw() ->json(); } ); } }
// app/Providers/AppServiceProvider.php — bind service as singleton use App\Services\VedikaService; public function register(): void { $this->app->singleton(VedikaService::class); }

Step 4: Build Controllers

# Generate controllers php artisan make:controller BirthChartController php artisan make:controller HoroscopeController php artisan make:controller AIChatController
// app/Http/Controllers/BirthChartController.php namespace App\Http\Controllers; use App\Services\VedikaService; use Illuminate\Http\Request; use Illuminate\Http\Client\RequestException; class BirthChartController extends Controller { public function __construct(private VedikaService $vedika) {} public function show(Request $request) { $profile = $request->user()->birthProfile; if (! $profile) { return redirect()->route('profile.setup') ->with('info', 'Please enter your birth details first.'); } $chart = null; $error = null; try { $chart = $this->vedika->getBirthChart( datetime: $profile->toVedikaDatetime(), latitude: (float) $profile->latitude, longitude: (float) $profile->longitude, timezone: $profile->timezone_offset, ); } catch (RequestException $e) { $error = 'Could not fetch your birth chart. Please try again.'; report($e); // Log to your error tracker } return view('astrology.birth-chart', compact('chart', 'profile', 'error')); } }
// app/Http/Controllers/HoroscopeController.php namespace App\Http\Controllers; use App\Services\VedikaService; use Illuminate\Http\Request; class HoroscopeController extends Controller { private const SIGNS = [ 'aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo', 'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces', ]; public function __construct(private VedikaService $vedika) {} public function index(Request $request) { $sign = strtolower($request->query('sign', 'aries')); if (! in_array($sign, self::SIGNS)) { $sign = 'aries'; } $horoscope = null; $error = null; try { $horoscope = $this->vedika->getDailyHoroscope($sign); } catch (\Exception $e) { $error = 'Daily horoscope temporarily unavailable.'; } return view('astrology.horoscope', [ 'horoscope' => $horoscope, 'selectedSign' => $sign, 'signs' => self::SIGNS, 'error' => $error, ]); } }
// app/Http/Controllers/AIChatController.php namespace App\Http\Controllers; use App\Services\VedikaService; use Illuminate\Http\Request; class AIChatController extends Controller { public function __construct(private VedikaService $vedika) {} public function show() { return view('astrology.ai-chat'); } public function ask(Request $request) { $validated = $request->validate([ 'question' => 'required|string|max:500', ]); $profile = $request->user()->birthProfile; if (! $profile) { return back()->with('error', 'Please save your birth details first.'); } try { $result = $this->vedika->queryAI( question: $validated['question'], datetime: $profile->toVedikaDatetime(), latitude: (float) $profile->latitude, longitude: (float) $profile->longitude, timezone: $profile->timezone_offset, ); return back()->with([ 'answer' => $result['answer'] ?? null, 'question' => $validated['question'], ]); } catch (\Exception $e) { return back()->with('error', 'Could not get an answer right now. Please try again.'); } } }

Step 5: Routes

// routes/web.php use App\Http\Controllers\BirthChartController; use App\Http\Controllers\HoroscopeController; use App\Http\Controllers\AIChatController; // Public routes Route::get('/', [HoroscopeController::class, 'index'])->name('home'); Route::get('/horoscope', [HoroscopeController::class, 'index'])->name('horoscope'); // Authenticated routes Route::middleware('auth')->group(function () { Route::get('/birth-chart', [BirthChartController::class, 'show'])->name('chart'); Route::get('/ask', [AIChatController::class, 'show'])->name('ai.chat'); Route::post('/ask', [AIChatController::class, 'ask'])->name('ai.ask'); });

Step 6: Blade Template

<!-- resources/views/astrology/birth-chart.blade.php --> @extends('layouts.app') @section('content') <div class="container"> <h1>Your Vedic Birth Chart</h1> @if(session('info')) <div class="alert alert-info">{{ session('info') }}</div> @endif @if($error) <div class="alert alert-danger">{{ $error }}</div> @elseif($chart) <div class="chart-header"> <span><strong>Ascendant:</strong> {{ $chart['ascendant'] ?? 'N/A' }}</span> <span><strong>Moon Sign:</strong> {{ $chart['moonSign'] ?? 'N/A' }}</span> <span><strong>Sun Sign:</strong> {{ $chart['sunSign'] ?? 'N/A' }}</span> </div> @if(!empty($chart['planets'])) <table> <thead> <tr> <th>Planet</th><th>Sign</th><th>House</th> <th>Degree</th><th>Dignity</th><th>Status</th> </tr> </thead> <tbody> @foreach($chart['planets'] as $planet) <tr> <td>{{ $planet['name'] }}</td> <td>{{ $planet['sign'] }}</td> <td>{{ $planet['house'] }}</td> <td>{{ number_format($planet['degree'], 2) }}&deg;</td> <td>{{ $planet['dignity'] }}</td> <td>{{ $planet['retrograde'] ? 'Retrograde' : 'Direct' }}</td> </tr> @endforeach </tbody> </table> @endif @if(!empty($chart['activeYogas'])) <h2>Active Yogas</h2> <ul> @foreach($chart['activeYogas'] as $yoga) <li>{{ $yoga }}</li> @endforeach </ul> @endif <!-- Ask Vedika AI --> <div class="ask-section"> <h2>Ask Vedika AI About Your Chart</h2> @if(session('answer')) <div class="ai-answer"> <p><em>You asked: {{ session('question') }}</em></p> <p>{{ session('answer') }}</p> </div> @endif <form method="POST" action="{{ route('ai.ask') }}"> @csrf <input type="text" name="question" placeholder="What does my current Mahadasha mean?" required class="question-input"> <button type="submit">Ask Vedika AI</button> </form> </div> @endif </div> @endsection

Step 7: Queued Job for Async Processing

For features where you want to pre-generate charts (e.g., after user signup), dispatch a queued job so the web request returns immediately:

# Generate job php artisan make:job GenerateBirthChartJob
// app/Jobs/GenerateBirthChartJob.php namespace App\Jobs; use App\Models\BirthProfile; use App\Services\VedikaService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; class GenerateBirthChartJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable; public int $tries = 3; public int $timeout = 60; public function __construct(private BirthProfile $profile) {} public function handle(VedikaService $vedika): void { // Pre-warm Redis cache — future page loads will be instant $vedika->getBirthChart( datetime: $this->profile->toVedikaDatetime(), latitude: (float) $this->profile->latitude, longitude: (float) $this->profile->longitude, timezone: $this->profile->timezone_offset, ); } } // Dispatch after user saves birth details: // GenerateBirthChartJob::dispatch($birthProfile);

Running the Queue Worker

# Run queue worker (development) php artisan queue:work --tries=3 --timeout=60 # Production: use Supervisor to keep worker running # Or use Laravel Horizon for Redis queue monitoring: composer require laravel/horizon php artisan horizon

Step 8: Artisan Command for Batch Horoscopes

Add an Artisan command to pre-generate and cache all 12 zodiac horoscopes each morning — useful for apps that display horoscopes on landing pages:

# Generate the command php artisan make:command PrewarmHoroscopes
// app/Console/Commands/PrewarmHoroscopes.php namespace App\Console\Commands; use App\Services\VedikaService; use Illuminate\Console\Command; class PrewarmHoroscopes extends Command { protected $signature = 'vedika:prewarm-horoscopes'; protected $description = 'Pre-fetch and cache today\'s horoscopes for all 12 signs'; private const SIGNS = [ 'aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo', 'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces', ]; public function handle(VedikaService $vedika): int { $this->info('Pre-warming horoscopes for all 12 signs...'); foreach (self::SIGNS as $sign) { try { $vedika->getDailyHoroscope($sign); $this->line(" ✓ $sign"); } catch (\Exception $e) { $this->error(" × $sign: {$e->getMessage()}"); } } $this->info('Done. All horoscopes cached.'); return Command::SUCCESS; } }
# Schedule it in bootstrap/app.php (Laravel 11+) Schedule::command('vedika:prewarm-horoscopes')->dailyAt('00:05'); # Run manually php artisan vedika:prewarm-horoscopes

Why Vedika API for Laravel Projects?

140+ Endpoints

Birth charts, planetary positions, yogas, doshas, dashas, panchang, numerology — all reachable through Laravel's fluent Http client in a single line.

AI Natural Language

One endpoint answers any astrology question. No need to build separate interfaces for each of the 140 calculation types.

Swiss Ephemeris Precision

Astronomical-grade calculations matching professional astrology software. Correct planetary positions users can trust.

30 Languages

Pass 'language' => 'hi' to get Hindi responses. Tamil, Telugu, Bengali, Gujarati, and 25 more languages supported natively.

Cache-Friendly

Birth chart data is deterministic — same inputs, same outputs forever. Cache aggressively with Cache::remember() and pay only for unique requests.

Starts at $12/month

Wallet-based credits with transparent per-query pricing. Starter plan covers most Laravel hobby projects and early SaaS products.

Conclusion

Laravel and Vedika API make a powerful combination for PHP astrology development. The architecture scales cleanly:

Next steps:

  1. Get your Vedika API key at vedika.io/signup
  2. Run composer create-project laravel/laravel astrology-app
  3. Add your API key to .env and register the service in config/services.php
  4. Copy the VedikaService class and controllers into your app
  5. Run php artisan queue:work to process async jobs

For complete endpoint documentation, request/response schemas, and sandbox testing without an API key, visit our developer docs or view pricing.


About Vedika Intelligence: Vedika is the only B2B astrology API with an AI-powered chatbot and 140+ calculation endpoints, serving production apps worldwide. Built on Swiss Ephemeris for astronomical precision, supporting Vedic, Western, and KP astrology across 30 languages. The world's first astrology MCP server.

Try the #1 Vedic Astrology API

140+ endpoints, 30 languages, Swiss Ephemeris precision. Free sandbox included — no credit card required.

Get Free API Key