What I Learnt Building a Full-Stack Task Management Application
Objective
I wanted to build a full-stack application to get hands-on experience with SpringBoot, React and REST APIs. Additionally, I wanted to take a concept from the planning stage all the way to deployment.
Programming languages / Frameworks / Tools used
- Backend: Java, SpringBoot, REST API
- Frontend: React + Tailwind CSS
- Database: MySQL
- Containerisation: Docker
- Deployment & Hosting: GitHub Actions, Nginx, Oracle Cloud
- Automated Testing: JUnit and Mockito
Development Experience & Challenges
Building this task management application introduced me to a variety of new challenges and learning opportunities.
On the backend, one of the biggest hurdles was working with Spring Security. I quickly discovered that Spring Security keeps rapidly evolving, with deprecations and removal of methods between versions and the documentation isn’t always up-to-date or easy to follow. That said, Spring Security was still useful as it allowed me to implement robust authentication and authorisation mechanisms.
I chose Hibernate as the ORM to handle database interactions. I was pleasantly surprised by how powerful and easy it was to integrate with Spring Boot, allowing for smooth CRUD operations without writing SQL queries manually.
I decided to use MySQL for the database, since each user has their own list of tasks, a relational database was an obvious choice for this application.
For handling authentication, I used JWT tokens. This allowed me to implement stateless authentication, where every user is assigned a JWT token upon logging in which is set to expire after a set time period.
On the frontend side, this was my first time working with React, and I found state management to be a challenge initially but overall, I enjoyed using React and found the documentation to be helpful.
React’s single-page approach is different from traditional server-side frameworks like Flask and Thymeleaf that I would have been more familar with conceptually. Traditionally with frameworks like Flask, the server handles rendering most of the content, whereas in a React SPA, the frontend is responsible for much more of the user interaction and rendering.
I used Tailwind CSS to create a responsive design for the webpages, which I hadn’t used previously, but I found it very easy to use.
Modern browsers have a security feature known as the Same-Origin Policy (SOP), which prohibits a site’s JavaScript to make requests to another destination than the site itself.
As my backend and frontend are hosted on different domains, I had to configure CORS in the backend to allow the frontend to communicate to the backend without being blocked by the browsers SOP.
So, the backend and frontend are running on different domains (and ports), how does the frontend talk to the backend? The answer is through a REST API.
Testing
I used JUnit and Mockito for both unit and integration testing in the backend. Unit tests helped validate the behaviour of individual methods, while integration tests were used to verify interactions between different layers of the application (e.g., service and repository layers). With Mockito, I was able to mock dependencies to isolate and test logic without relying on actual implementations.
Deployment
Unexpectedly, one of the biggest lessons I learnt from doing this project was learning how to deploy it!
I decided to use Docker to containerise my application for easy deployment, but when I tried to deploy my docker images onto the VPS running on Oracle Cloud, I ran into issues because the free tier VPS was running on a ARM64 architecture but the docker images had been built for a X86 architecture as that was what my own machine was using. So, I cross-compiled to ARM64 and was able to successfully deploy the application to the web.
However, this process was quite tedious to have to repeat every time I wanted to make a change to the application, so I decided to implement a CI/CD pipeline using GitHub Actions. This allowed the docker images to automatically be updated and cross-compiled using Docker buildx to both X86 and ARM_64 after I pushed any new code to GitHub.
Another benefit was that my unit and integration tests would also be automatically executed after each push to GitHub and if the tests failed, the docker images would not be updated in order to avoid pushing buggy code to production.
Finally, I used a reverse proxy to proxy the API requests to the backend container running on Docker and used Let’s Encrypt to generate a TLS certificate for the deployed website.
Deployed App
Click here to visit the deployed app
Future Enhancements.
- Add OAuth integration
- Refresh the JWT tokens upon expiry.
- Add notifications for due tasks
Conclusion
I really enjoyed completing this project, from the initial scoping stage to having a deployed web-app at the end.
Through the challenges I faced, I was able to learn more about React state management, CI/CD pipelines and Docker.
If you’d like to dive deeper into the code or explore the project further, here’s the GitHub Repo