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
- PHP 8.2 or later with Composer
- Laravel 11.x
- Redis (local or cloud) for caching and queues
- Vedika API key — get yours here
- Basic Laravel knowledge (controllers, routes, Blade)
Step 1: Project Setup
composer create-project laravel/laravel astrology-app
cd astrology-app
composer install
cp .env.example .env
php artisan key:generate
Configure .env
APP_NAME="My Astrology App"
APP_ENV=production
APP_KEY=base64:...
VEDIKA_API_KEY=vk_live_your_key_here
VEDIKA_BASE_URL=https://api.vedika.io
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
return [
'vedika' => [
'api_key' => env('VEDIKA_API_KEY'),
'base_url' => env('VEDIKA_BASE_URL', 'https://api.vedika.io'),
'timeout' => 45,
'cache_chart' => 2592000,
'cache_horoscope' => 86400,
'cache_ai' => 3600,
],
];
Step 2: Create the Eloquent Model
php artisan make:model BirthProfile -m
public function up(): void
{
Schema::create('birth_profiles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('birth_datetime');
$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();
});
}
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);
}
public function toVedikaDatetime(): string
{
return $this->birth_datetime;
}
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:
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');
}
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();
}
);
}
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();
}
);
}
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();
}
);
}
}
use App\Services\VedikaService;
public function register(): void
{
$this->app->singleton(VedikaService::class);
}
Step 4: Build Controllers
php artisan make:controller BirthChartController
php artisan make:controller HoroscopeController
php artisan make:controller AIChatController
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);
}
return view('astrology.birth-chart', compact('chart', 'profile', 'error'));
}
}
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,
]);
}
}
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
use App\Http\Controllers\BirthChartController;
use App\Http\Controllers\HoroscopeController;
use App\Http\Controllers\AIChatController;
Route::get('/', [HoroscopeController::class, 'index'])->name('home');
Route::get('/horoscope', [HoroscopeController::class, 'index'])->name('horoscope');
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
@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) }}°</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:
php artisan make:job GenerateBirthChartJob
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
{
$vedika->getBirthChart(
datetime: $this->profile->toVedikaDatetime(),
latitude: (float) $this->profile->latitude,
longitude: (float) $this->profile->longitude,
timezone: $this->profile->timezone_offset,
);
}
}
Running the Queue Worker
php artisan queue:work --tries=3 --timeout=60
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:
php artisan make:command PrewarmHoroscopes
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::command('vedika:prewarm-horoscopes')->dailyAt('00:05');
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:
- Eloquent + migration: Birth profiles, saved charts, and subscription data modelled cleanly with zero raw SQL
- Http facade + VedikaService: Testable, injectable service class wrapping Guzzle — mock it in unit tests with
Http::fake()
- Cache::remember(): Deterministic birth chart data cached for 30 days — 1 API call serves unlimited page views for the same user
- Job queues: Async pre-warming keeps web responses fast. Users never wait 30 seconds for a birth chart
- Artisan commands: Scheduled horoscope pre-warming means zero API calls during peak traffic
Next steps:
- Get your Vedika API key at vedika.io/signup
- Run
composer create-project laravel/laravel astrology-app
- Add your API key to
.env and register the service in config/services.php
- Copy the VedikaService class and controllers into your app
- 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.