We're planting a tree for every job application! Click here to learn more

Firebase and Ionic React Chat (Fire Chat) Part 3: Firebase Functions

King Somto

24 Aug 2021

7 min read

Firebase and Ionic React Chat (Fire Chat) Part 3: Firebase Functions
  • Node.js

Imploying firebase functions### Introduction

In this article, we will be continuing our series of articles on creating a chat app by employing firebase functionality with React-Ionic. You can follow up this article from here. Today we will be working with firebase cloud functions to perform some operations that we wouldn't want to perform on the frontend.

What is Firebase Cloud Functions

Firebase Cloud Functions is a framework that enables users to run javascript/typescript code triggered by HTTPS requests or events. An easy example would be adding an item to a collection, let's say a collection called cart, we would like to know if the item added to the cart is still available, the firebase cloud function is able to check the database to see if the item is still available and either return an “Item not available response” or add the item to cart collection.

The flow would be

Screen Shot 2021-08-22 at 10.01.53 PM.png

Use cases

Firebase cloud functions come in handy for things we would normally like to do on the backend if we were using a traditional stack like MEAN or MERN stack, mostly confidential things we wouldn't want the user to have access to or basically system-intensive processes that can slow the user's devices.

An example of some things you may want to do with firebase functions would be to:

  • Process user details when saving them to user collection
  • Handling sensitive operations you most likely wouldn't want the frontend to perform an example would be operations making use of secret keys.
  • Sending emails to users to notify them of recent logins.

Set up

Setting up the firebase cloud functions is straightforward. We would be focusing on running an instance of firebase on our local (firebase emulator) development device (firebase cloud functions is unfortunately not a free service 😭).

Step 1

We will be installing the firebase functions using node package installer, open the terminal and type

npm install -g firebase-tools

Note you may need to run this command with sudo or use the admin mode for windows users.

Now type firebase in the terminal to test if the package has been installed.

Step 2

Run firebase init command

This sets up firebase in the system following the prompts showed by the system, a very detailed explanation on setting up the firebase emulator for firebase cloud function can be found here.

Step 3

Run firebase emulators:start in the directory firebase was just set up

The output would be Screen Shot 2021-08-17 at 1.50.34 PM.png Now we have an instance of firebase emulator running which comes with firestore, firestorage, and of course firebase-cloud functions.

Our specific use case#### Case 1

We have done some cool things with out of the box functions by firebase but our app is getting more complex and we would like to add more functionality to some things like our signup process, the current implementation lets users create accounts and adds a username in the user collection our current way of knowing if the username has been used already is okay but we can always make it better, so here we would be using firebase functions, the flow would be when the function is called we first check if the username or email exist if it doesn't we go ahead and call firebase signup functions and add the user to the app/DB, else we return an error response to the user saying the user name or email has been used

Case 2

We would like to add more laws to create a password on our platform eg we want the passwords to have

  • 8 Characters
  • Caps and non-caps characters
  • Numbers
  • A special character
  • And last but not least 2 drops of blood of a chicken (🩸🩸) because why not

Code implementation

The code implementation involves 2 parts the frontend and the backend cloud functions are written in node. I chose Ts for my project set up (Why? Because if it's not hard then why do it?😄), we have to build our project with every change with

npm run build.

This updates our JS file.

Side note 😩

So for those that have previous Ts experience, you would know Ts is basically not running on the system as it is compiled down to JS and then ran on the node V8 engine, so we need to run our build process to create a new JS file.

The code

import * as functions from "firebase-functions";
 
export const createAnAccount = functions.https.onCall(
   async ({ email, password, userName }, context) => {
 
     console.log({email, password, userName})
     return  {
	       msg:’Done’
    };
});

Breaking the code into smaller pieces

Part 1:

 
import * as functions from "firebase-functions";
 

Setting up our import statements to get the firebase functions from the firebase library.

Part 2:

export const createAnAccount = functions.https.onCall(
   async ({ email, password, userName }, context) => {
 
   /////empty code here
});		

This part of the codebase creates a function that is exposed to our frontend application.

Part 3

///////
{
 
   console.log({email, password, userName})
 
   return  {
	       msg:’Done’
    };
}
///////

This last part of the code logs the param data passed with the context variable.

The second part of this would be to add the needed code in our frontend codebase, this lets us access the local emulator instead of the firebase live server.

Changing the frontend code

For us to use the emulator we have to tell our app to check for the hostname our app is running on, in our test environment you are probably running it on http://localhost:3000 or something similar so our hostname here is localhost, so we tell our app to initialize with the local instead(not the best idea though but we should have a cleaner iteration later).

 
// eslint-disable-next-line no-restricted-globals
if (location.hostname === 'localhost') {
 firebase.firestore().useEmulator('localhost', 8080);
 firebase.functions().useEmulator('localhost', 5001);
 
 firebase
   .auth()
   .useEmulator('http://localhost:9099/', { disableWarnings: true });
}

We also need to expose our new signup function to all parts of our codebase, go to the config file and append the code below to the end of the file.

export const allFunctions = {
 createAnAccount: firebase.functions().httpsCallable('createAnAccount'),
};

Then in our signup.jsx we change our signup onSubmit function to

 
 
onSubmit={async e => {
                   e.preventDefault()
                   try {
 
  			const {data} = await allFunctions.createAnAccount({
                           email: inputHold.current.email,
                           password: inputHold.current.password,
                           userName: inputHold.current.userName
                       })
 
                       if (data.error || !data.token) {
                           throw new Error("Error signing up")
                       }
 
                       auth.signInWithCustomToken(data.token)
 
                   } catch (error) {
 
                       console.log(error)
                      
                   }
 
               }} 

Time to test !!!!!

Open your browser and navigate to http://localhost:4000/logs here we would see an interface similar to, Screen Shot 2021-08-21 at 2.39.50 AM.png

This is the logs interface that updates whenever a function or an event is called or just a simple console.log().

Our code above is called whenever a new user is added to the user collection, so let's open up our signup page and signup a new user.

Screen Shot 2021-08-22 at 10.27.17 PM.png

Notice that we are able to log the user data as we specified in our codebase, so everything is set up to start the hard part.

Let us change our signup flow. Screen Shot 2021-08-22 at 10.02.14 PM.png

From the process above we can see that this has a big potential flaw, here the user object is created before we verify if the username exist or not, this can lead to some users sharing usernames saved because they chose to use a username already in use, this can be changed on the frontend but putting this as a cloud function is cleaner and less prone to hacks.

The flow we would be using now would be

Screen Shot 2021-08-22 at 10.02.20 PM.png

So let's edit our createAccount cloud function to follow this process.

import * as functions from "firebase-functions";
import * as admin from 'firebase-admin';
 
 
 
admin.initializeApp()
 
export const createAnAccount = functions.https.onCall(
   async ({ email, password, userName }, context) => {
       try {
           ////check if username exist
           const usersRef = admin.firestore().collection('users')
           const checkUserNameIsUsed = await (await usersRef.where('userName', '==', userName).get())
           const checkUEmailIsUsed = await (await usersRef.where('email', '==', email).get())
 
           if (checkUserNameIsUsed.size > 0 || checkUEmailIsUsed.size > 0) {
               ///user exist  with theis username in the collection
               return {
                   error: true,
                   msg: 'user with username exist'
               }
           }
 
           const { uid } = await admin.auth().createUser({
               email, password
           })
 
           ///create users collection now
           await usersRef.doc(`${uid}`).set({
               userName, email
           })
 
           const token =  await admin.auth().createCustomToken(uid)
 
           console.log({ uid, userName, email,token })
 
 
           return { uid, userName, email,token }
 
 
       } catch (error) {
           return { error: 'Auth Error' };
       }
 
   },
)

Let's break the code down

Part 1

   const usersRef = admin.firestore().collection('users')
           const checkUserNameIsUsed = await (await usersRef.where('userName', '==', userName).get())
           const checkUEmailIsUsed = await (await usersRef.where('email', '==', email).get())
 
           if (checkUserNameIsUsed.size > 0 || checkUEmailIsUsed.size > 0) {
               ///user exist  with theis username in the collection
               return {
                   error: true,
                   msg: 'user with username or email exist'
               }
           }

This part of the code checks if the email or username has been used before and if it has it returns an error.

           const { uid } = await admin.auth().createUser({
               email, password
           })
 
           ///create users collection now
           await usersRef.doc(`${uid}`).set({
               userName, email
           })
           const token =  await admin.auth().createCustomToken(uid)
 
           console.log({ uid, userName, email,token })
 
 
           return { uid, userName, email,token }

The next part of the code performs the signup and adds the user document to the collection returning the user data needed on the client-side.

Now that's done we can move to scenario 2 which involves password checks on the backend before creating a user, to perform this we would use a simple regex function to perform this operation.

Reviewing the laws we listed earlier

  • 8 Characters
  • Caps and non-caps characters
  • Numbers
  • A special character
  • And last but not least 2 drops of blood of a chicken (🩸🩸) Am sure you thought I was kidding about the blood earlier.

Let's edit the codebase to support this


/////
 
           var strongRegex =/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})(?=.*🩸🩸)/.test(`${password}`)
 
         
           if (checkUserNameIsUsed.size > 0 || checkUEmailIsUsed.size > 0  ||  !strongRegex ) {
               ///user exist  with theis username in the collection
               return {
                   error: true,
                   msg: 'user with username exist'
               }
 
           }
 
////

Conclusion

Firebase functions are very useful, to say the least, we were able to separate the frontend from doing things like validating the signup process for users, also we found out how to use firebase-cloud functions to query Databases and build on top of that. We will be using more firebase functions going further into the series, to run functions like processing messages sent, so stay tuned!

Did you like this article?

King Somto

Dev

See other articles by King

Related jobs

See all

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

WorksHub

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
hello@works-hub.com

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2024 WorksHub

Privacy PolicyDeveloped by WorksHub