Build a GraphQL Wrapper for Vedika Astrology API

GraphQL Wrapper for Vedika Astrology API with Apollo Server
Published: March 13, 2026 | By Vedika Intelligence | Reading time: 15 minutes

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

# Initialize project mkdir astrology-graphql && cd astrology-graphql npm init -y # Apollo Server 4 + core dependencies npm install @apollo/server graphql graphql-ws npm install axios dataloader # Dev dependencies npm install --save-dev typescript @types/node ts-node nodemon # TypeScript config 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:

// src/schema/typeDefs.ts 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:

// src/server.ts 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!; // Reusable axios instance — initialized once, shared across requests 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, // Enable GraphQL Playground 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, // New DataLoader per request — prevents cross-request cache pollution 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:

// src/resolvers/index.ts 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' }); // Map Vedika API response to GraphQL type 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 ) => { // Use DataLoader for automatic batching 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' ]; // DataLoader batches all 12 into a single Vedika API call 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 ) => { // Vedika's AI handles 30 languages, natural language questions, Swiss Ephemeris math 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:

// src/dataloaders/horoscopeLoader.ts 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) => { // DataLoader collects all .load() calls in one event loop tick // keys = [{ sign: 'aries', period: 'daily' }, { sign: 'taurus', period: 'daily' }, ...] // Group by period (most requests will be same period) 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(); // One Vedika API call per period group await Promise.all( Object.entries(byPeriod).map(async ([period, signs]) => { const endpoint = `/v2/astrology/horoscope-${period}`; try { // Vedika API supports batch sign queries const { data } = await vedikaClient.post(endpoint, { signs }); const horoscopes = data.horoscopes || data; // Map responses back to sign keys (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) { // Fallback: individual calls for failed batch await Promise.all(signs.map(async (sign) => { try { const { data } = await vedikaClient.post(endpoint, { sign }); results.set(`${sign}:${period}`, data); } catch { /* swallow individual errors */ } })); } }) ); // Return results in same order as input keys return keys.map(key => results.get(`${key.sign}:${key.period}`) || new Error(`No horoscope for ${key.sign}/${key.period}`) ); }, { // Cache key function for deduplication 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:

// src/subscriptions/horoscopePublisher.ts 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' ]; // Call this at midnight IST via cron or setInterval 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 }); // Publish to sign-specific channel 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`); } }) ); } // Schedule at midnight IST (UTC+5:30 = 18:30 UTC) 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(); // Reschedule for next day }, msUntilMidnight); console.log(`Next horoscope publish in ${Math.round(msUntilMidnight / 60000)} minutes`); } scheduleNightly(); // In resolvers, add Subscription type: // Subscription: { // dailyHoroscope: { // subscribe: (_, { sign }) => // pubsub.asyncIterator([`DAILY_HOROSCOPE_${sign.toUpperCase()}`]) // } // }

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:

# Birth chart — mobile preview (minimal fields) query BirthChartPreview { birthChart(birth: { datetime: "1990-06-15T10:30:00" latitude: 28.6139 longitude: 77.2090 timezone: "+05:30" }) { ascendant planets { name sign house } } } # Full chart detail — desktop view 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 } } } # All 12 horoscopes (DataLoader batches into 1 Vedika API call) query AllDailyHoroscopes { allHoroscopes(period: DAILY) { sign prediction luckyNumber luckyColor } } # AI question — natural language 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 — real-time daily horoscope 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:

Next steps:

  1. Get your Vedika API key at vedika.io/dashboard
  2. Clone the complete Apollo Server template from our documentation
  3. Extend the schema with endpoints from the full API reference (panchang, muhurta, transit alerts)
  4. Add Redis PubSub to replace in-memory PubSub for multi-instance subscriptions
  5. 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.

Try the #1 Vedic Astrology API

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

Get Free API Key