Personal Cards API migration
Hi everyone, I am Sumrender Singh, MTS - 1 Frontend at Fyle.
This blog post highlights my experience on a recent initiative, Migrate Personal Cards API that I worked on. Q4 ended, and I just finished filling out my quarterly feedback. The feedback contains a lot of sections where you rate yourself on stuff like Technical Competency, Communication, Ownership, Delivery, etc.
Along with a rating, there’s also a comment section which I used to back up my claims with examples and such. While filling it out, I ended up going down memory lane—reliving the Aha! moments that made me feel on top of the world, and the occasional Oh no! moments that made me want to facepalm. 🤦
That finally led me to pen this down and share my experience and learnings.
Quick Context - Personal cards page
If you’ve made business expenses using your personal card or bank account, you can import these expenses into Fyle to claim reimbursement.
Only the user has access to this information, ensuring complete security.
This makes expense filing super easy—just head to the Fyle website or mobile app, fetch your latest transactions, select the ones you need, and boom! Your expense is created, ready to be sent for approval, and eventually reimbursed. (Woohoo!)
My initiative focused on migrating the APIs from v1 to v2, which, in simple terms, meant ensuring that all data displayed on the web and mobile apps was fetched from Server 2 instead of Server 1.
Sounds Easy, Right?
If your first thought is, “This doesn’t seem that hard,” welcome aboard, my friend! I thought the same at first. If it were as simple as just changing URLs, they’d probably have handed it to a newbie. (Um… writer, aren’t you basically a newbie?)
Coughs *Ahem*
What I meant was, if it were just about changing some URLs, I wouldn’t be writing this blog, since replacing URLs and testing would have taken only a week or two.
The Challenges
Here’s what made this initiative trickier than it seemed:
Replacing Old APIs with New Ones
This was the easiest part. Most data-fetching logic lives in dedicated services, so updating the endpoints there was a quick win.
Dealing with Different Data Formats
The data from the new API had a different format, which meant I had two options:
Transform the new data into the old format so the rest of the code could remain unchanged.
Update all the places where the data was consumed to match the new format.
I opted for the first approach on the web app since it’s built in AngularJS, making it harder to ensure that nothing was left out or that no tricky edge cases were missed. This page will soon be migrated to Angular, where we can define types and directly use the new format.
For the mobile app, I used a mix of both approaches. Initially, I created a layer with transformation functions to convert the new data into the old format, allowing us to quickly switch to the new endpoints. Once everything was working smoothly, I started removing the functions one by one until the personal cards mobile page used only the new data format.
No Data Synchronization between the 2 APIs
The backend team decided to run a one-time sync script post-migration.
This meant a Personal Card could only use either the v1 API or the v2 API entirely—not a mix of both. Why? Mixing APIs would lead to data mismatches.
For example, imagine fetching data from v2 but trying to delete it using v1. Since the resource exists in v2 but not in v1, the delete operation would fail. This inconsistency made it clear that partial migration wasn’t an option.
How is this usually handled? A feature flag service redirects regular users to the old API by default, while developers enable the flag on specific accounts to test the new APIs. This allows for incremental migration without disrupting users.
Switching between Angular and AngularJS
Our web app is a mix of Angular and AngularJS, as it’s being migrated gradually. Unfortunately, all the changes I needed to make were in the AngularJS portion—no type safety, and no error detection. 😭 On the bright side, the mobile app (built entirely in Angular) was much easier to handle.
Oh, and if you’re curious, the mobile app repo is open-source! I’d share the link, but then you’d start judging my pull requests. 🤓
Writing Unit Tests
The mobile app has a robust suite of unit tests, with a strict requirement: 91%+ test coverage to merge your code. No exceptions. While this added extra work, it ensured high-quality code.
Collaboration Between Teams
Depending on the initiative, collaboration might be needed with multiple teams, such as design, product, and backend. However, since this was a migration initiative with no new features or design changes, the front-end team primarily collaborated with the back-end team. Regular sync-ups ensured the migration progressed smoothly on both ends.
A big shoutout to Dimple, Tirth Bhagwat, Sumanth Avadhani, Satyam Jadhav, and Kulasekar for their help in resolving my doubts, to creating endpoints that were not present in the v2 apis.
As I mentioned earlier, even I thought this wouldn’t take too long. However, due to the factors above—plus additional challenges like backend API changes and our offsite—this initiative ended up spanning an entire quarter.
The Game Plan:
Research Past Initiatives
The first step was to explore how previous migration initiatives were handled. Fyle has a rich history of migrations, from API upgrades to transitioning entire pages from AngularJS to Angular, so there was plenty to learn from.
Write an Engineering Doc
At Fyle, every initiative begins with an engineering document. It outlines the game plan, lists the services, components, and utilities to be updated, defines contracts between different components, and details the files to remove and functions to change. I also use it as a living document, updating it with relevant findings throughout the initiative.
This step was particularly challenging because I had to map out every instance where the old endpoints were used across both the web and mobile apps. This thorough analysis helped me break the work into manageable tasks, ensuring I didn’t miss anything and could proceed with confidence.
Set a Timeline
Using the breakdown from the engineering doc, I worked with my manager, Dimple, to create a realistic timeline. Her guidance ensured we accounted for deployments, testing, and the bigger picture.
Code, Test, Deploy
Finally, it was time to implement the changes, rigorously test them, and ensure everything worked seamlessly in production.
Unexpected Bug:
While working on the migration, I discovered a feature that existed in the web app but was missing in the mobile app. After getting the green light to add it, I thought to myself, “This should take just one day.”
However, during implementation, I hit a snag: the web app relied on a library to achieve this functionality, but there was no equivalent library for Ionic apps.
It was time to dive into the documentation and find an alternative. It mentioned using a
HTML form
, which I could integrate into the mobile app. After figuring out the required data to pass into the form based on different actions, I got to work.By the time I was done, it was night. It was one of those days when you decide to finish something no matter what, and you don’t get up until it’s done.
The next day, I woke up feeling accomplished. After a round of testing, I merged my changes into the release branch, confirmed a successful deployment, did some work, lazed around, and went to sleep.
Only to wake up to this mail on my pull request the next day. 🫠
It turned out I had missed an edge case—one that only occurs when no cards are linked. During all my testing rounds, there was always at least one card present because I was continuously testing old features alongside new ones.
The root cause? I forgot to handle the scenario where no cards were linked. And you know what would have saved me? A simple optional chaining (
?
) operator.Ironically, I remember thinking about this exact situation while writing the code but forgot to implement it.
Lesson learned: when your brain isn’t “braining” properly, it’s better to take a break and come back refreshed the next day. Writing bad code in a rush just leads to bad results.
Thankfully, apart from this incident, there were no major issues. While a few other edge cases did break, they were all caught during unit testing and the testing rounds before release.
Gains:
Deep Understanding of App Structure
I developed a solid understanding of how pages in the app are structured and how different components interact with each other. I still remember my first day as an intern, feeling completely overwhelmed by the long and complex pages of code. Now, I feel right at home—each component is mapped to its purpose. When I make changes, I know exactly how they will propagate and which other components will be impacted.
Officially Became a Developer
I earned my “developer badge” by enabling developer mode—clicking the build number seven times. 😄 On a serious note, I set up the Android studio, ran the application on my phone in debug mode, monitored network calls, and ensured everything worked as expected.
Mastered RxJS Operators
While I had previously read about RxJS operators, this initiative provided a hands-on experience that made the concepts finally click. By using them extensively in real-world scenarios, I gained a much deeper understanding of how they work and where they shine.
Conclusion
The migration was successfully rolled out, and now both the web and mobile apps are fully using the new API. The transition went smoothly, with no noticeable changes for users—except for that one mobile app user who encountered a failure due to the above bug. This triggered Sentry to log the issue and notify the dev team. 🙇
Overall, this initiative was an amazing journey, filled with valuable learnings and great collaboration across teams!