DEV Community

Cover image for How I Integrated Google Gemini for 11 AI Features in My News App
PADMANABHA DAS
PADMANABHA DAS

Posted on

How I Integrated Google Gemini for 11 AI Features in My News App

How I Integrated Google Gemini for 11 AI Features in My News App

News apps today are just RSS readers with a search bar. I wanted something smarter—AI that could summarize articles, analyze sentiment, generate social media captions, and answer questions about the content. So I built PulsePress with Google Gemini & HuggingFace, creating 11 AI features that process 70+ news sources. Here's exactly how I did it, including the quota management and fallback strategy that keeps it running at scale.

The Problem: Why News Apps Need Intelligence

Traditional news aggregators give you articles. That's it. But what if you're scrolling through 50 headlines about AI regulation? You need:

  • Context: What's the sentiment? Is this good or bad news?
  • Speed: 5-minute summary, not 15-minute reads
  • Actionability: Social media captions, key takeaways, Q&A
  • Multilingual support: Not everyone reads in English

I built PulsePress to solve this. The backend aggregates from NewsAPI, The Guardian, NY Times, and 70+ RSS feeds. Then Google Gemini enhances each article with AI.

The 11 AI Features

Here's what Gemini powers in PulsePress:

  1. News Classification - Filters spam/promotional content
  2. Summarization - 3 styles (concise/standard/detailed) in 26 languages
  3. Tag Generation - 5-10 relevant keywords per article
  4. Sentiment Analysis - Positive/negative/neutral with confidence scores
  5. Key Points Extraction - 3-7 bullet-point takeaways
  6. Complexity Meter - Reading difficulty (easy/medium/hard)
  7. Question Generation - Auto-creates comprehension questions
  8. Question Answering - Users ask, AI answers from article context
  9. Geographic Extraction - Pulls locations mentioned
  10. Social Media Captions - Platform-specific (Twitter/Instagram/LinkedIn)
  11. News Insights - Deep analysis: themes, impact, stakeholders

All 11 use the same Gemini API with different prompts.

Technical Architecture: The Model Hierarchy

The Challenge: Google Gemini has daily quotas. I needed a fallback system that tries newer models first, then cascades to older ones.

Here's my model hierarchy:

const AI_MODELS = [
  'gemini-2.5-flash-preview-09-2025',      // Newest, try first
  'gemini-2.5-flash-lite-preview-09-2025',
  'gemini-2.5-flash-lite',
  'gemini-2.5-flash',
  'gemini-2.0-flash',
  'gemini-2.0-flash-lite',
  'gemini-1.5-flash',                      // Fallback
];
Enter fullscreen mode Exit fullscreen mode

Why this matters: If Model 1 hits quota, the system automatically tries Model 2. No failed requests.

Quota Tracking

I store daily quotas in MongoDB:

// ApiQuotaSchema
{
  service: 'gemini_summarization',
  date: '2025-01-25',
  requestCount: 45,
  lastResetAt: '2025-01-25T00:00:00Z',
}
Enter fullscreen mode Exit fullscreen mode

Before each AI call:

const checkQuota = async (service: string) => {
  const today = new Date().toISOString().split('T')[0];

  const quota = await ApiQuota.findOne({ service, date: today });

  if (quota && quota.requestCount >= DAILY_LIMIT) {
    throw new Error('Quota exceeded');
  }

  // Increment count
  await ApiQuota.updateOne(
    { service, date: today },
    { $inc: { requestCount: 1 } },
    { upsert: true },
  );
};
Enter fullscreen mode Exit fullscreen mode

Daily limits:

  • Total Gemini requests: 900/day
  • Per-model: 100-900 depending on tier

Implementation: Sentiment Analysis Example

Let me show you one feature end-to-end: Sentiment Analysis.

1. The API Endpoint

// AIController.ts
router.post('/sentiment', authenticateUser, async (req, res) => {
  const { url } = req.body;

  // Check quota first
  await QuotaService.checkQuota('gemini_sentiment');

  // Generate sentiment
  const result = await SentimentAnalysisService.analyze(url);

  return res.json({
    success: true,
    data: result,
  });
});
Enter fullscreen mode Exit fullscreen mode

2. The Service Layer

// SentimentAnalysisService.ts
const analyze = async (url: string) => {
  // Check cache first (30-day TTL)
  const cached = await ArticleEnhancement.findOne({ 
    articleId: generateArticleId(url) 
  });

  if (cached?.sentiment) return cached.sentiment;

  // Scrape article
  const content = await scrapeArticle(url);

  // Call Gemini with fallback
  const sentiment = await callGeminiWithFallback(
    AI_MODELS,
    buildSentimentPrompt(content),
  );

  // Cache result
  await cacheEnhancement(url, { sentiment });

  return sentiment;
};
Enter fullscreen mode Exit fullscreen mode

3. The Gemini Prompt

const buildSentimentPrompt = (content: string) => `
Analyze sentiment of this article:

Title: ${content.title}
Content: ${content.text}

Return JSON:
{
  "sentiment": "positive|negative|neutral",
  "confidence": 0.0-1.0,
}
`;
Enter fullscreen mode Exit fullscreen mode

4. The Fallback Strategy

const callGeminiWithFallback = async (models: string[], prompt: string) => {
  for (const model of models) {
    try {
      const result = await gemini.generateContent({
        model,
        prompt,
        generationConfig: {
          temperature: 0.3,
          maxOutputTokens: 500,
        }
      });

      return JSON.parse(result.response.text());

    } catch (error) {
      if (error.message.includes('quota')) {
        continue; // Try next model
      }
      throw error;
    }
  }

  throw new Error('All models exhausted');
};
Enter fullscreen mode Exit fullscreen mode

5. Response Formatting

// sentimentHelpers.ts
const formatSentiment = (raw: GeminiResponse) => ({
  type: raw.sentiment,
  confidence: raw.confidence,
  emoji: raw.sentiment === 'positive' ? '😊' : 
         raw.sentiment === 'negative' ? '😔' : '😐',
  color: raw.sentiment === 'positive' ? '#4ade80' :
         raw.sentiment === 'negative' ? '#ef4444' : '#94a3b8',
});
Enter fullscreen mode Exit fullscreen mode

Final response:

{
  "success": true,
  "data": {
    "sentiment": "positive",
    "confidence": 0.87,
    "emoji": "😊",
    "color": "#4ade80",
  }
}
Enter fullscreen mode Exit fullscreen mode

Scaling Strategy: Progressive/Background Enhancement

The key insight: Don't make users wait for AI.

When a user searches for news:

// Step 1: Return articles immediately
const articles = await fetchFromAPIs(query);
res.json({ 
  articles,
  processingStatus: 'pending',
});

// Step 2: Enhance in background (non-blocking)
enhanceArticlesAsync(articles);
Enter fullscreen mode Exit fullscreen mode

Then users poll for enhancements:

GET /api/v1/news/enhancement-status?articleIds=hash1,hash2

Response:
{
  "hash1": { "status": "completed", "sentiment": {...} },
  "hash2": { "status": "pending" },
}
Enter fullscreen mode Exit fullscreen mode

This keeps the UI fast while AI works asynchronously.

Results & Lessons Learned

What worked:

  • 30-day caching reduced API costs by 60%
  • Model fallback achieved 99.8% uptime
  • Background enhancement cut perceived latency from 8s to 0.3s

What I'd do differently:

  • Start with cheaper models (1.5 Flash) for non-critical features
  • Batch requests for tag generation across multiple articles
  • Add Redis for sub-second cache hits

Tech stack:

  • Backend: Node.js + TypeScript + Express
  • Database: MongoDB (10 collections)
  • AI: Google Gemini (7 models)
  • Caching: 30-day TTL on enhancements

GitHub: https://github.com/chayan-1906/PulsePress-Node.js


Building AI features doesn't require a PhD. Start with one feature (sentiment analysis took me 2 hours), add caching, and implement fallbacks. The rest is just repeating the pattern.

Questions? Drop them below—PulsePress is complete and open source. Check out the full implementation on GitHub.

Top comments (0)