@bonyanoss/bonyan-api
v1.0.2
Published
Official JavaScript and TypeScript SDK for Bonyan-API — Quran, Azkar, Hadith, Tafsir, Prayer times, Hijri, Qibla.
Maintainers
Readme
@bonyanoss/bonyan-api
Official JavaScript / TypeScript SDK for Bonyan-API — a unified API for Quran, Azkar, Hadith, Tafsir, Prayer times, Hijri calendar and Qibla direction, with automatic fallback between multiple upstream sources.
import { BonyanClient } from '@bonyanoss/bonyan-api';
const client = new BonyanClient();
const reciters = await client.reciters.list();
const fatiha = await client.surah.getById(1);
const morning = await client.azkar.getByCategory('أذكار الصباح');Table of contents
- Install
- Quick start
- Client options
- Resources
- Error handling
- Validation
- Retry and rate limiting
- Custom fetch / environments
- TypeScript
- Contributing
- License
Install
npm install @bonyanoss/bonyan-api
pnpm add @bonyanoss/bonyan-api
yarn add @bonyanoss/bonyan-apiRequirements: Node.js ≥ 20, or any modern browser. No runtime dependencies.
Quick start
import { BonyanClient } from '@bonyanoss/bonyan-api';
const client = new BonyanClient();
// Surahs
const surahs = await client.surah.list(); // 114 chapters
const fatiha = await client.surah.getById(1);
// Ayat
const verse = await client.ayat.getBySurah(2, 255); // Ayat al-Kursi
const search = await client.ayat.search('الرحمن', { limit: 20 });
console.log(search.total, search.results.length);
// Reciters
const reciter = await client.reciters.getById(1);
const audio = await client.reciters.getSurah(1, 1);
// Prayer times
const times = await client.prayer.getTimes({ city: 'Mecca', country: 'SA' });
// Health / metadata
const health = await client.health();
const readiness = await client.ready();Client options
import { BonyanClient } from '@bonyanoss/bonyan-api';
const client = new BonyanClient({
baseUrl: 'https://api.bonyanoss.org', // default
timeoutMs: 10_000, // default 10 s
retry: 3, // default 3
headers: { 'X-App-Id': 'my-app' }, // optional
userAgent: 'my-app/1.0', // optional
fetch: customFetch, // optional custom fetch
});| Option | Type | Default | Description |
| --- | --- | --- | --- |
| baseUrl | string | https://api.bonyanoss.org | Base URL of the API |
| timeoutMs | number | 10_000 | Per-request timeout in milliseconds |
| retry | number | 3 | Retry attempts on 5xx / 429 / network errors |
| headers | Record<string, string> | — | Extra headers applied to every request |
| userAgent | string | — | Adds a User-Agent header |
| fetch | typeof fetch | globalThis.fetch | Override the fetch implementation |
Resources
Every resource is reachable from the client instance. All methods validate their arguments before hitting the network — invalid input throws ValidationError.
reciters
| Method | Endpoint |
| --- | --- |
| client.reciters.list() | GET /reciters |
| client.reciters.getById(id) | GET /reciters/:id |
| client.reciters.search(name) | GET /reciters/search?name=… |
| client.reciters.getSurah(reciterId, surah) | GET /reciters/:id/surah/:surah |
const all = await client.reciters.list();
const reciter = await client.reciters.getById(1);
const matches = await client.reciters.search('العفاسي');
const audio = await client.reciters.getSurah(1, 1);surah
| Method | Endpoint |
| --- | --- |
| client.surah.list() | GET /surah |
| client.surah.getById(id) | GET /surah/:id |
| client.surah.search(name) | GET /surah/search?name=… |
ayat
| Method | Endpoint |
| --- | --- |
| client.ayat.list() | GET /ayat (heavy response — every ayah of every surah) |
| client.ayat.getById(id) | GET /ayat/:id — global id (1..6236) |
| client.ayat.getBySurah(surah, aya) | GET /ayat/:surah/aya/:aya — surah (1..114) + aya (1..286) |
| client.ayat.search(text, { limit }) | GET /ayat/search?text=…&limit=… (max 500) |
const { total, results } = await client.ayat.search('الرحمن', { limit: 50 });azkar
| Method | Endpoint |
| --- | --- |
| client.azkar.listCategories() | GET /azkar |
| client.azkar.getByCategory(category) | GET /azkar/:category |
| client.azkar.search(text, { limit }) | GET /azkar/search?text=…&limit=… |
| client.azkar.random() | GET /azkar/random |
tafsir
| Method | Endpoint |
| --- | --- |
| client.tafsir.listEditions() | GET /tafsir |
| client.tafsir.forSurah(edition, surah, { aya }) | GET /tafsir/:edition/:surah |
| client.tafsir.forAya(edition, surah, aya) | GET /tafsir/:edition/:surah/:aya |
hadith
| Method | Endpoint |
| --- | --- |
| client.hadith.listBooks() | GET /hadith |
| client.hadith.getBook(bookId, { from, to }) | GET /hadith/:book |
| client.hadith.getByNumber(bookId, number) | GET /hadith/:book/:number |
| client.hadith.random({ book }) | GET /hadith/random |
The from/to range is capped at 300 items per request.
prayer
| Method | Endpoint |
| --- | --- |
| client.prayer.getTimes(options) | GET /prayer/times |
options requires either latitude + longitude, or city + country. Optional: date (DD-MM-YYYY), method (calculation method).
hijri
| Method | Endpoint |
| --- | --- |
| client.hijri.today() | GET /hijri/today |
| client.hijri.fromGregorian(date?) | GET /hijri/from-gregorian?date=DD-MM-YYYY |
| client.hijri.toGregorian(date) | GET /hijri/to-gregorian?date=DD-MM-YYYY |
qibla
| Method | Endpoint |
| --- | --- |
| client.qibla.getDirection(latitude, longitude) | GET /qibla?latitude=…&longitude=… |
meta
| Method | Endpoint |
| --- | --- |
| client.health() | GET /health - returns { status, code, timestamp } (no envelope) |
| client.ready() | GET /ready - returns readiness plus cache stats |
| client.routes() | GET / - returns the API route catalogue |
| client.metrics() | GET /metrics - returns Prometheus metrics as text |
Error handling
The SDK throws three distinct error classes — handle each one by instanceof or the exported type guards.
import {
BonyanClient,
BonyanApiError,
BonyanRequestError,
ValidationError,
} from '@bonyanoss/bonyan-api';
const client = new BonyanClient();
try {
await client.reciters.getById(999_999);
} catch (error) {
if (error instanceof ValidationError) {
console.error('Invalid input on field:', error.field, error.message);
} else if (error instanceof BonyanApiError) {
// The API responded with a non-2xx
console.error(error.status, error.code, error.requestId, error.message);
} else if (error instanceof BonyanRequestError) {
// Network failure, timeout, DNS, …
console.error('Network error:', error.message);
}
}| Class | When | Notable fields |
| --- | --- | --- |
| ValidationError | Argument failed client-side validation (no network request was sent) | field |
| BonyanApiError | API returned a non-2xx response | status, code, requestId, retryAfterMs, body |
| BonyanRequestError | Network error, DNS, timeout, abort | cause |
Type guards: isBonyanApiError, isBonyanRequestError, isValidationError.
Validation
Every argument is checked before a request is sent:
surahmust be an integer in1..114- Global aya
id(used byayat.getById) must be in1..6236 - Per-surah
ayanumber (inayat.getBySurah,tafsir.forAya) must be in1..286 latitude∈[-90, 90],longitude∈[-180, 180]- Dates use the
DD-MM-YYYYformat - Search
limitis bounded per endpoint (200 forazkar, 500 forayat) - Hadith
from/torange is capped at 300
When validation fails, ValidationError is thrown synchronously — no HTTP request is made.
Retry and rate limiting
By default the SDK retries up to 3 times on:
5xxserver errors429 Too Many Requests- Network errors (DNS failure, socket reset, timeout)
Retry-After headers (in seconds or HTTP-date) are honored. Between attempts the SDK sleeps with exponential backoff + jitter (100ms * 2^attempt + random(0-100)).
Disable retries entirely:
const client = new BonyanClient({ retry: 0 });Custom fetch / environments
The SDK uses globalThis.fetch by default. To use it on a runtime without a global fetch, or with a custom HTTP stack:
import { BonyanClient } from '@bonyanoss/bonyan-api';
import { fetch as undiciFetch } from 'undici';
const client = new BonyanClient({ fetch: undiciFetch as typeof fetch });Works out of the box on:
- ✅ Node.js ≥ 20
- ✅ Browsers (Chrome, Firefox, Safari, Edge)
- ✅ Deno, Bun
- ✅ Cloudflare Workers, Vercel Edge, Netlify Edge
TypeScript
The SDK ships with strict type definitions — strict, exactOptionalPropertyTypes, noUncheckedIndexedAccess. Every public API has TSDoc comments visible in your editor.
import type {
Reciter,
Surah,
Aya,
AzkarItem,
HadithItem,
PrayerTimings,
HijriDate,
QiblaInfo,
} from '@bonyanoss/bonyan-api';Contributing
We :heart: pull requests! See CONTRIBUTING.md for the dev setup, code style, and how to add a new resource.
Quick start:
pnpm install
pnpm test
pnpm build