Sebagai developer, terutama di komunitas Muslim, kita sering mencari cara untuk mengintegrasikan nilai-nilai Islami ke dalam teknologi. Salah satu kebutuhan paling umum adalah menampilkan waktu sholat yang akurat. Anda mungkin berpikir untuk menggunakan API eksternal seperti Aladhan, yang merupakan layanan fantastis. Tapi pernahkah Anda bertanya-tanya:
- "Bagaimana jika saya ingin full control atas datanya?"
- "Bagaimana jika saya khawatir tentang rate limiting atau biaya jangka panjang?"
- "Atau... bagaimana sebenarnya cara kerja 'mesin' di balik perhitungan waktu sholat?"
Jika ya, Anda berada di tempat yang tepat. Ini bukan sekadar "tutorial" biasa. Ini adalah panduan mendalam (deep dive) yang akan membedah proses reverse-engineering, perancangan arsitektur, dan implementasi end-to-end untuk membangun Prayer Time API Anda sendiri yang robust, skalabel, dan akurat menggunakan Node.js, Express, dan TypeScript.
Kita tidak hanya akan membuat klon; kita akan memahami mengapa setiap keputusan desain dibuat.
Apa yang Akan Anda Pelajari
- Analisis & Arsitektur: Membedah fungsionalitas Aladhan API dan merancang arsitektur sistem yang scalable (Layered Monolith).
- Ilmu di Balik Kode: Memahami prinsip astronomi dan fikih di balik perbedaan metode perhitungan (MWL, ISNA, Kemenag, dll).
- Core Implementation: Menulis kode TypeScript yang type-safe untuk calculation engine, endpoint API, dan integrasi kalender Hijriah.
- Advanced Features: Mengimplementasikan kalkulator arah Kiblat (Qibla) dan menangani high-latitude adjustments.
- Production-Ready: Menerapkan caching dengan Redis, testing (Jest & Supertest), dan praktik deployment yang aman.
Siap untuk membangun sesuatu yang bermakna? Mari kita mulai.
Daftar Isi
Latar Belakang: Mengapa Membangun Prayer Time API Sendiri?
Di era digital, aplikasi Islami telah menjamur. Dari aplikasi alarm adzan hingga lifestyle app yang lengkap, fitur waktu sholat adalah intinya. Sebagian besar developer mengandalkan API pihak ketiga, dan Aladhan API telah menjadi standar de-facto karena kemudahan penggunaan dan kelengkapan fiturnya.
Namun, ketergantungan memiliki konsekuensi:
- Biaya & Rate Limiting: Seiring pertumbuhan aplikasi Anda, biaya API call bisa membengkak. Anda juga terikat pada rate limit yang dapat memengaruhi performa.
- Kustomisasi: Bagaimana jika Anda ingin menambahkan metode perhitungan kustom untuk komunitas lokal Anda? Atau mengintegrasikan tuning spesifik? Anda terbatas oleh apa yang disediakan API.
- Ketersediaan (Availability): Jika API pihak ketiga down, fitur inti aplikasi Anda juga down.
- Learning: Membangunnya sendiri adalah kesempatan luar biasa untuk belajar tentang astronomi, fiqh (yurisprudensi Islam), dan arsitektur backend yang kompleks.
Membangun Prayer Time API sendiri memberi Anda kedaulatan data dan kontrol penuh atas fungsionalitas, performa, dan biaya.
Tantangan Perhitungan Waktu Sholat
Mengapa ini sulit? Bukankah hanya tabel waktu yang statis? Tidak sama sekali.
Perhitungan waktu sholat pada intinya adalah masalah astronomi. Setiap waktu (Fajr, Dhuhr, Asr, Maghrib, Isha) didefinisikan oleh posisi matahari di langit.
- Fajr (Subuh): Dimulai saat fajar astronomis, ketika matahari berada pada sudut tertentu di bawah ufuk (misalnya, 18° untuk MWL, 20° untuk Kemenag).
- Dhuhr (Zuhur): Tepat setelah matahari melewati titik tertingginya (zenith).
- Asr (Asar): Ketika panjang bayangan objek mencapai rasio tertentu (1x atau 2x tinggi objek).
- Maghrib: Tepat setelah matahari terbenam (pusat matahari di 0.833° di bawah ufuk).
- Isha (Isya): Saat senja berakhir, ketika matahari mencapai sudut tertentu di bawah ufuk (misalnya, 17° untuk MWL, 18° untuk Kemenag).
Kompleksitas muncul karena:
- Geografis: Lintang dan bujur lokasi Anda secara drastis mengubah sudut-sudut ini.
- Metode Fikih: Otoritas agama yang berbeda (MWL, ISNA, Kemenag, Jakim, dll.) memiliki standar sudut yang berbeda untuk Fajr dan Isha.
- Mazhab: Perhitungan Asar berbeda antara standar (Jumhur/Shafi'i) dan Hanafi.
- Lintang Tinggi: Di negara-negara dekat kutub (misalnya, Norwegia, Swedia), matahari mungkin tidak pernah terbenam di musim panas atau tidak pernah terbit di musim dingin, sehingga perhitungan standar gagal total.
API kita harus mampu menangani semua kompleksitas ini dengan elegan.
Arsitektur Teknis dan Tech Stack Pilihan
Setelah menganalisis Aladhan API dan tantangan yang ada, kita akan merancang sistem monolitik berlapis (layered monolith). Mengapa monolit? Karena untuk layanan dengan domain yang terdefinisi dengan baik seperti ini, memulai dengan monolit jauh lebih sederhana untuk pengembangan dan deployment. Arsitektur yang bersih memungkinkan kita untuk mengekstrak layanan (menjadi microservices) nanti jika diperlukan.
[DIAGRAM 1: Architecture Overview]
Deskripsi Diagram: Sebuah flowchart yang menunjukkan alur permintaan dari Klien (Client) -> Lapisan Presentasi (Presentation Layer - Express Routes) -> Lapisan Logika Bisnis (Business Logic - Services) -> Lapisan Data (Data Layer - Database/Cache). Error Handling dan Middleware membungkus semua lapisan.
Tech Stack Pilihan
Komponen | Teknologi | Alasan Penggunaan |
Runtime | Node.js | Non-blocking I/O, ekosistem (NPM) yang kaya, performa cepat. |
Framework | Express.js | Minimalis, battle-tested, dan standar industri untuk API di Node.js. |
Bahasa | TypeScript | Type safety sangat penting untuk kalkulasi kompleks. Mengurangi bug runtime. |
Kalkulasi | @masaajid/prayer-times | Pustaka modern (TypeScript) yang sudah teruji untuk algoritma inti. Don't reinvent the wheel. |
Kalender | @tabby_ai/hijri-converter | Pustaka konversi Hijriah yang akurat dan ringan. |
Caching | Redis | Performa in-memory super cepat. Penting untuk mengurangi kalkulasi berulang. |
Database | PostgreSQL / MySQL | (Opsional) Untuk menyimpan data geografis (kota, negara, zona waktu). |
Testing | Jest & Supertest | Kombinasi kuat untuk unit test logika dan integration test API endpoint. |
Struktur Folder Berbasis Fitur
Kita akan menggunakan struktur berbasis fitur (feature-based) alih-alih MVC tradisional. Ini meningkatkan cohesion (kode yang terkait berada di satu tempat).
markdown
src/ ├── features/ │ ├── prayer-times/ │ │ ├── prayer-times.controller.ts # Logika HTTP & validasi │ │ ├── prayer-times.service.ts # Logika bisnis & kalkulasi │ │ ├── prayer-times.routes.ts # Definisi endpoint │ │ └── prayer-times.types.ts # Interface & Tipe data │ │ │ ├── qibla/ │ │ ├── qibla.controller.ts │ │ ├── qibla.service.ts │ │ └── qibla.routes.ts │ │ │ └── calendar-conversion/ │ └── ... │ ├── core/ │ ├── middleware/ │ │ ├── errorHandler.ts # Global error handler │ │ └── validator.ts # Skema validasi │ ├── lib/ │ │ ├── cache.service.ts # Abstraksi untuk Redis │ │ └── geocoding.provider.ts # (Opsional) Klien API Geocoding │ └── config.ts # Manajemen environment variable │ ├── data/ │ └── database.ts # (Opsional) Konfigurasi database │ ├── app.ts # Inisialisasi Express & middleware global └── server.ts # Titik masuk aplikasi (HTTP server)
Implementasi Inti: Step-by-Step
Mari kita bangun API ini dari awal.
Langkah 1: Setup Proyek Express.js + TypeScript
Bagian ini adalah boilerplate, tetapi penting untuk dilakukan dengan benar.
- Inisialisasi Proyek:
bash
mkdir prayer-times-api cd prayer-times-api npm init -y npm install express dotenv npm install -D typescript ts-node nodemon @types/node @types/express
- Konfigurasi
tsconfig.json:
npx tsc --init(Sesuaikan
tsconfig.json Anda, pastikan outDir ke ./dist dan rootDir ke ./src).- Buat
src/app.ts:
TStypescript
// src/app.ts import express, { Application, Request, Response } from "express"; import dotenv from "dotenv"; dotenv.config(); const app: Application = express(); app.use(express.json()); app.get("/", (req: Request, res: Response) => { res.status(200).json({ message: "Prayer Time API running!" }); }); // ... (Middleware error handling dan rute akan ditambahkan di sini) export default app;
- Buat
src/server.ts:
TStypescript
// src/server.ts import app from "./app"; const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });
Langkah 2: "Mesin" Kalkulasi Waktu Sholat
Pelajaran terpenting dari riset adalah: Jangan mengimplementasikan ulang algoritma astronomi dari awal. Itu sangat rumit dan rawan kesalahan. Kita akan membungkus (wrap) pustaka yang sudah ada dan teruji.
Kita akan menggunakan
@masaajid/prayer-times.npm install @masaajid/prayer-timesSekarang, kita buat PrayerTimesService untuk membungkus logika ini.
TStypescript
// src/features/prayer-times/prayer-times.service.ts import { PrayerTimeCalculator, IPrayerTimeCalculatorConfig, } from "@masaajid/prayer-times"; import { ICalculationInput } from "./prayer-times.types"; export class PrayerTimesService { /** * Menghitung waktu sholat harian berdasarkan konfigurasi. * Ini adalah inti dari "mesin" kalkulator kita. */ public calculateDailyTimes(input: ICalculationInput) { const { date, latitude, longitude, method, asr, highLatitude } = input; // 1. Petakan input API kita ke konfigurasi library const config: IPrayerTimeCalculatorConfig = { method: method || "MWL", // Default ke MWL jika tidak dispesifikasi date: date, location: [latitude, longitude], timezone: input.timezone || this.detectTimezone(latitude, longitude), // Helper asrSchool: asr === "Hanafi" ? "Hanafi" : "Standard", highLatitudeRule: highLatitude || "AngleBased", }; // 2. Buat instance kalkulator const calculator = new PrayerTimeCalculator(config); // 3. Lakukan kalkulasi const times = calculator.calculate(); // 4. (Opsional) Format output agar lebih rapi return { fajr: times.fajr.toLocaleTimeString("en-US", { hour12: false }), sunrise: times.sunrise.toLocaleTimeString("en-US", { hour12: false }), dhuhr: times.dhuhr.toLocaleTimeString("en-US", { hour12: false }), asr: times.asr.toLocaleTimeString("en-US", { hour12: false }), maghrib: times.maghrib.toLocaleTimeString("en-US", { hour12: false }), isha: times.isha.toLocaleTimeString("en-US", { hour12: false }), }; } // (Helper function sederhana, untuk produksi gunakan library seperti 'find-tz') private detectTimezone(lat: number, lon: number): string { // Implementasi deteksi zona waktu (bisa jadi kompleks) // Untuk saat ini, kita bisa minta user input 'timezone' // atau default ke 'UTC'. // Mari kita asumsikan 'timezone' adalah parameter wajib untuk kesederhanaan. // Dalam implementasi nyata, ini PENTING. return "Asia/Jakarta"; // Placeholder } // ... (Fungsi untuk kalender akan ditambahkan di sini) }
Langkah 3: Menangani Berbagai Metode Perhitungan
Perbedaan utama antar metode (MWL, ISNA, Kemenag) ada di parameter sudut Fajr dan Isha.
[TABLE 1: Calculation Methods Comparison]
Deskripsi Tabel: Perbandingan metode kalkulasi populer.
Metode | Otoritas | Sudut Fajr | Sudut Isha | Wilayah Umum |
MWL | Muslim World League | 18° | 17° | Global, Eropa |
ISNA | ISNA (Amerika Utara) | 15° | 15° | Amerika Utara |
Kemenag | Kemenag RI | 20° | 18° | Indonesia |
JAKIM | JAKIM | 20° | 18° | Malaysia |
Egyptian | Otoritas Survei Mesir | 19.5° | 17.5° | Mesir, Afrika |
Kabar baiknya, PrayerTimesService kita sudah mendukung ini melalui parameter method. Pustaka
@masaajid/prayer-times sudah memiliki preset untuk ini.Klien API kita cukup mengirimkan string seperti:
- method=MWL
- method=ISNA
- method=Egyptian
- method=Indonesia (Jika library mendukungnya, atau kita bisa buat custom method)
Kita juga bisa mengizinkan parameter kustom untuk tuning:
TStypescript
// Dalam calculateDailyTimes, tambahkan logika untuk parameter kustom if (input.fajrAngle && input.ishaAngle) { config.method = "Custom"; config.customMethod = { fajr: input.fajrAngle, isha: input.ishaAngle, }; }
Langkah 4: Implementasi Endpoint API (Routes & Controllers)
Sekarang kita ekspos logika servis kita melalui Express.
- Validasi Input: Kita gunakan express-validator untuk keamanan.
npm install express-validator
- Buat
prayer-times.routes.ts:
TStypescript
// src/features/prayer-times/prayer-times.routes.ts import { Router } from "express"; import { query } from "express-validator"; import { PrayerTimesController } from "./prayer-times.controller"; const router = Router(); const controller = new PrayerTimesController(); // Validasi untuk endpoint /timings const timingsValidation = [ query("date") .isISO8601() .toDate() .withMessage("Invalid date format (YYYY-MM-DD)"), query("latitude") .isFloat({ min: -90, max: 90 }) .withMessage("Invalid latitude"), query("longitude") .isFloat({ min: -180, max: 180 }) .withMessage("Invalid longitude"), query("timezone").notEmpty().isString().withMessage("Timezone is required"), query("method").optional().isString(), query("asr").optional().isIn(["Standard", "Hanafi"]), ]; // Endpoint: GET /api/v1/timings?date=...&latitude=...&longitude=... router.get("/timings", timingsValidation, controller.getDailyTimings); // Endpoint: GET /api/v1/calendar?year=...&month=...&latitude=... // ... (Tambahkan rute dan validasi untuk kalender) export default router;
- Buat prayer-times.controller.ts:
TStypescript
// src/features/prayer-times/prayer-times.controller.ts import { Request, Response, NextFunction } from "express"; import { validationResult } from "express-validator"; import { PrayerTimesService } from "./prayer-times.service"; export class PrayerTimesController { private prayerTimesService = new PrayerTimesService(); public getDailyTimings = ( req: Request, res: Response, next: NextFunction ) => { // 1. Cek hasil validasi const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } try { // 2. Ambil data yang sudah divalidasi const input = req.query as unknown as ICalculationInput; // 3. Panggil service const timings = this.prayerTimesService.calculateDailyTimes(input); // 4. Kembalikan respons sukses res.status(200).json({ code: 200, status: "OK", data: timings, params: input, }); } catch (error) { next(error); // Lempar ke global error handler } }; // ... (Implementasi getMonthlyCalendar) }
- Integrasi Rute ke
app.ts:
TStypescript
// src/app.ts // ... (setelah import dan app = express()) import prayerTimesRoutes from './features/prayer-times/prayer-times.routes'; app.use('/api/v1', prayerTimesRoutes); // ... (sebelum export default app)
Langkah 5: Integrasi Kalender Hijriah
API kita tidak lengkap tanpa kalender Hijriah. Kita akan buat endpoint konversi.
- Install Library:
npm install @tabby_ai/hijri-converter- Buat
calendar-conversion.service.ts:
TStypescript
// src/features/calendar-conversion/calendar-conversion.service.ts import { toHijri, toGregorian, HijriDate } from "@tabby_ai/hijri-converter"; export class CalendarConversionService { public convertGregorianToHijri(year: number, month: number, day: number) { const hijriDate = toHijri(year, month, day); return { date: `${hijriDate.hy}-${hijriDate.hm}-${hijriDate.hd}`, year: hijriDate.hy, month: hijriDate.hm, day: hijriDate.hd, monthName: this.getHijriMonthName(hijriDate.hm), }; } public convertHijriToGregorian(year: number, month: number, day: number) { const gregDate = toGregorian(year, month, day); return { date: `${gregDate.gy}-${gregDate.gm}-${gregDate.gd}`, year: gregDate.gy, month: gregDate.gm, day: gregDate.gd, }; } private getHijriMonthName(month: number): string { const names = [ "Muharram", "Safar", "Rabi' al-Awwal", "Rabi' al-Thani", "Jumada al-Ula", "Jumada al-Akhirah", "Rajab", "Sha'ban", "Ramadan", "Shawwal", "Dhu al-Qi'dah", "Dhu al-Hijjah", ]; return names[month - 1]; } }
- Buat Controller & Routes (mirip dengan langkah 4) untuk mengekspos servis ini di endpoint seperti:
- GET
/api/v1/gToH?date=2024-12-25
- GET
/api/v1/hToG?date=1446-06-23
Langkah 6: Caching Adalah Kunci (Integrasi Redis)
Perhitungan waktu sholat itu deterministic (hasilnya selalu sama untuk input yang sama) tetapi secara komputasi mahal jika dilakukan jutaan kali. Ini adalah skenario sempurna untuk caching.
- Install Redis Client:
npm install ioredisnpm install -D @types/ioredis- Buat
cache.service.ts:
TStypescript
// src/core/lib/cache.service.ts import Redis from "ioredis"; export class CacheService { private client: Redis; constructor() { this.client = new Redis(process.env.REDIS_URL || "redis://localhost:6379"); this.client.on("error", (err) => console.error("Redis Client Error", err)); } public async get(key: string): Promise<string | null> { return this.client.get(key); } public async set( key: string, value: string, ttlSeconds: number = 3600 ): Promise<void> { // 'EX' untuk set expiry dalam detik await this.client.set(key, value, "EX", ttlSeconds); } public async del(key: string): Promise<void> { await this.client.del(key); } } export const cacheService = new CacheService();
- Integrasikan ke PrayerTimesController:
TStypescript
// src/features/prayer-times/prayer-times.controller.ts import { cacheService } from '../../core/lib/cache.service'; // ... di dalam kelas PrayerTimesController public getDailyTimings = async (req: Request, res: Response, next: NextFunction) => { // ... (Validasi) try { // 1. Buat cache key yang unik berdasarkan input const input = req.query as unknown as ICalculationInput; // (Gunakan library 'object-hash' untuk membuat hash yang konsisten) const cacheKey = `timings:${JSON.stringify(input)}`; // 2. Cek di cache dulu const cachedData = await cacheService.get(cacheKey); if (cachedData) { return res.status(200).json({ code: 200, status: "OK", fromCache: true, data: JSON.parse(cachedData), }); } // 3. Jika tidak ada di cache, hitung baru const timings = this.prayerTimesService.calculateDailyTimings(input); // 4. Simpan ke cache (TTL 1 hari = 86400 detik) await cacheService.set(cacheKey, JSON.stringify(timings), 86400); // 5. Kembalikan respons res.status(200).json({ code: 200, status: "OK", fromCache: false, data: timings, params: input }); } catch (error) { next(error); } };
Dengan ini, 99% permintaan akan dilayani langsung dari memori, membuat API kita secepat kilat.
Fitur Lanjutan: Qibla dan High-Latitude
Kalkulator Arah Kiblat (Qibla)
Arah Kiblat adalah masalah navigasi bola (spherical trigonometry) untuk menemukan bearing (arah kompas) dari lokasi Anda ke Ka'bah (Lintang: 21.4225°, Bujur: 39.8262°).
Kita bisa mengimplementasikan rumusnya langsung.
TStypescript
// src/features/qibla/qibla.service.ts export class QiblaService { private readonly KAABA_LAT = 21.4225; private readonly KAABA_LON = 39.8262; private toRadians(degrees: number): number { return degrees * (Math.PI / 180); } private toDegrees(radians: number): number { return radians * (180 / Math.PI); } public calculateQiblaDirection(latitude: number, longitude: number): number { const latA = this.toRadians(latitude); const lonA = this.toRadians(longitude); const latB = this.toRadians(this.KAABA_LAT); const lonB = this.toRadians(this.KAABA_LON); const deltaLon = lonB - lonA; const y = Math.sin(deltaLon) * Math.cos(latB); const x = Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(deltaLon); const bearing = this.toDegrees(Math.atan2(y, x)); // Normalisasi hasil agar berada dalam rentang 0-360 derajat (utara = 0°) return (bearing + 360) % 360; } }
Kemudian, buat controller dan route untuk
GET /api/v1/qibla?latitude=...&longitude=... seperti yang kita lakukan sebelumnya.Menangani Wilayah Lintang Tinggi
Ini adalah masalah serius. Di lokasi seperti Oslo, Norwegia, saat musim panas, matahari mungkin tidak pernah turun ke 18° di bawah ufuk. Artinya, menurut perhitungan standar, waktu Isha "tidak pernah tiba".
Pustaka
@masaajid/prayer-times yang kita gunakan sudah memiliki solusi bawaan untuk ini melalui parameter highLatitudeRule:- AngleBased (Default): Metode hibrida yang cerdas.
- MiddleOfTheNight: Fajr dan Isha dihitung di tengah malam (antara terbenam dan terbit matahari).
- OneSeventhOfTheNight: Malam dibagi 7, Isha di 1/7 pertama, Fajr di 1/7 terakhir.
API kita sudah mengekspos ini melalui parameter highLatitude. Problem solved!
Testing dan Quality Assurance
API tanpa test adalah API yang menunggu untuk rusak. Kita akan gunakan Jest untuk unit test dan Supertest untuk integration test.
npm install -D jest ts-jest @types/jest supertest @types/supertestUnit Test (Contoh: qibla.service.test.ts):
TStypescript
// src/features/qibla/qibla.service.test.ts import { QiblaService } from "./qibla.service"; describe("QiblaService", () => { const service = new QiblaService(); it("should calculate correct Qibla for Jakarta, ID", () => { // Lintang: -6.2088, Bujur: 106.8456 const direction = service.calculateQiblaDirection(-6.2088, 106.8456); // Nilai referensi: 295.23° expect(direction).toBeCloseTo(295.23, 1); // Toleransi 1 desimal }); it("should calculate correct Qibla for New York, USA", () => { // Lintang: 40.7128, Bujur: -74.0060 const direction = service.calculateQiblaDirection(40.7128, -74.006); // Nilai referensi: 58.49° expect(direction).toBeCloseTo(58.49, 1); }); });
Integration Test (Contoh:
prayer-times.test.ts):TStypescript
// src/features/prayer-times/prayer-times.test.ts import request from "supertest"; import app from "../../app"; // Import aplikasi Express kita describe("GET /api/v1/timings", () => { it("should return 400 Bad Request for invalid latitude", async () => { const res = await request(app).get( "/api/v1/timings?date=2024-12-20&latitude=200&longitude=106&timezone=Asia/Jakarta" ); expect(res.status).toBe(400); expect(res.body.errors[0].msg).toContain("Invalid latitude"); }); it("should return 200 OK with prayer times for valid request (Jakarta)", async () => { const res = await request(app).get( "/api/v1/timings?date=2024-12-20&latitude=-6.2088&longitude=106.8456&timezone=Asia/Jakarta&method=Indonesia" ); expect(res.status).toBe(200); expect(res.body.data).toHaveProperty("fajr"); expect(res.body.data.fajr).toBe("04:14"); // Nilai referensi }); });
Strategi testing yang baik juga mencakup "Golden Tests", yaitu memvalidasi hasil API kita terhadap sumber tepercaya (misalnya, situs Kemenag atau observatorium) untuk beberapa lokasi dan tanggal kunci.
Deployment dan Pertimbangan Performa
API kita sudah siap untuk production.
- Kontainerisasi (Docker): Membungkus aplikasi dalam Docker container adalah praktik terbaik untuk portabilitas dan konsistensi.
- Keamanan API: Jangan lupakan keamanan dasar!
- Helmet: Menambahkan header keamanan HTTP (npm install helmet).
- CORS: Mengatur domain mana yang boleh mengakses API Anda (npm install cors).
- Rate Limiting: Mencegah abuse (npm install express-rate-limit).
TStypescript
// src/app.ts import helmet from "helmet"; import cors from "cors"; import rateLimit from "express-rate-limit"; // ... app.use(helmet()); app.use(cors({ origin: "https://domain-frontend-anda.com" })); // Whitelist const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 menit max: 100, // 100 permintaan per IP per 15 menit }); app.use("/api", limiter); // ...
- Platform Deployment:
- PaaS (Platform as a Service): Pilihan termudah. Platform seperti Railway, Vercel (Serverless), atau Heroku dapat deploy langsung dari GitHub.
- VPS (Virtual Private Server): Pilihan lebih manual (DigitalOcean, AWS EC2) memberi Anda kontrol penuh. Gunakan Docker Compose untuk menjalankan aplikasi, database PostgreSQL, dan Redis di satu server.
- Monitoring: Gunakan logger seperti winston atau pino dan integrasikan dengan layanan seperti Sentry atau Datadog untuk memantau error di production.
[SCREENSHOT 1: API Response Example]
Deskripsi Screenshot: Contoh respons JSON dari API di Postman atau Insomnia, menunjukkan struktur data: c
ode: 200, data: { fajr: "..." }, dan fromCache: true.Contoh Penggunaan API (Real-World Usage)
Setelah API Anda live di https://api.domain-anda.com, begini cara menggunakannya:
Mendapatkan waktu sholat harian untuk Jakarta (Metode Kemenag):
curl -X GET '[https://api.domain-anda.com/api/v1/timings?date=2024-12-25&latitude=-6.2088&longitude=106.8456&timezone=Asia/Jakarta&method=Indonesia](https://api.domain-anda.com/api/v1/timings?date=2024-12-25&latitude=-6.2088&longitude=106.8456&timezone=Asia/Jakarta&method=Indonesia)'Respons:
JSONjson
{ "code": 200, "status": "OK", "fromCache": false, "data": { "fajr": "04:17", "sunrise": "05:38", "dhuhr": "11:53", "asr": "15:18", "maghrib": "18:08", "isha": "19:24" } }
Mendapatkan arah Kiblat untuk London:
curl -X GET '[https://api.domain-anda.com/api/v1/qibla?latitude=51.5074&longitude=-0.1278](https://api.domain-anda.com/api/v1/qibla?latitude=51.5074&longitude=-0.1278)'Respons:
JSONjson
{ "code": 200, "status": "OK", "data": { "direction": 118.98 } }
Konversi tanggal Masehi ke Hijriah:
curl -X GET '[https://api.domain-anda.com/api/v1/gToH?date=2025-01-01](https://api.domain-anda.com/api/v1/gToH?date=2025-01-01)'Respons:
JSONjson
{ "code": 200, "status": "OK", "data": { "date": "1446-7-1", "year": 1446, "month": 7, "day": 1, "monthName": "Rajab" } }
Kesimpulan dan Langkah Selanjutnya
Selamat! Kita telah menempuh perjalanan panjang dari analisis teoretis hingga implementasi API yang siap produksi. Kita telah membangun Prayer Time API yang type-safe, akurat, dan sangat cepat menggunakan Express.js, TypeScript, dan Redis.
Pelajaran kuncinya adalah: abstraksi yang cerdas. Daripada tenggelam dalam rumus astronomi, kita fokus pada arsitektur sistem, caching, dan testing, sambil mendelegasikan perhitungan inti ke pustaka yang teruji.
API ini sekarang menjadi fondasi yang kokoh. Langkah selanjutnya bisa meliputi:
- Membangun Database Geografis: Membuat endpoint
/timingsByCity?city=Jakartadengan pre-populating database kota.
- Dokumentasi OpenAPI: Membuat dokumentasi interaktif dengan Swagger/OpenAPI.
- Client SDK: Membangun library NPM (my-prayer-times-client) untuk memudahkan integrasi frontend.
Saya harap panduan ini bermanfaat. Membangun teknologi untuk komunitas adalah salah satu hal paling memuaskan yang bisa kita lakukan sebagai developer.
FAQ (Frequently Asked Questions)
Apa perbedaan utama antara metode perhitungan MWL dan ISNA?
Perbedaan utamanya terletak pada sudut depresi matahari yang digunakan untuk Fajr (Subuh) dan Isha (Isya). MWL (Muslim World League) menggunakan 18° untuk Fajr dan 17° untuk Isha. ISNA (Islamic Society of North America) menggunakan 15° untuk keduanya. Ini berarti waktu Fajr ISNA akan sedikit lebih siang, dan waktu Isha ISNA akan sedikit lebih cepat dibandingkan MWL.
Seberapa akurat perhitungan API ini?
Akurasi perhitungan astronomi sangat tinggi (menggunakan algoritma modern). Namun, "akurasi" dalam waktu sholat bersifat relatif dan harus sesuai dengan otoritas lokal. API ini akurat secara matematis. Anda harus memilih parameter method yang sesuai dengan otoritas (misalnya, Indonesia untuk Kemenag) agar dianggap "akurat" secara fikih oleh pengguna Anda.
Mengapa saya perlu parameter 'timezone'? Bukankah bisa dideteksi dari lintang/bujur?
Mendeteksi timezone (seperti Asia/Jakarta) dari koordinat adalah masalah yang sangat kompleks (disebut reverse geocoding). Sebuah negara bisa memiliki banyak zona waktu, dan batasannya tidak lurus. Untuk performa dan akurasi, meminta klien (aplikasi frontend atau mobile) untuk menyediakan timezone perangkat adalah praktik yang jauh lebih baik dan lebih andal.
Apakah API ini gratis untuk digunakan?
Karena Anda yang membangun dan hosting API ini, Anda memiliki kontrol penuh! Anda bisa menggunakannya secara gratis untuk proyek pribadi Anda, atau bahkan mengubahnya menjadi layanan SaaS (Software as a Service) publik. Biaya operasional utamanya adalah untuk hosting server dan database Redis (yang seringkali memiliki free tier yang cukup).
Bagaimana cara menangani Daylight Saving Time (DST)?
Kabar baik! Dengan menggunakan timezone IANA (misf. America/New_York) dan pustaka Date JavaScript yang modern (atau day.js/luxon), DST ditangani secara otomatis. Perhitungan waktu sholat didasarkan pada tanggal dan timezone, dan pustaka kalkulasi akan secara otomatis menyesuaikan offset jam jika tanggal tersebut jatuh dalam periode DST.
Source Code & Demo
📦 GitHub Repository: [Link ke GitHub Repo Anda]
🚀 Live Demo: [Link ke Demo API Anda]
📖 Full Documentation: [Link ke Dokumentasi OpenAPI/Swagger Anda]
⭐ Star repositori ini jika Anda merasa bermanfaat!
Sumber Tambahan:
- Ingin melihat data mentahnya? Anda dapat mengakses halaman berikut
- Lebih suka mendengarkan sambil coding?