REST is great for Vedika API's 140+ individual endpoints. GraphQL is better for your clients. When you build a GraphQL layer on top of Vedika's REST API, mobile apps can request exactly the planet fields they render (not the full response), your API Explorer gains schema introspection and autocomplete, and DataLoader eliminates the N+1 query problem that makes horoscope dashboards slow.
This guide walks through building a production Apollo Server that wraps Vedika API's birth chart, horoscope, and AI query endpoints behind a clean GraphQL schema. We'll design the type system, write resolvers, add DataLoader for batching, and implement GraphQL subscriptions for real-time planetary transit alerts. The result is a flexible API layer that serves React, Vue, mobile apps, and AI agents from a single endpoint.
What You'll Build: An Apollo Server GraphQL gateway that wraps Vedika's REST endpoints. Queries for birth charts, dashas, horoscopes, and AI predictions. Mutations for AI chat. DataLoader for batching multi-sign horoscope requests. Subscriptions for daily horoscope pushes via WebSocket. Full TypeScript schema with introspection.
Why GraphQL for Astrology APIs?
No Over-fetching
Mobile renders only planet names? Request { planets { name sign } }. Full chart screen? Add degree dignity isRetrograde. One schema, every use case.
Schema Introspection
GraphQL Playground and IDE tools auto-discover all available queries. Your team gets autocomplete for every astrology field without reading docs.
DataLoader Batching
12 horoscope widgets on one page? DataLoader batches into a single Vedika API call. Reduces network overhead from O(n) to O(1).
Real-time Subscriptions
Push daily horoscope updates at midnight via WebSocket. Transit alerts when slow-moving planets change signs — without client polling.
Step 1: Project Setup
mkdir astrology-graphql && cd astrology-graphql
npm init -y
npm install @apollo/server graphql graphql-ws
npm install axios dataloader
npm install --save-dev typescript @types/node ts-node nodemon
npx tsc --init --moduleResolution node --target ES2020 --module CommonJS \
--outDir dist --rootDir src --strict
Step 2: GraphQL Schema Design
Design a type system that maps naturally to Vedika API's response structure. Key design decisions: separate VedicBirthChart and WesternNatalChart types (they have different fields), a reusable BirthInput input type, and enum types for signs to prevent invalid string inputs:
import { gql } from 'graphql-tag';
export const typeDefs = gql`
# ─── Input Types ───────────────────────────────────────────
input BirthInput {
datetime: String! # ISO 8601: "1990-06-15T10:30:00"
latitude: Float!
longitude: Float!
timezone: String! # UTC offset: "+05:30"
language: String # Default: "en" | Supports 30 languages
}
enum ZodiacSign {
ARIES TAURUS GEMINI CANCER LEO VIRGO
LIBRA SCORPIO SAGITTARIUS CAPRICORN AQUARIUS PISCES
}
enum HoroscopePeriod { DAILY WEEKLY MONTHLY YEARLY }
enum AstrologySystem { VEDIC WESTERN KP }
# ─── Planet Types ───────────────────────────────────────────
type Planet {
name: String!
sign: String!
house: Int!
degree: Float!
longitude: Float!
dignity: String # Exalted | Debilitated | Own | Moolatrikona | Neutral
isRetrograde: Boolean!
isCombust: Boolean!
nakshatra: String
nakshatraPada: Int
}
# ─── Birth Chart Types ──────────────────────────────────────
type VedicBirthChart {
ascendant: String!
ascendantDegree: Float!
planets: [Planet!]!
houseCusps: [Float!]!
yogas: [Yoga!]!
currentDasha: DashaPeriod
ayanamsa: Float!
}
type WesternNatalChart {
ascendant: String!
planets: [Planet!]!
houseCusps: [Float!]!
aspects: [Aspect!]!
}
type Yoga {
name: String!
isPresent: Boolean!
description: String
}
type DashaPeriod {
mahadasha: String!
mahadashaEnd: String!
antardasha: String!
antardashaEnd: String!
}
type Aspect {
planet1: String!
planet2: String!
type: String! # Conjunction | Trine | Square | Opposition | Sextile
orb: Float!
isApplying: Boolean!
}
# ─── Horoscope Types ────────────────────────────────────────
type Horoscope {
sign: String!
period: String!
prediction: String!
luckyNumber: Int
luckyColor: String
date: String!
}
# ─── AI Query Types ─────────────────────────────────────────
type AstrologyAnswer {
question: String!
answer: String!
system: String!
confidence: Float
}
# ─── Compatibility Types ────────────────────────────────────
type Compatibility {
score: Int!
category: String!
strengths: [String!]!
challenges: [String!]!
}
# ─── Root Operations ────────────────────────────────────────
type Query {
# Vedic birth chart (Swiss Ephemeris sidereal)
birthChart(birth: BirthInput!, system: AstrologySystem): VedicBirthChart!
# Western natal chart (tropical)
natalChart(birth: BirthInput!): WesternNatalChart!
# Horoscope for a zodiac sign
horoscope(sign: ZodiacSign!, period: HoroscopePeriod!): Horoscope!
# All 12 signs at once (DataLoader optimized)
allHoroscopes(period: HoroscopePeriod!): [Horoscope!]!
# Dasha periods for a birth chart
dashas(birth: BirthInput!): [DashaPeriod!]!
# Compatibility between two birth charts
compatibility(person1: BirthInput!, person2: BirthInput!): Compatibility!
}
type Mutation {
# AI-powered natural language astrology query
askAstrologer(
question: String!
birth: BirthInput!
system: AstrologySystem
): AstrologyAnswer!
}
type Subscription {
# Daily horoscope push at midnight
dailyHoroscope(sign: ZodiacSign!): Horoscope!
}
`;
Step 3: Apollo Server Setup
Configure Apollo Server 4 with the schema and a context function that injects the Vedika API client and DataLoader instances:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import axios from 'axios';
import { typeDefs } from './schema/typeDefs';
import { resolvers } from './resolvers';
import { createHoroscopeLoader } from './dataloaders/horoscopeLoader';
import { createBirthChartLoader } from './dataloaders/birthChartLoader';
const VEDIKA_BASE = 'https://api.vedika.io';
const VEDIKA_KEY = process.env.VEDIKA_API_KEY!;
export const vedikaClient = axios.create({
baseURL: VEDIKA_BASE,
headers: {
'X-API-Key': VEDIKA_KEY,
'Content-Type': 'application/json'
},
timeout: 30000
});
export type GraphQLContext = {
vedikaClient: typeof vedikaClient;
horoscopeLoader: ReturnType<typeof createHoroscopeLoader>;
birthChartLoader: ReturnType<typeof createBirthChartLoader>;
};
const server = new ApolloServer<GraphQLContext>({
typeDefs,
resolvers,
introspection: true,
formatError: (err) => {
console.error('GraphQL error:', err.message);
return { message: err.message, code: err.extensions?.code };
}
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async () => ({
vedikaClient,
horoscopeLoader: createHoroscopeLoader(vedikaClient),
birthChartLoader: createBirthChartLoader(vedikaClient)
})
});
console.log(`Astrology GraphQL server ready at ${url}`);
Step 4: Resolvers
Write resolvers that map GraphQL operations to Vedika API REST calls. The birth chart resolver calls /v2/astrology/birth-chart; the AI mutation calls the natural language endpoint:
import { GraphQLError } from 'graphql';
import type { GraphQLContext } from '../server';
export const resolvers = {
Query: {
birthChart: async (
_: unknown,
{ birth, system = 'VEDIC' }: { birth: BirthInput; system: string },
{ vedikaClient }: GraphQLContext
) => {
try {
const endpoint = system === 'WESTERN'
? '/v2/western/natal-chart'
: '/v2/astrology/birth-chart';
const { data } = await vedikaClient.post(endpoint, {
datetime: birth.datetime,
latitude: birth.latitude,
longitude: birth.longitude,
timezone: birth.timezone,
language: birth.language || 'en'
});
return {
ascendant: data.ascendant?.sign || data.lagna,
ascendantDegree: data.ascendant?.degree || 0,
planets: data.planets || [],
houseCusps: data.houseCusps || [],
yogas: data.yogas || [],
currentDasha: data.currentDasha,
ayanamsa: data.ayanamsa || 23.86
};
} catch (err: any) {
throw new GraphQLError(
err.response?.data?.error || 'Birth chart generation failed',
{ extensions: { code: 'UPSTREAM_ERROR', status: err.response?.status } }
);
}
},
horoscope: async (
_: unknown,
{ sign, period }: { sign: string; period: string },
{ horoscopeLoader }: GraphQLContext
) => {
return horoscopeLoader.load({ sign: sign.toLowerCase(), period: period.toLowerCase() });
},
allHoroscopes: async (
_: unknown,
{ period }: { period: string },
{ horoscopeLoader }: GraphQLContext
) => {
const SIGNS = [
'aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo',
'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces'
];
return Promise.all(
SIGNS.map(sign => horoscopeLoader.load({ sign, period: period.toLowerCase() }))
);
},
dashas: async (
_: unknown,
{ birth }: { birth: BirthInput },
{ vedikaClient }: GraphQLContext
) => {
const { data } = await vedikaClient.post('/v2/astrology/vimshottari-dasha', birth);
return data.dashas || [];
},
compatibility: async (
_: unknown,
{ person1, person2 }: { person1: BirthInput; person2: BirthInput },
{ vedikaClient }: GraphQLContext
) => {
const { data } = await vedikaClient.post('/v2/astrology/guna-milan', { person1, person2 });
return {
score: data.totalScore,
category: data.compatibility,
strengths: data.strengths || [],
challenges: data.challenges || []
};
}
},
Mutation: {
askAstrologer: async (
_: unknown,
{ question, birth, system = 'VEDIC' }: { question: string; birth: BirthInput; system: string },
{ vedikaClient }: GraphQLContext
) => {
const { data } = await vedikaClient.post('/api/v1/astrology/query', {
question,
birthDetails: {
datetime: birth.datetime,
latitude: birth.latitude,
longitude: birth.longitude,
timezone: birth.timezone
},
system: system.toLowerCase(),
language: birth.language || 'en'
});
return {
question,
answer: data.answer,
system: system.toLowerCase(),
confidence: data.confidence
};
}
}
};
Step 5: DataLoader for Batching Horoscope Requests
DataLoader is the most impactful optimization for astrology dashboards. A page showing all 12 signs' daily horoscopes without DataLoader makes 12 sequential API calls. With DataLoader, it makes one:
import DataLoader from 'dataloader';
import type { AxiosInstance } from 'axios';
type HoroscopeKey = { sign: string; period: string };
export function createHoroscopeLoader(vedikaClient: AxiosInstance) {
return new DataLoader<HoroscopeKey, any>(
async (keys) => {
const byPeriod = keys.reduce((acc, key) => {
(acc[key.period] = acc[key.period] || []).push(key.sign);
return acc;
}, {} as Record<string, string[]>);
const results: Map<string, any> = new Map();
await Promise.all(
Object.entries(byPeriod).map(async ([period, signs]) => {
const endpoint = `/v2/astrology/horoscope-${period}`;
try {
const { data } = await vedikaClient.post(endpoint, { signs });
const horoscopes = data.horoscopes || data;
(Array.isArray(horoscopes) ? horoscopes : [horoscopes]).forEach((h: any) => {
results.set(`${h.sign?.toLowerCase()}:${period}`, {
sign: h.sign,
period,
prediction: h.prediction || h.content,
luckyNumber: h.luckyNumber,
luckyColor: h.luckyColor,
date: new Date().toISOString().split('T')[0]
});
});
} catch (err: any) {
await Promise.all(signs.map(async (sign) => {
try {
const { data } = await vedikaClient.post(endpoint, { sign });
results.set(`${sign}:${period}`, data);
} catch { }
}));
}
})
);
return keys.map(key =>
results.get(`${key.sign}:${key.period}`) ||
new Error(`No horoscope for ${key.sign}/${key.period}`)
);
},
{
cacheKeyFn: (key) => `${key.sign}:${key.period}`
}
);
}
Step 6: GraphQL Subscriptions for Real-time Horoscopes
Add WebSocket subscriptions to push daily horoscope updates to connected clients at midnight:
import { PubSub } from 'graphql-subscriptions';
import { vedikaClient } from '../server';
export const pubsub = new PubSub();
const ZODIAC_SIGNS = [
'aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo',
'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces'
];
export async function publishDailyHoroscopes() {
console.log('Publishing daily horoscopes to all subscribers...');
const today = new Date().toISOString().split('T')[0];
await Promise.allSettled(
ZODIAC_SIGNS.map(async (sign) => {
try {
const { data } = await vedikaClient.post('/v2/astrology/horoscope-daily', { sign });
pubsub.publish(`DAILY_HOROSCOPE_${sign.toUpperCase()}`, {
dailyHoroscope: {
sign,
period: 'daily',
prediction: data.prediction,
luckyNumber: data.luckyNumber,
luckyColor: data.luckyColor,
date: today
}
});
} catch (err) {
console.error(`Failed to publish horoscope for ${sign}:`, err`);
}
})
);
}
const MIDNIGHT_IST_UTC_HOUR = 18;
const MIDNIGHT_IST_UTC_MINUTE = 30;
function scheduleNightly() {
const now = new Date();
const nextMidnight = new Date(now);
nextMidnight.setUTCHours(MIDNIGHT_IST_UTC_HOUR, MIDNIGHT_IST_UTC_MINUTE, 0, 0);
if (nextMidnight <= now) nextMidnight.setUTCDate(nextMidnight.getUTCDate() + 1);
const msUntilMidnight = nextMidnight.getTime() - now.getTime();
setTimeout(async () => {
await publishDailyHoroscopes();
scheduleNightly();
}, msUntilMidnight);
console.log(`Next horoscope publish in ${Math.round(msUntilMidnight / 60000)} minutes`);
}
scheduleNightly();
Build Your GraphQL Astrology Layer
Start with the free Vedika API sandbox — 65 mock endpoints for development, no auth required. Production plans from $12/month.
Full API Docs
View Pricing
Step 7: Example Client Queries
Once the server is running, clients can query exactly what they need. No more over-fetching:
query BirthChartPreview {
birthChart(birth: {
datetime: "1990-06-15T10:30:00"
latitude: 28.6139
longitude: 77.2090
timezone: "+05:30"
}) {
ascendant
planets {
name
sign
house
}
}
}
query BirthChartFull {
birthChart(birth: {
datetime: "1990-06-15T10:30:00"
latitude: 28.6139
longitude: 77.2090
timezone: "+05:30"
}) {
ascendant
ascendantDegree
ayanamsa
planets {
name sign house degree
dignity isRetrograde isCombust
nakshatra nakshatraPada
}
yogas { name isPresent description }
currentDasha {
mahadasha mahadashaEnd
antardasha antardashaEnd
}
}
}
query AllDailyHoroscopes {
allHoroscopes(period: DAILY) {
sign
prediction
luckyNumber
luckyColor
}
}
mutation AskAstrologer {
askAstrologer(
question: "What career direction suits me this year?"
birth: {
datetime: "1990-06-15T10:30:00"
latitude: 28.6139
longitude: 77.2090
timezone: "+05:30"
language: "en"
}
system: VEDIC
) {
answer
confidence
}
}
subscription AriesToday {
dailyHoroscope(sign: ARIES) {
prediction
luckyNumber
date
}
}
Why Vedika API as the Backend for GraphQL?
140+ Endpoints
Vedika's REST API covers every astrology calculation — birth charts, dashas, compatibility, muhurta, numerology, panchang. Your GraphQL schema grows as your app does.
Consistent JSON
Predictable response shapes across all endpoints simplify GraphQL resolver mapping. No surprise fields or missing keys.
AI Built-in
The askAstrologer mutation calls Vedika's AI endpoint — natural language queries, Swiss Ephemeris math, 30-language support. No separate NLP service.
$12/mo Starting
Starter plan at $12/month. Pay-per-use credits. No per-endpoint pricing surprises. DataLoader caching further reduces your spend.
Conclusion
A GraphQL layer on top of Vedika API gives your engineering team the flexibility of schema-driven development while Vedika handles the astronomical complexity. Key benefits in production:
- 60-80% payload reduction for mobile clients requesting only needed fields
- DataLoader batching cuts Vedika API calls by 80%+ on multi-sign pages
- Schema introspection eliminates "what does this endpoint return?" questions
- Single endpoint (
/graphql) replaces 140 REST URLs in client code
- Real-time subscriptions push horoscope updates without client polling
Next steps:
- Get your Vedika API key at vedika.io/dashboard
- Clone the complete Apollo Server template from our documentation
- Extend the schema with endpoints from the full API reference (panchang, muhurta, transit alerts)
- Add Redis PubSub to replace in-memory PubSub for multi-instance subscriptions
- Deploy the GraphQL server to Cloud Run, Lambda, or your preferred platform
Questions about the Vedika API schema? Our engineering team is available via our documentation portal.
About Vedika Intelligence: Vedika is the only B2B astrology API with AI-powered chatbot capabilities, serving production apps worldwide. Our platform provides 140+ endpoints with Swiss Ephemeris precision, supports 30 languages, and starts at $12/month. The world's first astrology MCP server enables AI agents to query planetary positions and predictions natively.