Flutter With A Firebase NoSQL and Cloud Functions Backend

Firebase is Google’s Mobile Development Platform, and it’s a great collection of services for a mobile app, or even website. For this scenario, I am specifically looking at using Cloud Functions as my API and Firestore as my NoSQL database. It will look something similar to this.

Note: Any recommendations I make here are general, and not all mobile applications would suit this setup.

NoSQL vs Relational Databases

If you already have experience with NoSQL databases, you can skip this section, as I’ll go through the different way of thinking required for NoSQL.

In relational databases you think of tables and rows. In NoSQL we think of collections and documents. We start with a top root collection, that contains documents. These documents are filled with key/value pairs, much like a JSON file. Documents in a collection do not need to be the same, but they are generally similar.

A document may look like this.

{
   "name" : "company name",
   "address" : {
     "street" : "1 plaza way",
     "zipcode" : 123456
   }
}

In this setup, as an example, you may have a list of companies, and a list of employees at each company. The collection and documents may look something like this. company/company/employees/person. In terms of collections and documents, it is collection/document/collection/document.

An example of the different way of thinking for NoSQL, comes in what data to store. If you wanted to get a count of all employees at each company, you would have to read each company and each staff document. This would count as 1 read per document, even if you were just counting them. The better way to do this, is to store an employee count in your company document, and increment or decrement it when an employee document is added. Then you only need to read the company file and that particular count property.

Coming from a relational background I know the initial reaction may be similar to, “What are you doing, you are duplicating data”. But NoSQL is not prioritizing storage or writes, it’s prioritizing reads, which in most cases is the most common scenario your app will perform. It may require a bit of extra code on the client side, or more writes to update items, but this means the reads are faster and simpler.

If you want a great conceptual overview of what Firestore has to offer and NoSQL in general , I highly recommend Get to know Cloud Firestore. It’s a number of video’s from the Firebase team (props to Todd) going through how everything works, without getting caught up in the details.

Firebase

If you don’t have a Firebase account:

  1. Go to https://console.firebase.google.com/
  2. Create a new account if you don’t have one. You can select the Free plan to start with.
  3. Press Add project.

Once complete you should be in the console at a url similar to https://console.firebase.google.com/project/<projectname>/overview.

Firestore

  1. Select Database from the sidebar in your project and Create Database
  2. Start in Locked Mode, as only our Cloud Function will access it anyway, there is no need for 3rd party access at this time.
  3. For our example I am creating company/document, then adding a field called name with a value of My Company

This is our basic setup that we will use in the Cloud Functions example below.

Cloud Functions

Firebase’s cloud functions are a way of running of code without thinking about the server it’s running on. I recommend using TypeScript as the language to write these functions in, and personally I use VSCode as my editor. There is a full guide to setting up your environment at Functions – Get Started. For a quick start guide follow these steps.

  1. Install Node.js
  2. Run npm install firebase-functions@latest firebase-admin@latest --save
  3. Run npm install -g firebase-tools
  4. Create a new directory where you want to store your source code for your functions, and navigate there in the command line.
  5. Run firebase login and login with the same account details as the account you used above.
  6. Run firebase init functions this will create a new project for you to write your functions in. As mentioned previously I prefer TypeScript, but you can select JavaScript if you prefer.
  7. Launch your editor. If using VSCode, type code .

In functions > src > index.ts is your access point for running functions. We are going to setup an HTTP trigger, to respond to a request.

At the beginning we import firebase-functions and firebase-admin and initialize the app. The great thing about it all being in the one project and that one project can only have one Firestore database, we can use functions.config().firebase to connect to Firestore, with no other configuration needed. Firebase does the rest automatically.

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp(functions.config().firebase);

Creating the function to handle the Http Trigger we export a function as such.

export const company = functions.https.onRequest((request, response) => {

});

We need to connect to Firestore and send back a response, via the response object. In addition to this, I’m going to get the companyId from the request path.

export const company = functions.https.onRequest((request, response) => {    
    const companyId = request.path;

    admin.firestore().collection('company').doc(companyId).get().then(result => {
        response.send(result.data());
    }).catch(error => {
        response.send(error);
    });
});

Now lets deploy by running firebase deploy. It will give you a URL to copy into your browser to test the function. Add the companyId from your document, and see if it returns the correct information.

I will go to a URL similar to http://us-central1-myprojectname.cloudfunctions.net/company/AgINAg0AAgcIDgMGBAoEDw and receive this response.

{"name":"My Company"}

You can use request.method to determine if its a POST or GET. You could also use request.query if you want to use query parameters instead. Note that this also isn’t secured. Authentication will be covered in another post. Additionally, each function by default has a 60 second timeout, hence we must return a response in that time.

Flutter API

Connecting to Cloud Functions is exactly like any HTTP call. For more information on HTTP calls in Flutter, read Http Connections and Retries. As a quick demonstration, here is the API call I would have made.

import 'package:http/http.dart';
import 'dart:convert';

class Company {
  String name;
  Company.fromJson(Map json)
  {
    this.name = json['name'];
  }
}

Future _click() async {
    var data = await get(
        'http://us-central1-myprojectname.cloudfunctions.net/company/AgINAg0AAgcIDgMGBAoEDw');
        var company = new Company.fromJson(json.decode(data.body));
    setState(() {
      _data = company.name;
    });
}

Summary

With this simple setup you can quickly build an API and backend without worrying about any server deployments. The pricing is great for startups (it has a very generous free tier), especially if you design your API and NoSQL database with reads and writes in mind. To me this helps ensure faster API call returns and a better user experience on your app.

Share on
© 2019 Adam Pedley