Build an Insight App in Node.js: Astrology, Vedic, Tarot APIs (2026)

12 min read
Brett Calloway
developerTypeScriptNode.jsAstrology APIVedic AstrologyTarot APISDKnpm

Node.js tutorial to build an astrology app covering Western charts, Vedic kundli, panchang, and tarot with the Roxy TypeScript SDK. Typed, Promise-based, by RoxyAPI.

TL;DR

  • One TypeScript SDK covers Western astrology, Vedic kundli, panchang, tarot, numerology, biorhythm, I Ching, crystals, dreams, and angel numbers.
  • Install with npm install @roxyapi/sdk or bun add @roxyapi/sdk. Every method returns { data, error, response } so you never throw in a hot request path.
  • Geocode the birth city with roxy.location.searchCities first. Pass the returned utcOffset (a decimal) as the timezone number on chart endpoints.
  • Build a daily horoscope, a Vedic birth chart, and a tarot spread in under 30 minutes with @roxyapi/sdk.

About the author: Brett Calloway is a Developer Advocate and AI Integration Specialist with 12 years of experience building APIs and developer tooling. He has led developer relations at two Series B SaaS companies and spoken at PyCon and JSConf on building context-rich AI agents using Model Context Protocol.

Why Node.js developers stall when building insight apps

Every astrology, tarot, or numerology side project starts with the same blocker: the math. Natal chart calculations need an ephemeris verified against authoritative sources. Vedic panchang needs sunrise and tithi math. KP needs sub-lord theory. Tarot needs a seeded RNG that produces the same card per user per day. Most Node.js developers spend a weekend piecing together unrelated npm packages, a tarot deck JSON, and three timezone workarounds, then ship a broken V1. The problem is not Node, the problem is stitching four single-purpose libraries together and reinventing every interpretation string yourself. The Roxy TypeScript SDK collapses ten spiritual-data domains behind one import. Calculations are verified against NASA JPL Horizons, interpretations ship in eight languages. This post walks through the three patterns that matter: geocode, compute, interpret.

Install and authenticate in two minutes

Pick your package manager. The SDK is pure TypeScript with zero runtime dependencies beyond the built-in fetch, so it installs in seconds on Node 20+, Bun, Deno, Cloudflare Workers, and Vercel Edge.

# npm
npm install @roxyapi/sdk

# or bun (recommended for monorepos)
bun add @roxyapi/sdk

# pnpm / yarn also work
pnpm add @roxyapi/sdk
yarn add @roxyapi/sdk

Get an API key at pricing. Starter is $39 a month for 5,000 requests across every domain with one key. Every endpoint uses the same X-API-Key header, and createRoxy injects it automatically.

import { createRoxy } from '@roxyapi/sdk';

const roxy = createRoxy(process.env.ROXY_API_KEY!);

// A first call. Typed response, no manual JSON parsing.
const { data } = await roxy.astrology.getDailyHoroscope({
  path: { sign: 'aries' },
});
console.log(data?.overview);

The SDK sends X-SDK-Client: roxy-sdk-typescript/1.x.x on every request so you can track SDK usage in your own logs. Every response is fully typed, your IDE shows request params and response fields without opening the docs.

Geocode the birth city before any chart call

Chart endpoints (natal, Vedic, synastry, panchang, KP, transits, compatibility) need latitude, longitude, and timezone as a decimal number. Never ask the user to type coordinates. Geocode the city first, then pass the result into the chart method.

The location response is a paginated envelope. Each city has two timezone fields: timezone is the IANA identifier (Asia/Kolkata) for your date library, and utcOffset is the DST-adjusted decimal (5.5 for IST, -5 for EST, 9 for JST). Pass utcOffset to chart endpoints.

const { data } = await roxy.location.searchCities({
  query: { q: 'Mumbai, India' },
});

// data = { total, limit, offset, cities: [{
//   city: 'Mumbai', latitude: 19.017, longitude: 72.857,
//   timezone: 'Asia/Kolkata', utcOffset: 5.5, ...
// }] }

const { latitude, longitude, utcOffset } = data!.cities[0];

const { data: natal } = await roxy.astrology.generateNatalChart({
  body: {
    date: '1990-01-15',
    time: '14:30:00',
    latitude,
    longitude,
    timezone: utcOffset,
  },
});

Passing the IANA string instead of the decimal returns validation_error: expected number, received string. This is the single most common mistake and the SDK AGENTS.md flags it in red for AI coding agents.

Build a daily horoscope endpoint in three lines

The daily horoscope endpoint is the cheapest hook in any consumer app. It drives daily active users, push notifications, and streaks. One GET call, no body, no coordinates.

const { data } = await roxy.astrology.getDailyHoroscope({
  path: { sign: 'scorpio' },
});

// data is typed, every field autocompletes in your IDE
console.log(data!.overview);         // General daily forecast, ~230 chars
console.log(data!.love);              // Love and relationship forecast
console.log(data!.career);            // Career outlook
console.log(data!.health);            // Health advice
console.log(data!.finance);           // Finance outlook
console.log(data!.luckyNumber);       // number
console.log(data!.luckyColor);        // string
console.log(data!.compatibleSigns);   // string[]
console.log(data!.moonPhase);         // Current moon phase name
console.log(data!.energyRating);      // 1 to 10

Weekly (getWeeklyHoroscope) and monthly (getMonthlyHoroscope) variants return the same shape plus luckyDays[] and keyDates[]. For caching, the horoscope changes at midnight UTC, so a 1-day TTL works. See the Western astrology reference for every field.

Ready to ship? Get your API key and drop a daily-horoscope card into your app this afternoon.

Generate a Vedic kundli and panchang

Vedic is where the depth shows. The kundli endpoint returns all twelve rashi houses with the planets placed in each, a meta dict keyed by planet name, house interpretations, and combustion / planetary-war metadata.

const { data: kundli } = await roxy.vedicAstrology.generateBirthChart({
  body: {
    date: '1990-01-15',
    time: '14:30:00',
    latitude: 28.6139,
    longitude: 77.209,
    timezone: 5.5,
  },
});

// Iterate rashis for the 12-sign chart view
const rashis = ['aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo',
                'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces'] as const;

for (const rashi of rashis) {
  const signs = kundli![rashi].signs; // planets in this rashi (can be empty)
  if (signs.length > 0) {
    console.log(rashi, signs.map(p => p.graha));
  }
}

// Or look up any planet directly via the meta lookup
console.log(kundli!.meta.Sun.rashi);            // e.g. "Capricorn"
console.log(kundli!.meta.Moon.nakshatra.name);   // e.g. "Uttara Ashadha"

Panchang is the other India-market staple. One call returns tithi, nakshatra, yoga, karana, rahu kaal, abhijit muhurta, gulika, and brahma muhurta for any date and location. See the Vedic astrology API product page for the full endpoint list.

Cast a tarot spread with a deterministic seed

Tarot readings need a seeded RNG so the same user gets the same card on the same day. The SDK handles the seed automatically. Pass a user ID or session ID and the card is stable until tomorrow.

// Daily card, same answer for this user until midnight
const { data: card } = await roxy.tarot.getDailyCard({ body: { seed: 'user-42' } });
console.log(card!.card.name);          // e.g. "The Star"
console.log(card!.card.imageUrl);      // Full-res card art URL
console.log(card!.card.reversed);      // bool, reversed meaning applies
console.log(card!.dailyMessage);       // Contextual message for today

// Ten-position Celtic Cross for a specific question
const { data: reading } = await roxy.tarot.castCelticCross({
  body: { question: 'What should I focus on this quarter?', seed: 'user-42-q1' },
});
for (const p of reading!.positions) {
  console.log(p.position, p.name, p.card.name, '-', p.interpretation);
}

// Lightweight yes / no for an impulse feature
const { data: yn } = await roxy.tarot.castYesNo({ body: { question: 'Should I take the offer?' } });
console.log(yn!.answer);     // "Yes" | "No" | "Maybe"
console.log(yn!.strength);    // "Strong" | "Qualified"
console.log(yn!.card.name);

Card art ships as public HTTPS URLs, no signed-URL dance, safe to embed directly in React or Next.js Image components. The full tarot API reference has 10 endpoints including three-card, love spread, and the 78-card catalog.

Handle errors without throwing

The TypeScript SDK returns { data, error, response } from every method. data and error are mutually exclusive, so branches never overlap. No try / catch in your hot path, no surprise throws, just a tagged union you can narrow.

const { data, error } = await roxy.astrology.getDailyHoroscope({
  path: { sign: 'scorpio' },
});

if (error) {
  switch (error.code) {
    case 'validation_error':
      console.error('Bad input:', error.error);
      break;
    case 'rate_limit_exceeded':
      console.error('Monthly quota reached, retry on the 1st');
      break;
    case 'subscription_inactive':
    case 'invalid_api_key':
      console.error('Auth problem:', error.code);
      break;
    default:
      console.error('API error:', error.code, error.error);
  }
  return;
}

// data is now non-null, TypeScript narrows automatically
console.log(data.overview);

Validation errors include an issues[] array with the specific field path that failed. No more guessing which parameter your integration sent wrong. The canonical error code list lives on the SDK docs page.

Deploy with Next.js, Express, or Hono

The SDK works anywhere fetch exists: Node 20+, Bun, Deno, Cloudflare Workers, Vercel Edge, Next.js server actions, Express, Hono, Fastify, Koa. Zero framework lock-in.

// Next.js 15 App Router route handler
// app/api/horoscope/[sign]/route.ts
import { createRoxy } from '@roxyapi/sdk';

const roxy = createRoxy(process.env.ROXY_API_KEY!);

export async function GET(
  _req: Request,
  { params }: { params: Promise<{ sign: string }> },
) {
  const { sign } = await params;
  const { data, error } = await roxy.astrology.getDailyHoroscope({ path: { sign } });
  return error
    ? Response.json({ error: error.code }, { status: 400 })
    : Response.json(data);
}
// Hono on Cloudflare Workers or Bun
import { Hono } from 'hono';
import { createRoxy } from '@roxyapi/sdk';

const app = new Hono();

app.post('/natal', async (c) => {
  const { city, date, time } = await c.req.json();
  const roxy = createRoxy(c.env.ROXY_API_KEY);

  const { data: locations } = await roxy.location.searchCities({ query: { q: city } });
  const { latitude, longitude, utcOffset } = locations!.cities[0];

  const { data, error } = await roxy.astrology.generateNatalChart({
    body: { date, time, latitude, longitude, timezone: utcOffset },
  });
  return error ? c.json({ error }, 400) : c.json(data);
});

export default app;

The SDK uses the platform fetch directly, so connection reuse, HTTP/2, and edge-native streaming all work automatically. Benchmarks on a Hetzner CX23 show median end-to-end latency of 87 ms for the horoscope endpoint and 180 ms for a full natal chart, including the round trip from Frankfurt to the API.

Give your AI agents the same SDK your code uses

Node.js insight apps are a natural fit for Claude Code, Cursor, Windsurf, and the OpenAI Agents SDK because the data they return is structured JSON, not prose. The Roxy TypeScript SDK ships an AGENTS.md inside the npm package at node_modules/@roxyapi/sdk/AGENTS.md, so any AI coding agent that reads AGENTS.md (Claude Code, Cursor, Windsurf, Copilot, Codex, Gemini CLI) picks up the critical patterns automatically: geocode first, timezone is a decimal, location returns an envelope, method names come from operationId, not URL paths. Separately, each domain exposes a remote MCP server at https://roxyapi.com/mcp/{domain} (Streamable HTTP, no stdio, no self-hosting) so agents can call the API directly without glue code. Read the MCP setup guide to wire up Claude Desktop or Cursor in under five minutes.

Troubleshooting checklist for the first hour

These are the five issues every new integrator hits. Work through them once and you will not hit them again.

First, the timezone parameter must be a decimal number. "Asia/Kolkata" fails with validation_error: expected number, received string. Use city.utcOffset, not city.timezone. Second, time must include seconds in HH:MM:SS format, 14:30 fails. Third, parameters are structured: always { path: {...} }, { body: {...} }, or { query: {...} }, never flat. Fourth, location responses are paginated envelopes, always index data.cities[0], never data[0]. Fifth, do not expose ROXY_API_KEY client-side. Call from server components, route handlers, server actions, or edge functions only. If you hit invalid_api_key with a correct key, check for a trailing newline or stray whitespace in your env file.

Frequently Asked Questions

Q: What does the Roxy TypeScript SDK cover? A: Ten spiritual-data domains behind one key. Western astrology, Vedic or Jyotish, numerology, tarot, biorhythm, I Ching, crystals, dreams, angel numbers, and location. Plus usage stats. A single npm install @roxyapi/sdk gets every domain, no stitching needed.

Q: Is the SDK fully typed? A: Yes, every request body, path param, query param, and response shape is a TypeScript type generated from the OpenAPI spec. Your IDE autocompletes every domain, every method, every field. No manual type definitions, no @types/* dependency.

Q: Why does the timezone field on a city return Asia Kolkata instead of a number? A: The city timezone field is the IANA identifier for date libraries like Luxon and day.js. Chart endpoints need a decimal like 5.5. Use the utcOffset field on the same city (already DST-adjusted) and pass it as the timezone value on chart requests.

Q: Can I run the SDK on Cloudflare Workers or Vercel Edge? A: Yes. The SDK depends only on the platform fetch API, so it runs on Node 20+, Bun, Deno, Cloudflare Workers, Vercel Edge, and browser service workers (server-side only). No Node-specific polyfills required.

Q: How do I avoid hitting the monthly quota during development? A: Keep the API key out of browser-exposed code and cache deterministic responses locally. Daily horoscopes, tarot daily cards, and angel number lookups are stable within a day, so a 1-day cache is safe. Use the free roxy.usage.getUsageStats() call to monitor quota in CI or a status endpoint.

Conclusion

Node.js developers stall on insight apps because the math and the content are both hard. The Roxy TypeScript SDK removes both layers: one import, ten domains, typed responses, framework-agnostic. Start with the TypeScript SDK quickstart, then pick the domain your users want first. Pricing starts at $39 a month for 5,000 requests across every endpoint, so your V1 costs less than a domain name.