← blog
JavaScript Firestore RTDB Pattern misto

Multi-category notification center: Firestore + RTDB in the same drawer

How to pick the right database per notification category, and combine a single-read Firestore query with real-time RTDB listeners in the same panel, without conflicts.

The problem

A notification center with multiple categories (leave requests, calendar, chat, reminders) often has data living in different databases with different characteristics: some need a one-time read, others need real-time updates. Using the same pattern for everything wastes reads or introduces unnecessary listeners.

Firestore: single read with multiple filters

For categories where history doesn't change often (e.g. leave requests), a Firestore query with .get() — not .onSnapshot() — avoids keeping an unnecessary listener open.

notifiche-ferie.js — query Firestore (lettura singola)
const cutoff = Date.now() - 90 * 24 * 60 * 60 * 1000;
const tipi = [
  'ferie_richiesta', 'ferie_approvata',
  'ferie_rifiutata', 'ferie_annullata'
];

db.collection('activityLog')
  .where('operatore', '==', currentUser)
  .where('tipo', 'in', tipi)
  .where('ts', '>=', cutoff)
  .get()   // una sola lettura, non real-time
  .then(snap => snap.docs.map(d => ({ id: d.id, ...d.data() })));

RTDB: real-time listeners on existing nodes

Calendar, reminders and chat instead live on the Realtime Database — small nodes, already structured for fast reads. Attaching with .on('value') to existing nodes doesn't add significant cost, and it's the right choice when data changes often and the user needs to see updates without refreshing.

notifiche-calendario.js — listener RTDB real-time
const ref = db.ref('calendario/' + reparto);

ref.on('value', snapshot => {
  const eventi = snapshot.val() || {};

  // Filtra: solo eventi già scattati, creati dall'utente o del suo reparto
  const visibili = Object.entries(eventi)
    .filter(([id, ev]) =>
      ev.firedAt && (ev.creatoDa === currentUser || ev.reparto === userReparto)
    );

  renderNotificheCalendario(visibili);
});

// Da chiamare allo smontaggio del componente / cambio utente
function cleanup() { ref.off('value'); }

Rule of thumb: data that changes rarely and is just consulted → Firestore .get(). Data that must update live in the UI without user action → RTDB .on('value').

Watch the bootstrap: in a PWA with "remember me", real-time listeners started at manual login must also be started in the session-restore path — otherwise half your users end up with notifications silently disabled after a refresh.

Related article
PWA notification center: Firebase, Firestore and category drawer — real-world case

When to use this hybrid Firestore + RTDB pattern

This snippet shows how to combine a Firestore query (single read) and a Realtime Database listener (.on value) in the same notification drawer, choosing the most suitable database for each data category.

When to use it

When NOT to use it

Alternatives

Common mistakes

Frequently asked questions about the hybrid notification drawer

Because different notification categories have different access patterns: historical or one-time-read data fits better with a Firestore query, while state that changes in real time benefits from a Realtime Database listener.

Yes, it's important to disconnect the .on value listener (with .off or the corresponding unsubscribe) when the component using it unmounts, to avoid memory leaks and unnecessary reads.

The pattern only makes sense if the project actually uses both databases; if you only use Firestore, it's better to keep all notifications there without introducing RTDB just for this case.