Building an API with Firebase

  1. Node 10.10 installed
  2. NVM installed with the instructions here
  3. A Google Account
  4. Postman installed
  5. Firebase CLI installed with a run of the terminal command npm install -g firebase-tools

The Basics

To start, I wanted to go over some basic concepts about what APIs are and how the technologies work. This is completely introductory, so feel free to skip this section if you are already familiar.

  • POST = creating or updating data
  • PUT = updating data
  • DELETE = deleting data

Firebase

Initial Setup

To start, go to the Firebase Console by clicking the link here. You should see something like the following:

Writing Code

In this step, we’ll assume you already have the Firebase CLI on your machine. I recommend following the instructions here to get that setup if you haven’t done so already.

  • Select yes for linting
  • select yes to install dependencies
  • and you’re good to go!
  • node_modules folder
  • package-lock.json
  • package.json

Serverless APIs and your First Endpoint

Firebase Functions enables you to use the ExpressJS library to host a Serverless API. Serverless is just a term for a system that runs without physical servers. This is a bit of a misnomer because it technically does run on a server, however, you’re letting the provider handle the hosting aspect. Traditional APIs would require you to setup a server either in the cloud or on prem that can host your application. This means that developers would have to be in charge of OS patches and alerts, etc. In the world of Serverless, you don’t have to worry about anything but your code. This is one of the coolest parts of Firebase!

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({ origin: true }));

app.get('/hello-world', (req, res) => {
return res.status(200).send('Hello World!');
});

exports.app = functions.https.onRequest(app);
npm i express
npm i cors
  • firebase-admin is the firebase admin SDK that enables your functions to control all of your backend Firebase services
  • express is the ExpressJS library that lets you create a server instance
  • cors is an npm module that allows your functions to run somewhere separate from your client. The app.use is just enabling CORS for your express server instance.
[<------domain---->]/[<-app id--->]/[<-zone-->]/app/[<-endpoint->]
http://localhost:5000/fir-api-9a206/us-central1/app/create
[<-zone + app id + cloudfunctions.net->]      /app/ [<-endpoint->]
https://us-central1-fir-api-9a206.cloudfunctions.net/app/hello-world

Database Calls

For the API we’re building, it’s just operations for a list of items. We’re just going to setup Create, Read, Update, and Delete (or CRUD) functions for this list of items.

var serviceAccount = require("./permissions.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://fir-api-9a206..firebaseio.com"
});
const db = admin.firestore();
// create
app.post('/api/create', (req, res) => {
(async () => {
try {
await db.collection('items').doc('/' + req.body.id + '/')
.create({item: req.body.item});
return res.status(200).send();
} catch (error) {
console.log(error);
return res.status(500).send(error);
}
})();
});

Building out the DB endpoints

So now that we have the “create” endpoint setup, let’s go ahead and add the rest of the operations. We’re going to add the following:

  • /read-items = read all items (total collection)
  • /update-item/:item_id = update an item
  • delete-item/:item_id = delete an item
// read item
app.get('/api/read/:item_id', (req, res) => {
(async () => {
try {
const document = db.collection('items').doc(req.params.item_id);
let item = await document.get();
let response = item.data();
return res.status(200).send(response);
} catch (error) {
console.log(error);
return res.status(500).send(error);
}
})();
});

// read all
app.get('/api/read', (req, res) => {
(async () => {
try {
let query = db.collection('items');
let response = [];
await query.get().then(querySnapshot => {
let docs = querySnapshot.docs;
for (let doc of docs) {
const selectedItem = {
id: doc.id,
item: doc.data().item
};
response.push(selectedItem);
}
});
return res.status(200).send(response);
} catch (error) {
console.log(error);
return res.status(500).send(error);
}
})();
});

// update
app.put('/api/update/:item_id', (req, res) => {
(async () => {
try {
const document = db.collection('items').doc(req.params.item_id);
await document.update({
item: req.body.item
});
return res.status(200).send();
} catch (error) {
console.log(error);
return res.status(500).send(error);
}
})();
});

// delete
app.delete('/api/delete/:item_id', (req, res) => {
(async () => {
try {
const document = db.collection('items').doc(req.params.item_id);
await document.delete();
return res.status(200).send();
} catch (error) {
console.log(error);
return res.status(500).send(error);
}
})();
});

Deploying

So now we have a fully functional CRUD API. We’re ready to deploy! If you’re developing an enterprise application (or one that you will be maintaining) it is typical to build out a Continious Integration Continuous Deployment (CICD) pipeleine. This is basically a set of steps that are automated for delivering your application into production.

Connecting a Frontend

So now that we’ve built out the API, I wanted to showcase what it would look like if you were to use it in a client application.

[<-zone + app id + cloudfunctions.net->] / app /    [<--endpoint-->]
https://us-central1-fir-api-9a206.cloudfunctions.net/app/hello-world
async selectAll() {
try {
console.log(environment.readAll);
console.log('calling read all endpoint');

this.exampleItems = [];
const output = await fetch(environment.readAll);
const outputJSON = await output.json();
this.exampleItems = outputJSON;
console.log('Success');
console.log(outputJSON);
} catch (error) {
console.log(error);
}
}

// really this is create but the flow is that
// click the "create item" button which appends a blank value to the array, then click save to actually create it permanently
async saveItem(item: any) {
try {
console.log(environment.create);
console.log('calling create item endpoint with: ' + item.item);

const requestBody = {
id: item.id,
item: item.item
};

const createResponse =
await fetch(environment.create, {
method: 'POST',
body: JSON.stringify(requestBody),
headers:{
'Content-Type': 'application/json'
}
});
console.log('Success');
console.log(createResponse.status);

// call select all to update the table
this.selectAll();
} catch (error) {
console.log(error);
}
}

async updateItem(item: any) {
try {
console.log(environment.update);
console.log('calling update endpoint with id ' + item.id + ' and value "' + item.item);

const requestBody = {
item: item.item
};

const updateResponse =
await fetch(environment.update + item.id, {
method: 'PUT',
body: JSON.stringify(requestBody),
headers:{
'Content-Type': 'application/json'
}
});
console.log('Success');
console.log(updateResponse.status);

// call select all to update the table
this.selectAll();
} catch (error) {
console.log(error);
}
}

async deleteItem(item: any) {
try {
console.log(environment.delete);
console.log('calling delete endpoint with id ' + item.id);

const deleteResponse =
await fetch(environment.delete + item.id, {
method: 'DELETE',
headers:{
'Content-Type': 'application/json'
}
});

console.log('Success');
console.log(deleteResponse.status);

// call select all to update the table
this.selectAll();
} catch (error) {
console.log(error);
}
}

Closing Thoughts

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store