express-quran-api
Version: 
Simple Quran API with Express
163 lines (145 loc) • 5.94 kB
JavaScript
const express = require('express');
const fs = require('fs');
const path = require('path');
class ExpressQuranAPI {
  constructor(options = {}) {
    this.app = express();
    this.port = options.port || 3000;
    this.dataPath = options.dataPath || path.join(__dirname, 'data');
    this.extraLanguages = options.extraLanguages || [];
    this.quranData = {};
    this.cache = new Map();
    this.searchIndex = new Map();
    this.loadData();
    this.buildSearchIndex();
    this.setupRoutes();
  }
  loadData() {
    const arPath = path.join(this.dataPath, 'quran_ar.json');
    if (!fs.existsSync(arPath)) throw new Error('quran_ar.json not found! Please provide the Arabic Quran data file.');
    this.quranData['ar'] = JSON.parse(fs.readFileSync(arPath, 'utf8'));
    this.extraLanguages.forEach(lang => {
      const filePath = path.join(this.dataPath, `quran_${lang}.json`);
      if (fs.existsSync(filePath)) {
        const langData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
        langData.forEach((surah, sIdx) => {
          this.quranData['ar'][sIdx].translationName = this.quranData['ar'][sIdx].translationName || {};
          this.quranData['ar'][sIdx].translationName[lang] = surah.translation || '';
          surah.verses.forEach((ayah, aIdx) => {
            this.quranData['ar'][sIdx].verses[aIdx].translation = this.quranData['ar'][sIdx].verses[aIdx].translation || {};
            this.quranData['ar'][sIdx].verses[aIdx].translation[lang] = ayah.translation || '';
          });
        });
      } else {
        console.warn(`File for language "${lang}" not found: ${filePath}`);
      }
    });
  }
  buildSearchIndex() {
    const data = this.quranData['ar'];
    data.forEach(surah => {
      surah.verses.forEach(ayah => {
        const words = ayah.text.replace(/[^\u0600-\u06FF\s]/g, '').split(/\s+/);
        words.forEach(word => {
          if (word.length > 0) {
            const cleanWord = word.trim();
            if (!this.searchIndex.has(cleanWord)) {
              this.searchIndex.set(cleanWord, []);
            }
            this.searchIndex.get(cleanWord).push({
              surah: surah.id,
              ayah: ayah.id,
              text: ayah.text
            });
          }
        });
      });
    });
  }
  setupRoutes() {
    const app = this.app;
    app.use(express.json({ limit: '1mb' }));
    app.get('/api/surahs', (req, res) => {
      const { filterType, type } = req.query;
      const cacheKey = `surahs_${filterType}_${type}`;
      
      if (this.cache.has(cacheKey)) {
        return res.json(this.cache.get(cacheKey));
      }
      
      let surahs = JSON.parse(JSON.stringify(this.quranData['ar']));
      if (filterType === 'true' && (type === 'meccan' || type === 'medinan')) {
        surahs = surahs.filter(s => s.type === type);
      }
      
      this.cache.set(cacheKey, surahs);
      res.json(surahs);
    });
    app.get('/api/search', (req, res) => {
      const { q, lang } = req.query;
      if (!q) return res.status(400).json({ error: 'Query parameter "q" is required' });
      const searchLang = lang || 'ar';
      const cacheKey = `search_${q.toLowerCase()}_${searchLang}`;
      
      if (this.cache.has(cacheKey)) {
        return res.json(this.cache.get(cacheKey));
      }
      
      const query = q.trim().replace(/[^\u0600-\u06FF\s]/g, '');
      const results = [];
      
      if (searchLang === 'ar' && this.searchIndex.has(query)) {
        const indexResults = this.searchIndex.get(query);
        indexResults.forEach(item => {
          const surah = this.quranData['ar'].find(s => s.id === item.surah);
          const ayah = surah.verses.find(a => a.id === item.ayah);
          results.push({
            surah: surah.id,
            surahName: surah.name,
            surahTranslation: surah.translationName?.[searchLang] || surah.translation || null,
            ayah: ayah.id,
            text: ayah.text,
            translation: (ayah.translation && ayah.translation[searchLang]) || null
          });
        });
      } else {
        const data = this.quranData['ar'];
        data.forEach(surah => {
          surah.verses.forEach(ayah => {
            const textMatch = ayah.text.toLowerCase().includes(q.toLowerCase());
            const translationText = (ayah.translation && ayah.translation[searchLang]) || '';
            const translationMatch = translationText.toLowerCase().includes(q.toLowerCase());
            if (textMatch || translationMatch) {
              results.push({
                surah: surah.id,
                surahName: surah.name,
                surahTranslation: surah.translationName?.[searchLang] || surah.translation || null,
                ayah: ayah.id,
                text: ayah.text,
                translation: translationText || null
              });
            }
          });
        });
      }
      
      this.cache.set(cacheKey, results);
      res.json(results);
    });
    app.get('/api/random', (req, res) => {
      const data = this.quranData['ar'];
      const surah = data[Math.floor(Math.random() * data.length)];
      const ayah = surah.verses[Math.floor(Math.random() * surah.verses.length)];
      const lang = req.query.lang || 'ar';
      const translation = (ayah.translation && ayah.translation[lang]) || null;
      res.json({
        surah: surah.id,
        surahName: surah.name,
        ayah: ayah.id,
        text: ayah.text,
        translation
      });
    });
  }
listen(callback) {
  this.app.listen(this.port, () => {
    console.log('Express Quran API started on port ' + this.port);
    console.log('You can open the API in your browser at http://localhost:' + this.port);
    if (callback) callback();
  });
 }
}
module.exports = ExpressQuranAPI;