Setup Firestore and Cloud Function
GCPThe application #
The idea of the application is very simple. launch the application twice (it's now offline, I did setup a website for testing purpose that i turned down), click on the big rectangle, points will be shown on both screen. Points will be store on the Firestore database, and will be send back in realtime in online browsers.
The setup summary #
A quick overview of technology we will playing with in this post.
- vue.js application.
- Firebase hosting.
- Firestore Database.
- Firestore realtime callback.
- Cloud functions for backend.
- FIrebase CLI
Setup firebase the project #
Firestore from the Google Cloud Platform is import and synchronized within a project in the Firebase platform. There are the same. Cloud functions as well. We will use the firebase tools to deploy functions and host the website. You can quickly add a Google Cloud platform project as seen in the following screenshot.
First, we need a firebase project, or just import a Google Cloud Platform project.
Let's register a new app, and setup the Firebase hosting in the same time.
Create a quick vue bootstrap with vue-cli.
vue create tracker
You need to install the firebase tools.
npm install -g firebase-tools
We initialize the project source code as seen in the 4th step.
firebase login
firebase init
Select hosting and functions, and database if not already running.
Leave options as default values by pressing enter except for the the public directory that would be dist.
Here my example of the firebase.json.
{
"hosting": {
"public": "dist",
"site": "niptracker",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}
Let's deploy the application, first build it and deploy.
npm run build
firebase deploy
This will deploy the website and cloud functions as well once we will setup some.
At this point, you should have a default vue home page one your web.app hosting like this.
Get Firestore credentials #
On your firebase console, go to projects setting and get configuration code.
While we are here, setup the write read like this for development purpose.
We initialize a first collection users.
Manage some points on the database #
Backend with cloud functions. #
Let's setup some backend funcions that will update the database. This is on the functions folder, we will setup like the official documentation, and add firebase on the package.json file.
We create to functions, for initialize the points array, and the second one for inserting data.
const functions = require('firebase-functions');
const firebase = require("firebase");
// Required for side-effects
require("firebase/firestore");
const firebaseConfig = {
apiKey: "AIzaSyBIjKVw0VqTQlsutpTyd-pwCAVAmw4zVRA",
authDomain: "nipsandbox.firebaseapp.com",
databaseURL: "https://nipsandbox.firebaseio.com",
projectId: "nipsandbox",
storageBucket: "nipsandbox.appspot.com",
messagingSenderId: "200494519109",
appId: "1:200494519109:web:4b34dcdc4bd8d6ec"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
var db = firebase.firestore();
/**
* INIT USER
*/
exports.initUser = functions.https.onRequest((request, response) => {
db.collection("users").doc(request.body.color).set(
{coords: []}
).then(function() {
response.send("Document successfully written!");
})
.catch(function(error) {
response.send(error);
});
});
/**
* ADD TICK
*/
exports.addTicks = functions.https.onRequest((request, response) => {
db.collection("users").doc(request.body.color).update(
{coords: firebase.firestore.FieldValue.arrayUnion(request.body.coords)}
).then(function() {
response.send("Document successfully written!");
})
.catch(function(error) {
response.send(error);
});
});
Test the backend #
We could deploy, but let's just check if we are good to go. On the package.json inside the functions folder, there is a npm run mode to launch a local server for testing purpose.
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
Let's run num
npm install --save firebase
npm run serve
This will give you something to test with.
Let's initialize and add some tick to the database, with postman, httpie, insomnia or whatever client you have.
Initialize the user.
Add Tick to user.
We can deploy functions and tests them like before in real production environment.
firebase deploy
You can see them on firebase console.
Frontend #
We will keep the HelloWorld component from the vuejs setup, take everything off, for a cleanup. We keep the template part just with a canvas.
<template>
<div>
<canvas id="tracker" :style="{border: '5px solid ' + color}" width="800" height="400">
</canvas>
</div>
</template>
On the data part, we just initialize the color, we dont even keep ticks.
data() {
return {
color: '#' + (Math.random()*0xFFFFFF<<0).toString(16)
}
},
On the mounted, we setup the initialization of the user, and the click listener. we will handle the realtime as well
let data = {
color: this.color
};
// initalization of the array
axios.post(baseurl + 'initUser', data).then(r => console.log(r.data));
let self = this;
function getCursorPosition(canvas, event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
let data = {
color: self.color,
coords: {x, y}
};
console.log('', );
axios.post(baseurl + 'addTicks', data).then(r => console.log(r.data));
}
// click listener
const canvas = document.getElementById('tracker');
canvas.addEventListener('mousedown', function(e) {
getCursorPosition(canvas, e)
});
Realtime callback #
On the mounted we will redraw every ticks on the canvas.
// realtime callback
db.collection("users")
.onSnapshot(function(snapshot) {
snapshot.docChanges().forEach(function(change) {
if (change.type === "added") {
console.log("New ticks: ", change.doc);
}
if (change.type === "modified") {
var canvasSetup = document.getElementById("tracker");
var ctx = canvasSetup.getContext("2d");
change.doc.data().coords.forEach(item => {
ctx.beginPath();
ctx.rect(item.x-2, item.y-2, 4, 4);
ctx.lineWidth = "1";
ctx.strokeStyle = change.doc.id;
ctx.stroke();
});
}
});
});
The complete vue component #
Here the complete file from the HelloWorld component.
<template>
<div>
<canvas id="tracker" :style="{border: '5px solid ' + color}" width="800" height="400">
</canvas>
</div>
</template>
<script>
import axios from 'axios';
const baseurl = 'https://us-central1-nipsandbox.cloudfunctions.net/';
const firebase = require("firebase");
// Required for side-effects
require("firebase/firestore");
const firebaseConfig = {
apiKey: "AIzaSyBIjKVw0VqTQlsutpTyd-pwCAVAmw4zVRA",
authDomain: "nipsandbox.firebaseapp.com",
databaseURL: "https://nipsandbox.firebaseio.com",
projectId: "nipsandbox",
storageBucket: "nipsandbox.appspot.com",
messagingSenderId: "200494519109",
appId: "1:200494519109:web:4b34dcdc4bd8d6ec"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
var db = firebase.firestore();
export default {
name: 'HelloWorld',
data() {
return {
color: '#' + (Math.random()*0xFFFFFF<<0).toString(16)
}
},
mounted() {
let data = {
color: this.color
};
// initalization of the array
axios.post(baseurl + 'initUser', data).then(r => console.log(r.data));
let self = this;
function getCursorPosition(canvas, event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
let data = {
color: self.color,
coords: {x, y}
};
axios.post(baseurl + 'addTicks', data).then(r => console.log(r.data));
}
// click listener
const canvas = document.getElementById('tracker');
canvas.addEventListener('mousedown', function(e) {
getCursorPosition(canvas, e)
});
// realtime callback
db.collection("users")
.onSnapshot(function(snapshot) {
snapshot.docChanges().forEach(function(change) {
if (change.type === "added") {
console.log("New ticks: ", change.doc);
}
if (change.type === "modified") {
var canvasSetup = document.getElementById("tracker");
var ctx = canvasSetup.getContext("2d");
change.doc.data().coords.forEach(item => {
ctx.beginPath();
ctx.rect(item.x-2, item.y-2, 4, 4);
ctx.lineWidth = "1";
ctx.strokeStyle = change.doc.id;
ctx.stroke();
});
}
});
});
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#tracker {
margin:auto;
}
</style>
Make sur you have axios, core and firebase installed.
npm install --save axio
npm install --save firebase
CORS restrictions on cloud functions #
Let's test our code, nothing happen it seems we have some CORS problems, let's solve it. The error is very explicit, it's a well documented problem.
Warning about the CORS error as it can show this error for different problem. Error 500 will send back CORS error with others. Even a wrong spell function, it doesn't return a 404 but a CORS error. It's ambiguous.
Access to XMLHttpRequest at 'https://us-central1-nipsandbox.cloudfunctions.net/addInit' from origin 'https://niptracker.web.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
Install cors as dependency with npm.
npm install --save cors
We update our code, wrap the code with the cors function.
const functions = require('firebase-functions');
const firebase = require("firebase");
// Required for side-effects
require("firebase/firestore");
const firebaseConfig = {
apiKey: "AIzaSyBIjKVw0VqTQlsutpTyd-pwCAVAmw4zVRA",
authDomain: "nipsandbox.firebaseapp.com",
databaseURL: "https://nipsandbox.firebaseio.com",
projectId: "nipsandbox",
storageBucket: "nipsandbox.appspot.com",
messagingSenderId: "200494519109",
appId: "1:200494519109:web:4b34dcdc4bd8d6ec"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
var db = firebase.firestore();
const cors = require('cors')({origin: true});
/**
* INIT USER
*/
exports.initUser = functions.https.onRequest((req, res) => {
cors(req, res, () => {
db.collection("users").doc(req.body.color).set(
{coords: []}
).then(function() {
res.send("ok")
})
.catch(function(error) {
res.send(error);
});
});
});
/**
* ADD TICK
*/
exports.addTicks = functions.https.onRequest((req, res) => {
cors(req, res, () => {
db.collection("users").doc(req.body.color).update(
{coords: firebase.firestore.FieldValue.arrayUnion(req.body.coords)}
).then(function() {
res.set({ 'Access-Control-Allow-Origin': '*' }).sendStatus(200)
})
.catch(function(error) {
res.send(error);
});
});
});
And deploy the functions
firebase deploy --only functions
Test your application #
As describe on the introduction, just launch two google chrome, and click on the rectangle. you should see them on both screen.
Conclusion #
This is a quick project, the main goals was to test the realtime of the firestore of google cloud platform (or firebase, as it's the same).
It's easy to setup and gives good results, I'll will definitely use for some projects, personal use for sure, maybe even for professional use.