Nurullah Türkoğlu

26 November 2025 · 2 min read

Flutter Push Notifications with FCM: Token and Topic Messages

How to send token-based and topic-based push notifications from a Node.js backend to a Flutter app using Firebase Cloud Messaging.

Push notifications show up in most kinds of apps: order status in a shop, new messages in a social app, breaking news in a news app. In Flutter, Firebase Cloud Messaging (FCM) lets you send them either to one device by token or to everyone subscribed to a topic. This post builds both with a Flutter app and a Node.js backend.

Flutter app receiving a token notification

Project structure

The project has two parts: the Flutter app and a Node.js Express server. The app receives notifications through Firebase Messaging. The server sends them with the Firebase Admin SDK.

Firebase Messaging service

This is the service that sets up Firebase Messaging in the Flutter app:

class FirebaseMessagingService {
  static final FirebaseMessagingService _instance =
      FirebaseMessagingService._internal();
  
  factory FirebaseMessagingService.instance() => _instance;

  Future<void> init({required LocalNotificationsService localNotificationsService}) async {
    _localNotificationsService = localNotificationsService;
    _requestPermission();
    await _initializeNotificationSubscription();
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
    FirebaseMessaging.onMessage.listen(_onForegroundMessage);
  }

  Future<void> subscribeToGeneralTopic() async {
    await FirebaseMessaging.instance.subscribeToTopic('everyone');
    await SharedPreferencesHelper.setNotificationEnabled(true);
  }

  Future<String?> getFCMToken() async {
    return await FirebaseMessaging.instance.getToken();
  }
}

It runs when the app starts and asks the user for notification permission. It also handles subscribing to topics and getting the FCM token. It is a singleton, so the whole app uses one instance. When a notification arrives in the foreground, the app shows it as a local notification. In the background or terminated state, Firebase's own notification system takes over.

Flutter app receiving a topic notification

Backend endpoints

The backend has two main endpoints, one for topic notifications and one for token notifications. A topic notification goes to every user subscribed to that topic. A token notification goes to one device only. That covers both general announcements and personal messages.

// Send a topic notification
app.post("/api/topic/notify", async (req, res) => {
  try {
    const { title, body } = req.body;
    
    const message = {
      notification: { title, body },
      topic: "everyone",
    };
    
    const response = await admin.messaging().send(message);
    res.json({
      success: true,
      messageId: response,
      message: "Notification sent successfully",
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error.message,
    });
  }
});

API service and HTTP requests

The Flutter app talks to the backend with the Dio package, an HTTP client that supports async/await:

class ApiService {
  static const String baseUrl = 'http://localhost:3000';
  final Dio _dio = Dio();

  ApiService() {
    _dio.options.baseUrl = baseUrl;
    _dio.options.connectTimeout = const Duration(seconds: 5);
    _dio.options.receiveTimeout = const Duration(seconds: 5);
  }

  Future<ApiResponse> sendTopicNotification(String title, String body) async {
    try {
      final response = await _dio.post(
        '/api/topic/notify',
        data: {'title': title, 'body': body},
      );
      
      if (response.statusCode == 200) {
        return const ApiResponse(
          success: true,
          message: 'Notification sent successfully',
        );
      }
      return const ApiResponse(
        success: false,
        message: 'Failed to send notification',
      );
    } on DioException catch (e) {
      return ApiResponse(
        success: false,
        message: e.message ?? 'Failed to send notification',
      );
    }
  }
}

The timeouts and error handling keep the connection to the backend reliable. Use your local IP address on the Android emulator and localhost on the iOS simulator.

Notification delivered successfully

When to use which

Token notifications are for messages meant for one user. In an e-commerce app, for example, only that user gets a notification when their order status changes. Topic notifications fit announcements and campaigns, like a discount for everyone or a maintenance notice. For those, a topic is much more efficient than sending to each token.

iOS and backend setup

APNs configuration in the Firebase console

iOS needs APNs (Apple Push Notification service) configured. Upload an APNs Authentication Key or a certificate in the Firebase console to turn on iOS notifications. The Authentication Key is the more modern and recommended option, because one key works for both development and production.

Service account configuration

To use the Firebase Admin SDK on the backend, download a Service Account Key and add it to the project. The key lets your server access Firebase services. Never commit this file to version control, and in production use it as an environment variable.

Video and source code

Video walkthrough: https://www.youtube.com/watch?v=oETdrD_Vs5A

Source code: https://github.com/nurullahturkoglu/flutter-fcm-topic-notifications