Wednesday, December 6, 2017

Express app using firebase HTTP Trigger

if you want to know about firebase and firebase cloud functions before jumping into this article, please visit the following links.

1) Firebase RealTime Database
2) Firebase Authentication Service
3) Why we need cloud functions
4) Introduction to cloud functions

HTTP Trigger:
It will be invoked based on the rest call from the client. You can perform the pre validation of data before writing into db, connect to third party services like git, slack etc.

Express App using HTTP Trigger:

Please checkout the following projects from github if you want to play around.

Web Client:
1) https://github.com/fullstacktechnos/firebase-frontend

Firebase Functions:
2) https://github.com/fullstacktechnos/firebase-functions


* Following code sends a message from the web app using http post.

firebase.auth().currentUser.getToken().then(function(token) {
                    let restEndPoint = 'https://us-central1-testapp.cloudfunctions.net/app/writemsg';
                    console.log('Sending request to', restEndPoint, 'with ID token in Authorization header.');
                    var req = new XMLHttpRequest();
                    req.onload = function() {
                        console.log(req.responseText);
                    }.bind(this);
                    req.onerror = function() {
                        console.log('There was an error');
                    }.bind(this);
                    req.open('POST', restEndPoint, true);
                    req.setRequestHeader('Authorization', 'Bearer ' + token);
                    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                    req.send("message="+content);
                }.bind(this))
                .catch(function(err) { console.log('No token found')});


- In the above code we are passing a message using http post request to the rest endpoint 'writemsg'.
- User is sending the message after he/she login. So we can pass the token in Authorization header for verification in backend.
- https://us-central1-testapp.cloudfunctions.net/app/ is the rest api base url. You can find it in firebase project under Functions or in the console at the time of deployment.


* Following cloud function code

- creates an express app
- invoke the http trigger when the rest api is called
- validate the user token
- go to the http post handler
- validate the input data and write into the realtime database.


const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

//http trigger
const express = require('express');
const cookieParser = require('cookie-parser')();
const cors = require('cors')({ origin: true });
const app = express();

const validateFirebaseIdToken = (req, res, next) => {
  console.log('Check if request is authorized with Firebase ID token');

  if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
    !req.cookies.__session) {
    console.error('No Firebase ID token was passed as a Bearer token');
    res.status(403).send('Unauthorized');
    return;
  }

  let idToken;
  if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
    console.log('Found "Authorization" header');
    // Read the ID Token from the Authorization header.
    idToken = req.headers.authorization.split('Bearer ')[1];
  } else {
    console.log('Found "__session" cookie');
    // Read the ID Token from cookie.
    idToken = req.cookies.__session;
  }

  admin.auth().verifyIdToken(idToken).then(decodedIdToken => {
    console.log('ID Token correctly decoded', decodedIdToken);
    req.user = decodedIdToken;
    next();
  }).catch(error => {
    console.error('Error while verifying Firebase ID token:', error);
    res.status(403).send('Unauthorized');
  });
};

app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);

app.post('/writemsg', (req, res) => {
  if (req.body.message === undefined) {
    res.status(400).send('No message defined!');
  } else {
    console.log('input data :: ' + req.body.message);
    
    //Insert message to Realtime Database node "messages"
    admin.database().ref('messages').push({
      type: 'Rest',
      description: req.body.message
    })
    .then(() => res.status(200).send('Msg updated Successfully'))
    .catch( error => res.status(401).send('Not able to update the msg'))
  }
});

exports.app = functions.https.onRequest(app);

* Note:
To know more please visit the following link.
https://firebase.google.com/docs/functions/http-events

Useful Reads:
http://www.fullstacktechnos.com/2017/12/firebase-realtime-database-trigger.html
http://www.fullstacktechnos.com/2017/12/firebase-authentication-trigger.html

Firebase Realtime Database Trigger

if you want to know about firebase and firebase cloud functions before jumping into this article, please visit the following links.

1) Firebase RealTime Database
2) Firebase Authentication Service
3) Why we need cloud functions
4) Introduction to cloud functions

Realtime Database Trigger :

We can invoke realtime db trigger in response to any database events from the client.
From your web or mobile app when ever there is a database event, cloud realtime db trigger can be invoked. It is a post db operation.

For ex: User sends a msg called "stupid message" from the app to write into message node. If a db trigger is attached to the message node, it can listen to the onWrite() event, read the incoming msg, change to "Wonderful message" and stores back.

Please checkout the following projects from github if you want to play around.

Web Client:
1) https://github.com/fullstacktechnos/firebase-frontend

Firebase Functions:
2) https://github.com/fullstacktechnos/firebase-functions


** Following function from the web client will write a message to firebase realtime database having node "messages"

var ref = firebase.database().ref('messages');
                var data = {
                    type: 'Generic',
                    description: 'Stupid msg'
                }
                ref.push(data);



** Following cloud function in firebase will be invoked, sanitized the data and save it back.

//db trigger
function sanitize(msgDescription) {
  var sanitisedContent = msgDescription;
  sanitisedContent = sanitisedContent.replace(/\bstupid\b/ig, "wonderful");
  return sanitisedContent;
}
exports.sanitizeMessage = functions.database.ref('/messages/{mid}').onWrite(event => {
  const msg = event.data.val();

  //prevent calling same trigger again.
  if (msg.sanitized) {
    return;
  }
  console.log("Sanitizing new message : " + event.params.mid);
  console.log(msg);
  msg.sanitized = true;

  msg.description = sanitize(msg.description);
  return event.data.ref.set(msg);
})

*Please check the github project mention above for the working copy.

- The above db trigger (sanitizeMessage) will be invoked every time there is a new message write.
- It reads the incoming message after the message written into the db(as db triggers are post operation trigger), check the word "stupid" in it, if present change it to "wonderful" and writes back.

- If we write back to the same node again the same db trigger will be invoked which will continue for infinite loop. So to avoid the infinite looping we are checking at the beginning if the message is sanitized. If it is then we don't need to proceed.

- Its always better to return a promise from the trigger as mentioned in the last line of "sanitizeMessage" function. This way google function won't be killed until the promise is either resolved or rejected. So we can wait for the db operation to complete.

Event Handlers:
onWrite(), which triggers when data is created, destroyed, or changed in the Realtime Database.
onCreate(), which triggers when new data is created in the Realtime Database.
onUpdate(), which triggers when data is updated in the Realtime Database.
onDelete(), which triggers when data is deleted from the Realtime Database.

**Note:
For more information please visit:
https://firebase.google.com/docs/functions/database-events

Firebase Authentication Trigger

If you want to familiar with what is firebase and firebase cloud functions before jumping into this article please visit the following links.

1) Firebase RealTime Database
2) Firebase Authentication Service
3) Why we need cloud functions
4) Introduction to cloud functions

We can trigger auth cloud functions that can run in response to user creation or deletion.

For ex: From your web or mobile app when ever there is a user signup you can invoke your auth trigger in the backend, which is a firebase cloud function, that will send a welcome email to the user. You don't need to duplicate sending welcome email code in your web and mobile apps. Common business logic can move to the server side.

Please checkout the following projects from github if you want to play around.

Web Client:
1) https://github.com/fullstacktechnos/firebase-frontend

Firebase Functions:
2) https://github.com/fullstacktechnos/firebase-functions

First one is a frontend web app using which you can send request to your firebase backend.
Second one is your firebase cloud functions which will be triggered in specific conditions.

If you have issues setting up cloud function project please check the link Introduction to cloud functions above


** Following function from the web client will send a user registration request to firebase:


firebase.auth().signInWithEmailAndPassword(email, password)
                .then(function(result) {
                    console.log(result);
                })
                .catch(function(error) {
                    console.log('signIn error', error);
                })



** Following cloud function in firebase will be invoked and performed few extra work and save it.


//Auth trigger
exports.createUserAccount = functions.auth.user().onCreate(event => {
  const uid = event.data.uid;
  const email = event.data.email;
  const photoUrl = event.data.photoUrl || 'http://lorempixel.com/400/200/';
  const newUserRef = ref.child(`/users/${uid}`);

  return newUserRef.set({
    photoUrl: photoUrl,
    email: email
  })
})


- The above auth trigger will be invoked every time there is a new user creation in firebase.
- In the above function you can see that its checking for photo url which should come as a input from client, if it is not present then trigger is adding a default photo url.
- Once the data is ready trigger is writing to the child node(/users/useruid/) of user pointing to user id.
- Firebase stores User data in its own place which we cant change but we can store our own data into Real time database.

**Note:
For more information please visit:
https://firebase.google.com/docs/functions/auth-events

Useful Read :
http://www.fullstacktechnos.com/2017/12/firebase-realtime-database-trigger.html
http://www.fullstacktechnos.com/2017/12/express-app-using-firebase-http-trigger.html