DEV Community

Anshuman Mahato
Anshuman Mahato

Posted on

Learning Journal - Digileger Project Log

I recently started using Twitter and came across this trend of #learninpublic. I saw many people logging their daily progress. It was inspirational to see fellow developers supporting each other in upskilling. It gave me the idea of logging my learnings through blogs. So, here I am.

learn in public

A couple of months back, I started learning React and Tailwind. At the time, I was also thinking about building an application to track my expenses and budgeting. So naturally, it came to me that it would be a great project to practice my learnings. And so I began.

I planned to build a CRUD application where I could enter the details of my transactions, and it could process those to provide monthly analytics and cumulative data.

Here's what I made, Digiledger, a transaction management and expense-tracking app.

Here are the base features that I have integrated till now.

  • Record Transactions
  • Transaction Filtering
  • Expense and Income Analytics

Here are the links:
Live Site: https://digiledger.vercel.app/
Frontend Repo: https://github.com/AnshumanMahato/Digiledger-Frontend
Backend Repo: https://github.com/AnshumanMahato/DigiLedger-Backend

I have plans to expand this project with some more features. But that's all about it for now. This article is more about the things I learnt while building this project.

Build projects to learn stuff, and share your learnings to solidify them.

Working on stuff is the best way to learn anything. There is no questioning this. Along with learning, it also builds the confidence to work with stuff. That's what this project helped me with. I figured out React faster and got some interesting insights.

Here are the major stuff that I learnt:

Frontend First or Backend First?

In my previous projects, I mostly worked on the backend. I never faced any issues as I had the guidelines around which I had to work. I got the list of endpoints, their request body and their responses. So it was never a problem.

It was the first time that I started building everything from scratch. And being a backend guy, I jumped their head first. And it was very cumbersome. I had the endpoints figured out. What was difficult was figuring out the request and response structures.

frontend or backend

As working on the backend became tiring, so I switched to the frontend. And that's when things became a lot clear. Knowing how we present data gives us a clear understanding of what we receive and send.

I spoke with some of the developers regarding their workflows. Some began with the design and frontend part with a mock API. After a basic structure is ready, they move to the backend. And then keep switching back and forth, as needed. Others said they did the backend first and then integrated the frontend.

At first, I thought it must be an "each to their own" thing. But upon thinking it through, I finally got it. It's not frontend first or backend first. It's planning that needs to done first.

Planning out designs, mockups, architecture, etc. and documenting them at the beginning is crucial for the development process. They provide a boundary for the application. It simplifies the scope of the application.

And I also realized that my design skills are not so good. I need to do some work on it.😅

Working of useEffect

From articles and tutorials, my initial understanding of useEffect was that the function within it executes at first render, and then it is called based on the dependency array. I thought that it all happened sequentially.

Let's see the following example.

import "./styles.css";
import { useEffect, useRef } from "react";

export default function App() {
  const testArr = useRef();

  useEffect(() => {
    testArr.current = ["kratos", "atreus", "mimir", "sindri", "brok"];
  }, []);

  return (
    <div className="App">
      {testArr.current.map((el) => (
        <p>{el.toUpperCase()}</p>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

If I go by my initial understanding, this is how it executes. First, testArr is declared, then useEffect executes, and then the component is rendered.

And boy, I was so wrong. The console sent me greetings "Cannot read properties of undefined (reading 'map')".

I did a lot of pondering and tried different solutions, but nothing worked. But then it struck me. All articles and tutorials always referred to the function as useEffect callback. I finally understood what was happening. First, we declare the testArr initially undefined. Then useEffect executes, and the callback's context to the callback queue. Then the component is rendered. After this, when the execution stack becomes empty, the callback is picked from the callback queue and is executed. And then, the component is re-rendered based on value changes.

So testArr = useRef([]) solves the issue. At first, it was a eureka moment, but then I felt dumb for not going through the resources thoroughly.
What I learnt from all this was a good understanding of useEffect and why it is essential to have appropriate initializations for states and refs.

Working with httpOnly cookies in Cross-Site situation

authentication

I am using JWT tokens for authentication. Storing it in local storage makes it vulnerable to XSS (Cross-Site Scripting) attacks, so I am using httpOnly cookies. The concept is that once we receive an httpOnly cookie from a host, the browser sends it with every request to that host as long as the browser has it. As httpOnly cookies are not accessible by client-side scripts, it prevents XSS attacks.

I have used this strategy in most of my previous projects. It always worked fine. But this time, it didn't work. The browser was receiving the cookie but was not sending it back. There was no error either in the browser console or on the server. I googled "browser not sending cookie to server" but got nothing.

Although I didn't find any resolution, there were mentions of CORS policies, origin header, sameSite and CSRF attacks on cookie-based authentication. My frontend and backend are independent applications. They were working on separate ports, so it was a cross-site situation. So, even if I didn't understand it much, I had a hunch that this might be the problem (and I was right).

As we know, we send the httpOnly cookie to its host with every request. It happens by default only if the client and server are on the same domain. It would have worked if I had served my frontend as static files. Or if I had used server-side rendering like my previous projects. But we have to specify this explicitly for cross-site requests. In Axios, we set the withCredentials config field.

    axios.get(BASE_URL + '/todos', { withCredentials: true });
// default for all requests
    axios.defaults.withCredentials = true
// only with a group of requests
    const instance = axios.create({
        withCredentials: true,
        baseURL: BASE_URL
    })
    instance.get('/todos')
Enter fullscreen mode Exit fullscreen mode

In Fetch API, we use the credentials config field. It takes one of the
three values,

  • If you only want to send credentials if the request URL is on the same origin as the calling script, add credentials: 'same-origin'.
  • To cause browsers to send a request with credentials included on both same-origin and cross-origin calls, add credentials: 'include'.
  • To instead ensure browsers don't include credentials in the request, use credentials: 'omit'.
fetch("https://example.com", {
  credentials: "include",
});
Enter fullscreen mode Exit fullscreen mode

We set the Access-Control-Allow-Credentials response header to true on our server.

But even after this, it didn't work. After some research, I found that it was due to protection against CSRF attacks.

data breach

CSRF stands for Cross-Site Resource Forgery. Let us understand this by an example. Assume a system where the frontend is at www.front.com and the backend is at www.back.com. What we intend is to do API calls from www.front.com to www.back.com. But let's say the user goes to a hoax website, www.trustmebiro.com. It silently does some API calls to www.back.com. As cross-site is allowed, this would work fine, as we only need the token cookie for authentication. Thus, we provide unauthorized access to unintended clients and become vulnerable to data breaches.

The SameSite attribute can be set in the cookie options to prevent CSRF attacks. It can take any of the values,

  • Strict - your cookie will only be sent in a first-party context. In user terms, the browser sends the cookie only if the site for the cookie matches the site currently shown in the browser's URL bar. However, when following a link to your site, say from another site or via an email from a friend, the browser will not send the cookie on that initial request. It is good when you have cookies relating to functionality that will always be behind an initial navigation, such as changing a password or making a purchase, but it is too restrictive.
  • Lax - If your reader follows the link into the site, they want the cookie sent so their preference can be applied. That's where SameSite=Lax comes in by allowing the browser to send cookies with these top-level navigations.
  • None - With this option, the browser will send the cookie with all requests, irrespective of the origin. But with this, the cookie must have the Secure attribute. It ensures that we send the cookie only over an HTTPS connection. Also, we need to include the The Access-Control-Allow-Origin in our CORS policies.

The last option is what I had to do. So, after all this, I finally made my app work as I intended. It was tedious. But I learned many things about Internet attacks, CORS policies, server configurations and networks.

Cancellation of Requests upon component unmount

request response cycle

After all this, the minimal features of the app were complete. So I deployed it. I used free services to deploy my app, so the performance was slow. It got me thinking about users with slow connections.

There was no loading screen in my app. So there was no indication that something was happening. Therefore I made one. It also got me thinking, what if the user tries to navigate when a request is ongoing? By the time response comes, they will be on a different page. It will have no use. So I started looking for ways to cancel a pending request upon component unmount. And I got to learn about the AbortController.

As per MDN, the AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired. It has a property signal and an instance method abort().

Here's how we use it with Axios.

const controller = new AbortController(); 
axios
    .get('/foo/bar', { signal: controller.signal })
    .then(function(response) { 
         //... 
    }); 

// cancel the request 
controller.abort();
Enter fullscreen mode Exit fullscreen mode

Although this looks simple, I am facing difficulty integrating it with React. Till now, I have figured out how to implement it for requests we make within useEffect callbacks. Here's how we do it.

import axios from "axios";
import {useEffect} from "react";

function Component() {
useEffect(()=>{
    const controller  = new AbortController();
    (async()=>{
        const response = await axios.get('www.example.com',{
            signal: controller.signal,
        })
        //further manipulations
    })();

    return ()=>controller.abort();
});

return (<div>
        {/*Component body*/}
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

I still need to figure out how to do it for requests triggered based on user actions, like logins or transaction creation. I am looking into it. If you have any suggestions, please let me know in the comments.

Well, That's all, folks!

That's everything that I have gathered till now through this project. As I said earlier, it has been a great learning experience. I need to figure out much more stuff. A lot of work that I need to do on this project. But whatever I have done so far, it was fun.

I hope this blog was insightful for you. If you have any suggestions or feedback regarding anything, please comment. Or you may connect with me on Twitter as well. My handle is @AnshumanMahato_.

Thank You for reading this far. 😊

Top comments (0)