You Might Not Need a Backend

Surya Kant Bansal
The Startup
Published in
9 min readJan 3, 2021

--

In any graphical application, there are two major components, Frontend & Backend. If you are building a new application, you would probably use a framework or library like Angular or React to write a powerful Frontend application, you would use another framework like Express.js, Django or Spring to write a powerful Backend application and along with that, you would use a database service like MongoDB, PostgreSQL or MySQL. Now if you think about it, these are 3 separate applications that you need to create for your project and you would also need to deploy these 3 applications on multiple cloud resources.

If you are building a small project which does not really require high computation power, the development and deployment of this architecture might cost you a lot more time and funds than you expect. There is an alternative way to build such a project which is simpler, easier and possibly cheaper. You can use Firebase with a React Frontend. Firebase provides services NoSQL database, Cloud Functions, Hosting, Analytics and much more and it also provides a JavaScript SDK that can be used directly inside the React application to communicate with the Firebase services easily.

In this blog, we will create a simple “Inventory Application” in React using Firebase services for database, authentication, authorisation and hosting. I’ll start by creating a base project from my react-boilerplate template repository. This boilerplate has a minimal React application setup with all the important development features like TypeScript, ESLint, Prettier, Jest and other project configurations which makes your development experience seamless.

Development

In this section, we’ll go over creating the application user interface, linking the application to Firebase and rendering data on the user interface that is received from Firebase.

Basic User Interface

Once we have the React application set up, before jumping into Firebase, I’ll create the user interface for the application using some mock data. I want the inventory application to be configurable, so, I also create a configuration object for the application that we can later put in Firebase. The configuration object will make it easier to make some simple changes in the application without redeploying it. In my case, I am creating a headers object that is used to render the inventory table.

const config = {
headers: {
name: {
order: 1,
size: "auto",
text: "Name",
},
price: {
order: 2,
size: "md",
text: "Price ($)",
},
quantity: {
order: 3,
size: "sm",
text: "Quantity",
},
},
};
const items = {
1: {
name: "Hat",
price: "20",
quantity: "100",
},
};

Using this configuration, I render a table that can be customised by just changing a few configurations instead of rewriting the code. Note that the keys inside an item object correspond to the keys of the headers, this is how the data is being populated in the table. This is how the UI looks now.

I also added a “SIGN IN” placeholder button and a filter that refines the items in the table.

Firebase Setup

We begin with creating a project on the Firebase console. You can follow the simple on-screen steps to create the project. Once the project is created, the next step would be to create an app inside the project. To do this, you need to go the project settings and at the bottom, you’ll find an option to create a new app where you can select the option for a web app. Then you just follow the on-screen instructions to create the app. After the app has been created, you need to install the firebase-sdk to the React application and also install the firebase-tools CLI.

# In the project directory
yarn add firebase
# In any directory, installed globally
npm i -g firebase-tools

You would also find a configuration object in the Firebase application settings that looks like this:

const firebaseConfig = {
apiKey: "<your_api_key>",
authDomain: "<your_auth_domain>",
projectId: "<your_project_id>",
storageBucket: "<your_storage_bucket>",
messagingSenderId: "<your_sender_id>",
appId: "<your_app_id>"
};

Next, we will use the Firebase CLI tools to log in to the firebase account and link our React project to the Firebase project.

firebase login

This command will use your browser log into your Firebase account.

firebase init

This command is to be run inside the project directory. This command will ask about the Firebase project that is to be linked with the React project. It will also ask which services this project will be using. We will use Cloud Firestore and Hosting services. Cloud Firestore is the NoSQL database service and Hosting service can be used to deploy our application from Firebase itself. You can find the required answers to the project setup questions below.

// Firestore Setup
What file should be used for Firestore Rules? firestore.rules
What file should be used for Firestore indexes? firestore.indexes.json
// Hosting Setup
What do you want to use as your public directory? build
Configure as a single-page app (rewrite all urls to /index.html)? Yes
Set up automatic builds and deploys with GitHub? No
File build/index.html already exists. Overwrite? No

Now, all we need is to create a local firebase module with all the required settings that can be used across the React application.

// firebase.tsimport firebase from "firebase";
const config = {
apiKey: process.env.apiKey,
authDomain: process.env.authDomain,
databaseURL: process.env.databaseURL,
projectId: process.env.projectId,
storageBucket: process.env.storageBucket,
messagingSenderId: process.env.messagingSenderId,
appId: process.env.appId,
};
let db: firebase.firestore.Firestore;function connect(): void {
firebase.initializeApp(config);
db = firebase.firestore();
}
function disconnect(): void {
firebase
.app()
.delete()
.then(() => console.log("Firebase app deleted successfully."))
.catch((error) => console.log("Error delete Firebase app", error));
}
export { firebase, db, connect, disconnect };

Note that I have used dotenv-webpack to pick the Firebase configurations from an environment file.

The connect and disconnect functions can be used to create and terminate the connection with firebase. Now, we can import db from this module to communicate with Cloud Firestore database. To understand more about Cloud Firestore and how to use it from the Firebase SDK, you can visit the official documentation. The SDK provides very easy ways to perform CRUD operations as per the developer's requirements.

Using Firebase

Once the setup is done and we have the db instance to connect to Cloud Firestore, I created a React context that will provide functions throughout the application to perform CRUD operations on Cloud Firestore. I created 2 collections in Cloud Firestore, one for the headers configuration and another for the items to display. As you may have noticed earlier, I am storing the items as a map between its ID and its values. Now, to create new items, instead of keeping track of numbered IDs, we can use the uuid package to generate universal IDs. These IDs will be used as document IDs in the Cloud Firestore. Cloud Firestore will also require you to write some access rules, you can just set them open to all for now.

Now, with the React context in place, I created a modal form to add more items in the table and I also added edit and delete action buttons in the table itself.

Added action buttons and a modal form

Authentication and Authorisation

The next thing this inventory dashboard requires is proper authentication and authorisation. Firebase Authentication provides its own authentication modules inside the firebase-sdk that are really easy to use. Firebase also provides some UI libraries that includes pre-built authentication interfaces but we won’t be using that for this project. We will use a simple Google OAuth2 authentication process for our application and we will use Cloud Firestore to handle authorisation.

OAuth Authentication

The first thing we need to do is to enable Google sign-in from the Firebase console under “Authentication” > “Sign-in method”. Then, we can create a GoogleAuthProvider instance using the firebase-sdk. I did this in the same local firebase module that I created earlier.

// firebase.ts...let db: firebase.firestore.Firestore;
let googleAuth: firebase.auth.GoogleAuthProvider;
function connect(): void {
firebase.initializeApp(config);
db = firebase.firestore();
googleAuth = new firebase.auth.GoogleAuthProvider;
}
...export { firebase, db, googleAuth, connect, disconnect };

Next, I created another context called AuthContext.tsx to handle authentication actions and store user information. In this context, I have two functions that handle sign-in and sign-out. The user information returned by the OAuth API after sign-in is stored inside a state variable.

const signIn = useCallback(async () => {
try {
await firebase.auth()
.setPersistence(firebase.auth.Auth.Persistence.LOCAL);
const result = await firebase.auth()
.signInWithPopup(googleAuth);
setUser(result.user);
} catch (e) {
console.log(e);
}
}, [setUser]);
const signOut = useCallback(async () => {
try {
await firebase.auth().signOut();
} catch (e) {
console.log(e);
}
}, []);

These functions can now be linked to a sign-in/sign-out button click events. In addition to this, we also need to handle cases in which the page reloads. When the page reloads, we need to check if the user was already signed in or not and set the state accordingly. We can do this using a useEffect hook.

useEffect(() => {
firebase.auth().onAuthStateChanged(setUser);
}, []);

This sets up the basic authentication where any user can sign-in to the application using his/her Google account.

Authorisation

With the authentication in place, now we want some of our users to have administrator access so that they can alter the inventory. The first step to do this is to create another collection in Cloud Firestore called users. This collection will store some information regarding the application users mapped using their UID. UID is generated by Firebase and it is unique to each user and Firebase project.

Once a user signs into the application, you can see his/her details (including UID) in the “Authentication” tab in the Firebase console. Now, you can copy this UID, make a document, inside the users collection, with ID as the UID and inside that document, create a boolean field called admin.

In the application AuthContext.tsx , we can now fetch the data from the users collection and check if the current user UID has admin access or not and the application UI can be manipulated based on that information.

We also need to modify the Cloud Firestore rules so that only administrators have the write access. To do this, you can modify the firestore.rules file with the following code which restricts write access to all collections except items for which, write access is only provided to administrators.

rules_version = '1';
service cloud.firestore {
match /databases/{database}/documents {
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
}
match /{document=**} {
allow read: if true;
allow write: if false;
}
match /items/{item} {
allow read: if true;
allow write: if request.auth != null && isAdmin();
}
}
}

You can deploy these rules using the following command:

firebase deploy --only firestore:rules

Hosting

Firebase also provides hosting service which can be used very easily. First, check that the public directory is set to build in the firebase.json file. Next, you need to compile the React application by running yarn build. Now you can run the following command to deploy your application.

firebase deploy --only hosting

Conclusion

https://github.com/skb1129/inventory-app

You can find the code for this project on the repository mentioned above. I hope this project helps us get a headstart for our future projects based on Firebase and React. I encourage you to star, fork and contribute to this template repository to make it even better.

“Intelligence is the ability to avoid doing work, yet getting the work done.” — Linus Torvalds

Thank you for reading this!

--

--