Friday, January 18, 2019

FaaS tutorial 2: Set up Google Cloud Function

Now that we have deployed an app in FaaS tutorial 1: Start with Firebase and prepare the ground, time to spice up 🌢️ our basic app to add some back-end stuff.

What about defining a REST API to add a new record to the database. We'll use HTTP triggered functions. There are different kind of triggers for different use cases, we dig into that in the next post.

Let's start out tutorial, as always step by steps πŸ‘£!

Step 1: init function

For your project to use Google Cloud Functions (GCF), use firebase cli to configure it. Simply run the command:
$ firebase init functions

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Users/corinne/workspace/test-crud2


=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Select a default Firebase project for this directory: test-83c1a (test)
i  Using project test-83c1a (test)

=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
✔  Wrote functions/package.json
✔  Wrote functions/index.js
✔  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes
Below the ascii art 🎨 Firebase gets chatty and tells you all about it's doing.
Once you've selected a firebase project (select the one we created in tutorial 1 with the firestore setup), you use default options (JavaScript, no ESLint).

Note: By default, GCF runs on node6, if you want to enable node8, in your /functions/package.json add the following json at root level:
"engines": {
    "node": "8"
  }
You will need node8 for the rest of the tutorial as we use async instead of Promises syntax.
Firebase has created a default package with an initial GCF bootstrap in functions/index.js.

Step 2: HelloWorld

Go to functions/index.js and uncomment the helloWorld function
exports.helloWorld = functions.https.onCall((request, response) => {
 response.send("Hello from Firebase!");
});
This is a basic helloWorld function, we'll use just to get use to deploying functions.

Step 3: deploy

Again, use firebase cli and type the command:
$ firebase deploy --only functions
✔  functions[helloWorld(us-central1)]: Successful update operation. 
✔  Deploy complete!

Please note that it can take up to 30 seconds for your updated functions to propagate.
Project Console: https://console.firebase.google.com/project/test-83c1a/overview
Note you call also deploy just our functions by adding firebase deploy --only functions:myFunctionName.
If you go to the Firebase console and then in the function tab, you will find the URL where your function is available.



Step 3: try it

Since it's an HTTP triggered function, let's trying with curl:
$ curl https://us-central1-test-83c1a.cloudfunctions.net/helloWorld
Hello from Firebase!
You've deployed and tried your first cloud function. πŸŽ‰πŸŽ‰πŸŽ‰
Let's now try to fulfil the same use-case as per tutorial 1: We want an HTTP triggered function than insert 2 fields in a database collection.

Step 4: onRequest function to insert in DB

  • In function/index.js add the function below:
    const admin = require('firebase-admin');
    admin.initializeApp(); // [2]
    
    exports.insertOnRequest = functions.https.onRequest(async (req, res) => {
      const field1 = req.query.field1; // [2] 
      const field2 = req.query.field2;
      const writeResult = await admin.firestore().collection('items').add({field1: field1, field2: field2}); // [3]
      res.json({result: `Message with ID: ${writeResult.id} added.`}); // [4]
    });
    

    • [1]: import the Firebase Admin SDK to access the Firestore database and initialize with default values.
    • [2]: extract data from query param.
    • [3]: add the new message into the Firestore Database using the Firebase Admin SDK.
    • [4]: send back the id of the newly inserted record.
  • Deploy it with firebase deploy --only functions. This will redeploy both functions.
  • Test it by curling:
    $ curl https://us-central1-test-83c1a.cloudfunctions.net/insertOnRequest\?field1\=test1\&field2\=test2
    {"result":"Message with ID: b5Nw8U3wraQhRqJ0vMER added."}
    
Wow! Even better, you've deployed a cloud function that does something πŸŽ‰πŸŽ‰πŸŽ‰

Note thatif your use-case is to call a cloud function from your UI, you can onCall CGF. Some of the boiler plate around security is taken care for you. Let's try to add an onCall function!

Step 5: onCall function to insert in DB

  • In function/index.js add the function below:
    exports.insertOnCall = functions.https.onCall(async (data, context) => {
      console.log(`insertOnCall::Add to database ${JSON.stringify(data)}`);
      const {field1, field2} = data;
      await admin.firestore().collection('items').add({field1, field2});
    });
    
  • Deploy it with firebase deploy --only functions. This will redeploy both functions.
  • Test it in your UI code. In tutorial 1, step5 we defined a Create component in src/component/index.js, let's revisit the onSumit method:
    onSubmit = (e) => {
        e.preventDefault();
        // insert by calling cloud function
        const insertDB = firebase.functions().httpsCallable('insertOnCall'); // [1]
        insertDB(this.state).then((result) => { // [2]
          console.log(`::Result is ${JSON.stringify(result)}`);
          this.setState({
            field1: '',
            field2: '',
          });
          this.props.history.push("/")
        }).catch((error) => {
          console.error("Error adding document: ", error);
        });
      };
    

    In [1] we passed the name of the function to retrieve a reference, we simply call this function in [2] with json object containing all the fields we need.

Where to go from here?

In this tutorial, you've get acquainted with google function in its most simple form: http triggered. To go further into learning how to code GCF, the best way is to look at existing code: the firebase/functions-samples on GitHub is the perfect place to explore.
In next tutorials we'll explore the different use-cases that fit best a cloud function.

Stay tuned!

Thursday, January 17, 2019

FaaS tutorial 1: Start with Firebase and prepare the ground

As being an organiser of RivieraDEV, I was looking for a platform to host our CFP (call for paper). I've bumped into the open source project conference-hall wandering on twitter (the gossip 🐦 bird is useful from time to time).

The app is nicely crafted and could be used free, even better I've learned afterward, there is an hosted version! That's the one I wanted to use but we were missing one key feature: sending email to inform speaker of the deliberations and provide a way for speakers to confirm their venue.

πŸ’‘πŸ’‘πŸ’‘Open Source Project? Let's make the world 🌎 better by contributing...

On a first look, conference-hall is a web app deployed on Google Cloud Platform. The SPA is deployed using Firebase tooling and make use of firestore database. By contributing to the project, I get acquainted to Firebase. Learning something new is cool, sharing it is even better πŸ€— 🀩

Time to start a series of blogs post on the FaaS subject. I'd like to explore Google functions as a service but also go broader and see how it is implemented in open source world.

In this first article, I'll share with your how to get started configuring a project from scratch in Firebase and how to deploy it to get a ground project to introduce cloud functions in the next post. Let's get started step by step...

Step 1️⃣: Initialise firebase project

Go to Firebase console and Create a firebase project, let's name it test

Step 2️⃣: Use Firestore

  • In left-hand side menu select Database tab, then click Create Database. Follow Firestore documentation if in trouble. The Firebase console UI is quite easy to follow. Note Firestore is still beta at the time of writing.
  • Choose Start in test mode then click Enabled button.
You should be forwarded to the Database explorer, you can now add a new collection items as below:

Step 3️⃣: Boostrap app

We use create-react-app to get an initial react app
npx create-react-app test-crud
cd test-crud
npm install --save firebase
and then we've added firebase SDK.

Insert firebase config

  • We use react-script env variable support
  • In env.local copy variable from firebase console
  • In src/firebase/firebase.js, read env variable and initialise
    const config = {
      apiKey: process.env.REACT_APP_API_KEY,
      authDomain: process.env.REACT_APP_AUTH_DOMAIN,
      databaseURL: process.env.REACT_APP_DATABASE_URL,
      projectId: process.env.REACT_APP_PROJECT_ID,
      storageBucket: process.env.REACT_APP_STARAGE_BUCKET,
      messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
    };
    firebase.initializeApp(config);
    
    firebase.firestore().settings(settings);
    
This way you keep your secret safe, not committed in your code 🀫🀫🀫

Step 4️⃣: Add routing

npm install --save react-router-dom
mkdir src/components
touch src/components/create.js
And define route in src/index.js
ReactDOM.render(
  <Router>
    <div>
      <Route exact path='/' component={App} />
      <Route path='/create' component={Create} />
    </div>
  </Router>,
  document.getElementById('root')
);
In the root path, we'll display the list of items. In the the Create component we'll define a form component to add new items to the list.

Step 5️⃣: Access Firestore in the app

Let's define the content of Create component in src/component/index.js
class Create extends Component {
  constructor() {
    super();
    this.ref = firebase.firestore().collection('items'); // [1] retrieve items reference
    this.state = {
      field1: '',
      field2: '',
    };
  }
  onChange = (e) => {
    const state = this.state;
    state[e.target.name] = e.target.value;
    this.setState(state);
  };
  onSubmit = (e) => {
    e.preventDefault();
    const { field1, field2 } = this.state;
    this.ref.add({                                     // [2] Insert by using firestore SDK
      field1,
      field2,
    }).then((docRef) => {
      this.setState({
        field1: '',
        field2: '',
      });
      this.props.history.push("/")
    }).catch((error) => {
      console.error("Error adding document: ", error);
    });
  };

  render() {
    const { field1, field2 } = this.state;
    return (
      <div>
        <div>
          <div>
            <h3>
              Add Item
            </h3>
          </div>
          <div>
            <h4>Link to="/" >Items List</Link></h4>
            <form onSubmit={this.onSubmit}>
              <div>
                <label htmlFor="title">field1:</label>
                <input type="text" name="field1" value={field1} onChange={this.onChange}  />
              </div>
              <div>
                <label htmlFor="title">field2:</label>
                <input type="text" name="field2" value={field2} onChange={this.onChange} />
              </div>
              <button type="submit">Submit</button>
            </form>
          </div>
        </div>
      </div>
    );
  }
}

export default Create;
It seems a lot of code but the key points are [1] and [2] where we use the firestore SDK to add a new item in the database directly from the client app. the call in [2] is going to be revisited in next blog post to make usage of cloud function.

Step 6️⃣: Deploy on firebase

So we build a small test app accessing firestore DB let's deploy it on the cloud with Firebase tooling πŸ‘ !
  • Start running a production build
    $ npm run build
    
  • Install firebase tools
    $ npm install -g firebase-tools
    $ firebase login
    
  • Init function
    $ firebase init
    
    • Step 1: Select the Firebase features you want to use: Firestore Hosting. For now we focus only on deploying ie: hosting the app
    • Step 2: Firebase command-line interface will pull up your list of Firebase projects, where you pick firebase-crud.
    • Step 3: Keep the default for the Database Rules file name and just press enter.
    • Step 4: Pay attention to the question about public directory, which is the directory that will be deployed and served by Firebase. In our case it is build, which is the folder where our production build is located. Type “build” and proceed.
    • Step 5: Firebase will ask you if you want the app to be configured as a single-page app. Say "yes".
    • Step 6: Firebase will warn us that we already have build/index.html. All fine!
  • deploy!
    $ firebase deploy
    ...
    ✔  Deploy complete!
    
        Project Console: https://console.firebase.google.com/project/test-83c1a/overview
        Hosting URL: https://test-83c1a.firebaseapp.com
    


Where to go from there?

In this blog post you've seen how to configure and deploy an SPA on firebase and how to set up a Firestore DB. Next blog post, you'll see how to write you first Google Cloud Function. Stay tuned.