Firebase Functions to Build a Serverless CRUD App
Firebase is a powerful backend service provided by Google that speeds up Web Development by providing a BaaS (Backend-as-a-service) infrastructure. Firebase allows Engineering teams to quickly build out and prototype applications without the need to worry about common backend services like authentication, authorization, database provision, task schedulers (thank you cloud functions!), and more! Some of the features listed above are different services within the Firebase console — e.g Firestore is Firebase’s database service.
Firebase can be integrated with a web or mobile application using any of the available SDKs. In this project, we’ll be using the Firebase JavaScript SDK.
In this article, we’ll look at some of the most common functions you’ll need to build out a Backend using Firebase’s Firestore database and auth service. Think of this as a Cheat sheet to avoid reading the firebase docs.
Getting Started with Firebase
To get started setting up your firebase service, simply head over to https://firebase.google.com/, make sure you’re signed in with your Google account, and hit the Get started button.
On the Welcome to Firebase screen, click Create Project to spin up a new firebase project. Give your project a name, accept the terms and conditions and let’s dance! You’ll be brought to the firebase console. Next, select your project from the list of projects and click Firestore Database to the left of your dashboard. From here, we can manage everything to do with our Databases.
By default, Firebase allows anyone to read and write from your database for a period of 30 days — as long as they have your keys. You can see your database rules by going under the rules tab on the Firestore page in your firebase console. Make sure to edit these rules when you’re ready to go to PROD.
Adding Firebase to your Project
To add Firebase to your project, you’ll need a set of keys. Click on the gear icon next to Project Overview to access the keys for your project.
We’ll be integrating firebase into our React project. To set up Firebase in your project, run the command;
npm install firebase
This will add the Firebase SDK to your project. Create a .env
file in the root directory of your project and set the following variables. You can get the values from the Project Settings section above.
apiKey= YOUR_KEY
authDomain= YOUR_KEY
projectId=YOUR_KEY
storageBucket=YOUR_KEY
messagingSenderId=YOUR_KEY
appId=YOUR_KEY
With your environment variables set up, we’re ready to start adding functions.
Setup Firebase in Your Codebase
We’ll now run through getting Firebase setup within our app. In the root directory of your project, create a folder named utils and a file called firebase.js. You can follow whatever directory structure you prefer but I like to setup my projects like this.
In your firebase.js file, add the following lines. We start off by importing all the functions we need from within the firebase module.
// initialize firebase
import { initializeApp } from 'firebase/app'
// firebase auth service
import {
getAuth,
signInWithEmailAndPassword,
onAuthStateChanged,
updateProfile,
signOut,
createUserWithEmailAndPassword
} from 'firebase/auth'
// firebase DB service
import {
collection,
getFirestore,
addDoc,
getDocs,
updateDoc,
doc,
getDoc,
setDoc,
arrayUnion,
deleteDoc
} from 'firebase/firestore'
const firebaseConfig = {
apiKey: process.env.apiKey,
authDomain: process.env.authDomain,
projectId: process.env.projectId,
storageBucket: process.env.storageBucket,
messagingSenderId: process.env.messagingSenderId,
appId: process.env.appId,
}
// initialize firebase
export const app = initializeApp(firebaseConfig)
// connect to firebase auth with your credentials
export const auth = getAuth(app)
// connect to firebase db with your credentials
const db = getFirestore(app)
On to the next!
Auth
What’s one thing every app must have? You guessed it! Auth!
Firebase allows you to use sign-in providers like Google, Twitter, Microsoft or Facebook to allow your users sign in with their accounts on other platforms. To set this up, simply head over to the Build tab on your Firebase console, click Authentication and then the sign in method tab.
In this article, we’ll be keeping it simple and using the E-mail and password auth provided by Google. To get started, under the Users tab, click Add user.
This allows you to create a login for users on your platform from inside Firebase itself. To allow users sign up from within your UI, use the following block of code.
const register = async ({ email, password }) => {
try {
const { user } = await createUserWithEmailAndPassword(auth, email, password)
return user
} catch (e) {
return false
}
}
For your login flow, call the signInWithEmailAndPassword
provided by Firebase, like below.
const login = async ({ email, password }) => {
try {
const { user } = await signInWithEmailAndPassword(auth, email, password)
return user
} catch (e) {
return false
}
}
To check if a user is signed in, use;
const isUserSignedIn = async () => {
onAuthStateChanged(auth, (user) => {
if (user) {
// get signedIn user
const uid = user.uid
console.log(user)
return uid
} else {
return false
}
})
}
To sign out a user from your service;
const signOutUser = async () => {
await signOut(auth)
}
To update a user’s metadata, use;
const updateUserProfile = async ({ displayName, photoURL }) => {
try {
await updateProfile(auth.currentUser, {
displayName,
photoURL,
})
} catch (error) {
}
}
Adding new Collections and Documents to Firestore
Firebase is a No-SQL database. This means that the Database structure is a little bit different from conventional SQL databases. For example, while SQL databases are based off the idea of Tables and Columns, No-SQL databases use documents and collections to structure data. Collections can be likened to tables and documents to columns — they’re not like for like though and shouldn’t be considered as such.
Let’s create a User’s collection in our DB. To create a new collection, we’ll simply add a document to the collection in our codebase. Firebase is intelligent enough to create the collection and a document at the same time even if the collection does not already exist.
// take in the user's details from the UI
const addNewUser = async ({ phoneNo, name, email, address, cars = '' }) => {
try {
// add a new Doc to the User's Collection
const userRef = await addDoc(collection(db, 'users'), {
phoneNo,
name,
email,
address,
cars
})
console.log('Document written with ID: ', userRef.id)
} catch (e) {
console.error('Error adding document: ', e)
}
}
Getting all the Data in a Firestore Collection
If we want to get all the users in our database for example, we can use the getDocs()
function.
const getCollection = async () => {
try {
const users = []
const querySnapshot = await getDocs(collection(db, 'users'))
querySnapshot.forEach((doc) => {
users.push({ user: doc.data(), docId: doc.id })
})
return users
} catch (error) {
console.log(error)
}
}
Update a Record in Firestore
If you have a user in the DB already, you can update their records in the DB.
// Update the user's details
const updateUserPhoneNo = async ({ phoneNo, user }) => {
try {
const userRef = doc(db, 'users', [USER_DOCUMENT_ID])
// Update the User's Collection
const userRef = await updateDoc(userRef, {
phoneNo,
})
console.log('Document written with ID: ', userRef.id)
} catch (e) {
console.error('Error adding document: ', e)
}
}
For the block of code above to work, you’ll need to know the USER_DOCUMENT_ID to look up the user and update the user’s records.
Firebase will automatically generate a Document ID for data written to the database. While writing to the DB, this ID is generated after the document has been written which may make it a little difficult to update the record in the future. To prevent this, I prefer to use a unique ID to identify unique users in my database and subsequently use that ID to look up a user’s records in my database. You can use the popular UUID npm package to generate these IDs.
For example, we can change our function to add a user to the following;
// take in the user's details from the UI
const addNewUser = async ({ phoneNo, name, email, address, cars = '' }) => {
const userId = uuid_v4() // generate the userId
const userRef = doc(db, 'users', userId) // use the userId generated to create a docId
const data = {
phoneNo,
name,
email,
address,
cars,
userId // save the userId to the user record
}
try {
// add a new Doc to the User's Collection
await setDoc(userRef, data, { merge: true })
console.log('Document written with ID: ', userRef.id)
} catch (e) {
console.error('Error adding document: ', e)
}
}
This would mean that our update function from earlier will look like;
// Update the user's details
const updateUserPhoneNo = async ({ phoneNo, user }) => {
try {
const userRef = doc(db, 'users', user.userId) // use the userId to find the user in the DB
// Update the User's Collection
const userRef = await updateDoc(userRef, {
phoneNo,
})
console.log('Document written with ID: ', userRef.id)
} catch (e) {
console.error('Error adding document: ', e)
}
}
Note that addDoc() and setDoc() are slightly different in the way they work. addDoc
will automatically generate a Document ID for your document while writing to the DB while setDoc
allowing you to specify the Document ID you’ll like to use. For writing data that you’ll want to look up later, using setDoc
is advisable to allow for easier look-up.
Update an array Record in Firestore
If you have a record in your database that takes in an array and you need to update that record, use the array_union
function. For example, imagine that our user has some cars in his Garage.
// Update the user's details
const updateUserPhoneNo = async ({ phoneNo, carName }) => {
try {
const userRef = doc(db, 'users', [USER_DOC_ID]) // USER_DOC_ID is the userId if you followed the setDoc example
// Update the User's Collection
const userRef = await updateDoc(userRef, {
cars: arrayUnion({
carName: carName,
}),
})
console.log('Document written with ID: ', userRef.id)
} catch (e) {
console.error('Error adding document: ', e)
}
}
Deleting a Document in Firestore
const deleteUser = async ({ user }) =>{
try {
const userRef = doc(db, 'users', user.userId)
await deleteDoc(doc(db, "users", userRef));
} catch (e) {
console.error('Error adding document: ', e)
}
}
Rounding Up
With the functions above, you can create your own application by using Firebase as a backend service. The functions above list all the CRUD operations you need to create your own backend service. To summarise;
setDoc
or addDoc
allows you to CREATE a new firestore document/collection.
getDoc
allows you to READ the data within your firestore collection
updateDoc
allows you to UPDATE a document within a firestore collection
deleteDoc
allows you to DELETE a document within a firestore collection.
For more information, the Firebase docs provide a complete resource for all firestore functions and features.