A Deep Dive into Building a Multi-Entity CRUD Application with Firebase-Express SDK

Mithat Akbulut
9 min readAug 16, 2023

--

Web development projects encompass not only user interfaces but also backend management, database operations, and API integrations. Firebase-Express SDK assists developers who aren’t well-versed in backend or focus on frontend development in overcoming these challenges.

Firebase-Express SDK combines Firebase’s rapid development capabilities with the flexibility of Express. This provides developers with the opportunity to create fully-featured web applications with frontend expertise and basic backend knowledge.

In this blog post, I’ll be sharing my experience of crafting a comprehensive CRUD application using Firebase-Express SDK. By focusing on real-world entities like products, users, orders, and categories, I aim to shed light on how this tool simplifies the development process and empowers us to build scalable, robust, and feature-rich applications without getting bogged down in intricate backend intricacies.

Throughout this blog, I’ll walk you through each step of the process — from setting up endpoints for various entities to implementing Create, Read, Update, and Delete operations effortlessly. Together, we’ll uncover how Firebase-Express SDK streamlines the codebase, minimizes repetitive tasks, and allows developers to channel their energy into crafting exceptional user experiences.

To embark on this exciting guide, first navigate to https://firebase-express-sdk.vercel.app/docs/intro. There, you’ll find the steps to create a basic code template that includes Firebase-Express SDK, Express, and other dependencies.

Project Structure

Import Dependencies:

At the outset, we import the necessary dependencies that form the backbone of our project. Notably, Express and Firebase-Express SDK take center stage as the primary components. We also include the Firebase service account file to ensure secure access.

const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const FirebaseExpressSdk = require("firebase-express-sdk");
const serviceAccountFile = require("./serviceAccount.json");

const app = express();
const port = 3001;

app.use(cors());
app.use(bodyParser.json());

Data Entity Definitions and Endpoints:

Once our dependencies are in place, we proceed to define the various data entities using the collections object. Each data entity is accompanied by its respective attributes, meticulously crafted to reflect the entity's distinctive features. For example, the users collection encompasses attributes such as names, ages, passwords, and more.

const collections = {
users: {
documentAttributes: [
"name",
"age",
"password",
"email",
"role",
"status",
"created_at",
"updated_at",
],
},
products: {
documentAttributes: [
"name",
"price",
"description",
"image",
"quantity_in_stock",
"category_id",
"status",
"created_at",
"updated_at",
],
},
categories: {
documentAttributes: ["name", "status", "created_at", "updated_at"],
},
orders: {
documentAttributes: [
"user_id",
"product_id",
"quantity",
"status",
"created_at",
"updated_at",
],
},
};

The structured approach of defining attributes within the collections object aids in maintaining clarity and consistency. Firebase-Express SDK leverages these definitions to create robust and manageable endpoints.

Initializing Firebase-Express SDK:

Now that we’ve laid the groundwork for our multi-data entity CRUD application, it’s time to introduce the heart of our solution — initializing Firebase-Express SDK. This step will enable us to seamlessly manage and interact with our data entities using the power of Firebase.

In this phase, we create an instance of Firebase-Express SDK by utilizing the FirebaseExpressSdk class constructor. This is a pivotal step that sets the stage for integrating our application with Firebase services. By configuring this instance, we establish the connection between our Express application, Firebase service account, data entity collections, and the specified port.

Here’s how we initialize Firebase-Express SDK:

const firebaseExpressSdk = new FirebaseExpressSdk({
app, // Express app
serviceAccountFile, // Firebase service account file
collections, // Collections to expose
port, // Port to listen to (default: 3000)
});

When we initialize Firebase-Express SDK in this manner, it equips us with the tools necessary to manage our data entities effectively. This instance acts as the bridge between our Express application and Firebase’s infrastructure. It simplifies the process of creating endpoints, handling CRUD operations, and interacting with the underlying database.

By encapsulating the intricacies of backend integration, Firebase-Express SDK empowers us, frontend developers, to focus on crafting remarkable user experiences without the burden of complex backend implementation.

Defining CRUD Actions with Firebase-Express SDK

Defining CRUD Actions:

As our journey through developing a powerful CRUD application with Firebase-Express SDK continues, we now arrive at a pivotal phase — defining CRUD actions. This step involves configuring endpoints that enable us to perform a wide range of operations on our data entities. Whether it’s retrieving, creating, updating, or deleting data, Firebase-Express SDK empowers us to accomplish these tasks effortlessly.

Creating CRUD Endpoints:

Firebase-Express SDK simplifies the process of creating endpoints by offering a clear and intuitive structure. By utilizing the addActions method, we can easily define various actions for different data entities. This method takes an array of objects, where each object outlines the action details, such as the target collection, the endpoint URL, and the type of HTTP request.

Let’s take a closer look at how we can define CRUD actions for different data entities:

firebaseExpressSdk.addActions([
// actions for users
{
collection: "users",
endpoint: "/api/getUsers",
request: {
type: "GET",
},
},
{
collection: "users",
endpoint: "/api/getUserById/:id",
request: {
type: "GET",
paramKey: "id",
},
},
{
collection: "users",
endpoint: "/api/createUser",
request: {
type: "POST",
},
},
{
collection: "users",
endpoint: "/api/deleteUser/:id",
request: {
type: "DELETE",
paramKey: "id",
},
},

// actions for products
{
collection: "products",
endpoint: "/api/getProducts",
request: {
type: "GET",
},
},
{
collection: "products",
endpoint: "/api/getProductById/:id",
request: {
type: "GET",
paramKey: "id",
},
},
{
collection: "products",
endpoint: "/api/getProductByCategoryId/:category_id",
request: {
type: "GET",
paramKey: "category_id",
query: {
operator: "==",
},
},
},
{
collection: "products",
endpoint: "/api/createProduct",
request: {
type: "POST",
},
},

// actions for categories
{
collection: "categories",
endpoint: "/api/getCategories",
request: {
type: "GET",
},
},
{
collection: "categories",
endpoint: "/api/getCategoryById/:id",
request: {
type: "GET",
paramKey: "id",
},
},
{
collection: "categories",
endpoint: "/api/createCategory",
request: {
type: "POST",
},
},

// actions for orders
{
collection: "orders",
endpoint: "/api/getOrders",
request: {
type: "GET",
},
},
{
collection: "orders",
endpoint: "/api/getOrderById/:id",
request: {
type: "GET",
paramKey: "id",
},
},
{
collection: "orders",
endpoint: "/api/getOrderByUserId/:user_id",
request: {
type: "GET",
paramKey: "user_id",
query: {
operator: "==",
},
},
},
{
collection: "orders",
endpoint: "/api/createOrder",
request: {
type: "POST",
},
},
{
collection: "orders",
endpoint: "/api/updateOrderStatus/:id",
request: {
type: "PATCH",
paramKey: "id",
},
},
]);

Enhanced Functionality:

What’s remarkable about this approach is that it supports various HTTP request types including GET, DELETE, PUT, PATCH, and POST. Additionally, Firebase-Express SDK allows us to harness the potential of query parameters, request parameters, and request bodies to craft highly customized and dynamic interactions with our data entities.

Unveiling Project Potential:

With our multi-data entity CRUD application up and running, it’s time to unlock the potential it offers. The Firebase-Express SDK endpoints allow us to interact with our data entities in a seamless and intuitive manner. Let’s dive into some of the specialized CRUD actions we’ve designed and explore how they empower us to manage data effectively.

User Management:

  1. The user-centric actions cater to a variety of use cases. The /api/getUsers endpoint, for instance, retrieves a list of all users – an essential feature for administrators or user listings. To fetch a specific user by ID, the /api/getUserById/:id endpoint comes into play. This could be useful for user profiles or detailed user information pages.
  2. Creating a new user is simplified with the /api/createUser endpoint. This is particularly valuable for user registration or administrative processes. Similarly, the /api/deleteUser/:id endpoint enables the removal of a user by their ID, ensuring comprehensive user management.

You’ve brought up an interesting observation regarding the usage of request parameters for fetching user data, even though the id attribute isn't present within the documentAttributes of the users data entity. This intriguing capability stems from Firebase's auto-generated document IDs and a setup within our application.

When a new user is created in Firebase, it’s assigned an auto-generated document ID by Firebase itself. This ID uniquely identifies the user’s document within the collection. Although the id attribute isn't explicitly specified in the documentAttributes, Firebase still assigns this auto-generated ID behind the scenes.

In our project, when we create an endpoint like /api/getUserById/:id, the :id part signifies a request parameter. This parameter refers to the unique ID of the user's document within the Firebase collection. So, even though id isn't listed as a distinct attribute, it's actually the auto-generated ID provided by Firebase.

To accommodate this feature, in the code, we set the paramKey for these request parameter-based actions to id. This aligns with the auto-generated document ID assigned by Firebase when a new user is created. This way, when we make a request to fetch a user by ID, Firebase-Express SDK knows that the request parameter corresponds to the auto-generated document ID.

Product Management:

  1. For products, the /api/getProducts endpoint facilitates product catalog browsing. The /api/getProductById/:id endpoint allows fetching product details, which could be useful for product pages. The /api/getProductByCategoryId/:category_id endpoint lets users filter products by category, enhancing user experience by narrowing down selections.
  2. Product addition is streamlined via the /api/createProduct endpoint, providing an avenue for product insertion into the system.

As we navigate through the intricacies of our application, it’s important to highlight the robust data validation and schema enforcement capabilities offered by Firebase-Express SDK. These features act as safeguards against unintentional errors and improper usage, ensuring that our application maintains data integrity.

Consider the createProduct endpoint as an example. When we send a POST request with a JSON body to create a new product, Firebase-Express SDK performs a thorough validation. The request body must align with the specified schema for the products collection. In the absence of attributes like updated_at, the SDK recognizes this mismatch and responds with a clear and informative error message.

For instance, if we omit the updated_at attribute in the request body, the response will indicate:

{
"status": "ERROR",
"message": "Missed keys: updated_at"
}

Similarly, when adding attributes that are not part of the specified schema, such as weight, Firebase-Express SDK immediately recognizes this divergence and notifies us accordingly. This proactive validation prevents the introduction of unexpected or erroneous data attributes.

When the request includes an unrecognized attribute like weight, the response informs us:

{
"status": "ERROR",
"message": "Don't use these keys: weight: they are not in the schema"
}

This robust validation mechanism safeguards our application from potential misconfigurations and data inconsistencies. By adhering to the predefined schema, we ensure that the data conforms to our expectations, reducing the chances of errors in our backend processes.

Category Management:

  1. The /api/getCategories endpoint gives an overview of available product categories. To retrieve specific category details, you can utilize the /api/getCategoryById/:id endpoint. This might be useful for category-specific displays or filtering.
  2. Creating new categories is made straightforward by the /api/createCategory endpoint, ensuring that your application remains adaptable to evolving needs.

Order Management:

  1. For orders, the /api/getOrders endpoint furnishes order information for review or tracking. Fetching order details based on the order ID is possible using the /api/getOrderById/:id endpoint. To examine orders linked to a specific user, the /api/getOrderByUserId/:user_id endpoint comes into play, streamlining order history access.
  2. The /api/createOrder endpoint offers a mechanism for creating new orders. Additionally, the /api/updateOrderStatus/:id endpoint lets you modify order statuses, aiding in order tracking and management.

To retrieve orders linked to a particular user, the /api/getOrderByUserId/:user_id endpoint comes into play. The paramKey configuration, set to "user_id", allows us to capture the value provided in the request parameter, specifically the user_id.

The intriguing part is the inclusion of the query configuration within the request. By specifying operator: "==", we instruct Firebase-Express SDK to filter the data based on equality. The user_id within the request parameter is used as the reference point, and Firebase-Express SDK ensures that only orders with matching user_id values are returned.

This efficient filtering mechanism significantly simplifies the process of retrieving orders linked to a specific user. The operator: "==" ensures that only orders with user_id values matching the request parameter are included in the response.

As we conclude this journey through the intricacies of building a robust multi-data entity CRUD application with Firebase-Express SDK, we’ve witnessed the power of simplification and efficiency in action. From setting up a dynamic codebase with Firebase-Express SDK to effortlessly managing users, products, categories, and orders, we’ve embarked on a transformative exploration. But remember, this is just the beginning. To dive deeper into the possibilities that await and to truly unleash the full potential of your applications, don’t hesitate to explore the comprehensive documentation available at the docs website. And if you’ve found this tool as invaluable as we have, don’t forget to show your support by starring the repository on GitHub. As you venture further into the world of full-stack development, let Firebase-Express SDK be your steadfast companion, simplifying complexities, and paving the way for seamless innovation.

Thank you for joining us on this enlightening journey.

Continuing to Explore and Innovate…

--

--