Transitioning from AWS to Firebase: Creating an Advanced Web Application with Flask, Firebase, and Google Cloud

In recent years, mobile and web development have undergone a significant evolution. Modern applications not only possess powerful features but also require a more comprehensive infrastructure beyond traditional hosting services. At this point, major companies like Amazon and Google offer extensive solutions to meet all these requirements. When transitioning between platforms like AWS and Firebase, it's crucial to meticulously plan each step and manage this complex integration process correctly, ensuring projects successfully adapt to serverless architectures. In this article, I will explain the process of transferring my primary AWS flow to the Firebase platform using Firebase services. In doing so, we will discover how similar the structures of Firebase and AWS platforms actually are. Additionally, we will examine how to send notifications to users using the Firebase Cloud Messaging service.

Definition of AWS Flow

Figure 1 The AWS Flow where User Input is Taken and Sent to Replicate, and the Result is Returned to the User

In Figure 1's AWS workflow, user input and connection ID information are received. These collected data are sent to the Replicate service, which runs machine learning models in the cloud environment, through an AWS Lambda function. Replicate provides users with machine learning models in Application Programming Interface (API) or Docker formats. In our article, we will use the models in API format. Replicate's APIs allow the use of a webhook service structure. With the help of the webhook service, the response from the Replicate API is redirected to a different URL. The response received through the webhook service is saved into an AWS S3 bucket using AWS Lambda. The saved response is then returned to the user with the assistance of Websocket.

The AWS workflow described in Figure 1 has been translated into the following steps within a Firebase structure:

  1. Setting Up the Flask Application:
    1. Begins with creating a basic Flask project.
  2. Integration of Firebase Cloud Messaging:
    1. In the Flask project, FCM token information is obtained with Firebase Cloud Messaging configurations.
  3. Transformation of AWS Replicate Function:
    1. The 'Send to AWS Replicate' Lambda function is converted into a cloud function using Firebase Cloud Functions.
    2. Within the function, configurations for the API used in Replicate are set up. In addition, FCM token information and the webhook service URL are also sent via the API.
  4. Processing the Webhook Service URL:
    1. The webhook service URL sent to Replicate is directed to the function equivalent to the 'Save to AWS S3' Lambda function in Figure 1, which saves the image received from the UPA to Google Cloud Storage.
  5. Transformation of the 'Save to AWS S3' Function:
    1. Within the Firebase Cloud Function:
      1. The image received from Replicate is saved in Google Cloud Storage.
      2. The image URL and FCM token information are extracted.
      3. Within the application, a POST request containing the image URL and FCM token information is sent to the Firebase Function that sends notifications.
  6. Firebase Bildirim Gönderme Fonksiyonu:
    1. Bu fonksiyon, FBM aracılığıyla bir bildirim göndermeyi sağlar. İsteğin JSON verisi içindeki image_url ve jeton bilgilerini alır, ardından bu bilgileri kullanarak belirtilen cihaza bir bildirim gönderir. 

Flask Application

To send notifications to the user, a structure on the user side is required, hence a simple Flask application is created. Within the Flask application, user input is received as depicted in Figure 2, to transmit the input information from the user to a Replicate API. The text input received and the FCM token information generated when the page is first opened are sent to the function equivalent to the 'Send to AWS Replicate' Lambda function on the Flask server side. 

Figure 2 The Flask Application Screen Where User Input is Taken and FBM Token Information is Displayed on the Console

Integration of Firebase Cloud Messaging

Creation of Firebase Software Development Kit (SDK)

First, we log into the Firebase console and then create a new Firebase project as shown in Figure 3.

Figure 3 The Firebase Console New Project Creation/Existing Projects List Screen

We go to the project settings section within the project we created. In the project settings, under the service accounts tab, we select 'Create new private key'. In the Admin SDK configuration section, it is shown how to use the downloaded private key file according to the language structure we use in the project. 

An example of the contents inside a private key file is as follows.

{
  "type": "service_account",
  "project_id": "your_project_id",
  "private_key_id": "your_private_key_id",
  "private_key": "your_private_key",
  "client_email": "your_client_email.com",
  "client_id": "your_client_id",
  "auth_uri": "your_auth_uri",
  "token_uri": "your_token_uri",
  "auth_provider_x509_cert_url": "auth_provider_x509_cert_url",
  "client_x509_cert_url": "client_x509_cert_url",
  "universe_domain": "universe_domain"
}

On the same settings page, under the General tab, how to install the Firebase SDK into your application is shown as in Figure 4. To set up the SDK connection, we need to create a firebase-messaging-sw.js configuration file with the information shown in Figure 4. This file should be located in the root folder location of the project.

Figure 4 Integration of Adding Firebase SDK Configurations to the Application

Adding the SDK File to the Flask Application

In our Flask application, we need to write a JavaScript function to obtain the FCM token information. The 'Firebase FCM Token Generation' code below performs the necessary actions to receive instant notifications in the browser using FCM.Firstly,we initialize Firebase with firebaseConfig and then create a messaging instance to facilitate interaction with notifications. The registerServiceWorker function registers a Service Worker in the browser. Service Workers are typically used to enable features like offline access and notifications. When the Service Worker is successfully registered, it prints the scope information to the console; in case of an error, an error message is printed to the console. This code ensures safe operation by handling potential error situations while enabling the capability to send instant notifications to the user. Firstly, we create a service worker with Firebase and register our device with the Firebase Cloud Messaging service. Firebase Cloud Messaging service returns a unique token specific to the device. This token allows us to send notifications to the device. Then, we send this FCM token information to a server-side Python function. The Python function, in turn, sends this token information and the user input to the Firebase cloud function.

firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();


function registerServiceWorker() {
  return navigator.serviceWorker.register('/firebase-messaging-sw.js')
    .then((registration) => {
      console.log('Service Worker registered with scope:', registration.scope);
      return registration;
    })
    .catch((error) => {
      console.error('Service Worker registration failed:', error);
      throw error;
    });
}

function requestAndSendFCMToken() {
  return messaging.requestPermission()
    .then(async () => {
      const token = await messaging.getToken();
      console.log('FCM token:', token);
      return token;
    })
    .catch((error) => {
      console.error('Error requesting permission or getting FCM token:', error);
      throw error;
    });
}

Transformation of AWS Replicate Function

Creating a Cloud Function with Google Cloud Functions

We log into the Google Cloud Console.After logging in, we select our project created via Firebase from the upper left section and then navigate to the Google Cloud Functions service by typing 'Google Cloud Functions' in the search bar.

In the Cloud Functions service, we click on the 'create function' button to create a new function. We configure the sections shown in Figure 5 according to the structure of our function.

Figure 5 Defining Cloud Functions Function and Configuring Access Credentials

As shown in Figure 6, Google Cloud Functions can be written in multiple languages. In the section named 'entry point', we specify the name of the main function that will operate within the function we have written.

Figure 6 Selecting the Programming Language and Version for Cloud Functions

Function to Send Queries to Replicate API with Google Cloud Functions

Bu projede oluşturulan tüm fonksiyonlar Python dilinde yazılmış olup Python 3.10 versiyonu kullanılmıştır. Fonksiyon için kullanacağımız dili seçtikten sonra python diline özel bir requirements.txt dosyası oluşturuyoruz. Fonksiyonumuzun bulunacağı .py dosyasına  aşağıdaki örnekteki gibi bir Replicate sorgusu yazıyoruz. "Firebase Cloud Function - Sending Query to Replicate" function, the token information received from Flask and the user input are used to query the stable diffusion model on the Replicate site. The function completes without waiting for a response from the query. When a response is received, it is sent to the address specified in the webhook service link. To do this, a connection is established with the Replicate service using the API token information generated by Replicate

import os
import threading
import requests
import replicate


os.environ["REPLICATE_API_TOKEN"] = "YOUR_API_TOKEN"


def replicate_request(request):
    request_json = request.get_json()
    prompt = request_json.get('prompt', 'default_prompt')
    user_id = request_json.get('user_id', 'default_user_id')
    model = replicate.models.get("stability-ai/stable-diffusion")
    version = model.versions.get("27b93a2413e7f36cd83da926f3656280b2931564ff050bf9575f1fdf9bcd7478")
    prediction = replicate.predictions.create(
        version=version,
        input={"prompt": prompt, "user_id": user_id},
        webhook="https://example.com/your-webhook",
        webhook_events_filter=["completed"])


    return {"message": "Input  sended to replicate."}

Transformation of the 'Save to AWS S3' Function

We first create a bucket using Google Cloud Storage. The images we save will be stored in this bucket. For these operations, we create the 'Firebase Cloud Storage - Saving Received Image Information as PNG' function as shown below.Firstly, a specific Google Cloud Storage bucket name, BUCKET_NAME , is defined. Then, the download_image function downloads an image from a given URL and converts it into an image object using the Pillow (PIL) library. The convert_image_to_array function converts an image object into a NumPy array using the Pillow library. The upload_to_gcs function uploads this NumPy array to Google Cloud Storage. For this process, a temporary file is created, the image is saved to this file, then it is uploaded to Google Cloud Storage, and the temporary file is deleted. Finally, the process_replicate_webhook function receives an input request, processes this request by downloading and processing the image, and uploads it to Google Cloud Storage. The image URL and token information from the Replicate API query are sent to the new cloud function we will create for sending notifications.

import os
import tempfile
from google.cloud import storage
from PIL import Image
import numpy as np
from io import BytesIO
import requests
import json


BUCKET_NAME = 'your_bucket_name'


def download_image(image_url):
    print("url:",image_url)
    response = requests.get(image_url)
    print("url:",image_url)
    image = Image.open(BytesIO(response.content))
    return image


def convert_image_to_array(image):
    return np.array(image)


def upload_to_gcs(bucket_name, user_id, image_array):
    client = storage.Client()
    bucket = client.bucket(bucket_name)


    _, temp_filename = tempfile.mkstemp()
   
    Image.fromarray(image_array).save(temp_filename, format='PNG')


    destination_blob_name = f"{user_id}/image.png"


    blob = bucket.blob(destination_blob_name)
    blob.upload_from_filename(temp_filename)


    os.remove(temp_filename)


    return f"gs://{bucket_name}/{destination_blob_name}"


def process_replicate_webhook(request):
    request_json = request.get_json()
    print("request:",request_json)


    if not request_json:
        return {"error": "Invalid JSON payload"}


    user_id = request_json['input']['user_id']
    status = request_json['status']
    image_url = request_json['output'][0]


    if not image_url or not user_id:
        return {"error": "Missing image_url or user_id"}


    image = download_image(image_url)


    image_array = convert_image_to_array(image)


    gcs_url = upload_to_gcs(BUCKET_NAME, user_id, image_array)
    print("Received Replicate webhook:", user_id, status, image_url)
    headers = {
        "Content-Type": "application/json",
    }
    push_notificaiton_payload = {
        "token": user_id,
        "image_url": image_url
    }
    response_push_notification = requests.post("your_notification_function_url", data=json.dumps(push_notificaiton_payload), headers=headers)
    return {"message": f"Image saved to {gcs_url}"}

Firebase Notification Sending Function

"Firebase Cloud Notification - Sending Result Image with Notification" code, performs the process of sending a notification using Firebase Cloud Messaging and the Flask web application framework. Firstly, a Firebase application is initialized using the credentials obtained from a firebase_key.json file to access the Firebase project. send_notification function processes the incoming HTTP request and retrieves the FCM token of the requesting device and an image URL from the request. If the FCM token is missing, it returns a 400 Bad Request status with an error message. Then, a messaging.Message object is created, which includes the FCM token, notification content, and the image URL. This notification is customized with a title, body, and a click action. Finally, the notification sending process is performed with messaging.send(message) .In case of a successful send, it returns a success message and an HTTP status of 200. In case of any error, it returns a message containing the error and an HTTP status of 500.

import json
import firebase_admin
from firebase_admin import credentials, messaging
from flask import request




cred = credentials.Certificate('firebase_key.json')
firebase_admin.initialize_app(cred, options={'senderId': 'your_sender_id'})
def send_notification(request):


  request_json = request.get_json()
  image_url = request_json.get('image_url', '')
  registration_token = request_json.get('token', '')




  if not registration_token:
    return 'Missing FCM token in the request', 400




  message = messaging.Message(
      token=registration_token,
      data={'click_action': image_url,
            'title':'Resminiz Hazır',
            'body':'Resminiz Hazır Indirmek Için Tıklayın!'},  
  )
 
  try:


    response = messaging.send(message)
    return f'Successfully sent message: {response}', 200
  except Exception as e:
    return f'Error sending message: {str(e)}', 500

In order to display the sent notification to the user on the user's side, we need to add the following 'JavaScript Notification Reception and Display to the User' code block to the firebase-messaging-sw.js file. This code represents a background service running in a web browser.The messaging.onBackgroundMessage function listens for incoming notification messages while the browser is in the background and processes the data within the payloadpayload. Among these data are the notification title (notificationTitle) and the click action (click_action). self.registration.showNotification function displays a notification in the browser. The title of the displayed notification is set as notificationTitle , and the click action is defined as click_action . self.addEventListener('notificationclick', function(event) { ... }); part defines the actions to be taken when a user clicks on a notification. The clicked notification is closed, and if the previously clicked URL (lastClickedURL) is different from the currently clicked URL, a new window is opened and directed to the payload.data.click_action URL. Additionally, the clicked URL is assigned to the lastClickedURL variable as the last clicked URL. This code listens to FCM notifications received through a background service, displays notifications, and redirects the user to a specific URL when clicked.

let lastClickedURL = '';


messaging.onBackgroundMessage(function(payload) {
    console.log('Received background message ', payload);


const notificationTitle = payload.data.title;


const notificationOptions = {
        click_action: payload.data.click_action,
        notificationTitle: notificationTitle,
    };


    self.registration.showNotification(notificationTitle, notificationOptions);
    console.log('test2', payload.data.click_action);


    self.addEventListener('notificationclick', function(event) {
        const clickedNotification = event.notification;
        clickedNotification.close();


        if (payload.data.click_action !== lastClickedURL) {
            event.waitUntil(
                clients.openWindow(payload.data.click_action)
            );
            lastClickedURL = payload.data.click_action;
        }
    });
});

Demo

Figure 6 The text information received from the user as input is sent to the Firebase Cloud Function with the Submit button.
Figure 7 The Firebase Cloud Function sends a notification to the user's device.
Şekil 8 Result of the image produced by Demo Replicate Stablediff

Conclusion


In this article, I explained the process of transitioning a web application developed using Flask between Firebase and AWS. I provided a detailed explanation of how an AWS flow was adapted using Firebase services. During the transition to Firebase, I first added Firebase Cloud Messaging integration to the Flask application. A user structure was created on the user side to send notifications, and the FCM token was obtained and passed to the Flask application. Then, the process of sending queries to the Replicate API's using Google Cloud Functions instead of AWS Lambda functions was performed on Firebase. The incoming responses were saved to Google Cloud Storage, and this process was managed using Firebase Cloud Functions. Finally, a notification was sent to the user using Firebase Cloud Messaging with the image URL and FCM token. These steps were explained in detail, and example code snippets were provided to guide through the transition process step by step.