How Varo Used GraphQL To Share Business Logic Across Multi-Platform Bank Apps
Originally posted on Medium – Engineering @Varo
Starting in September 2019, and during the majority of 2020, the engineering team at Varo worked on our new banking app called Varo Bank, released in October 2020. The four years prior were dedicated to working on our initial offering. Varo Money was our original app when we were in partnership with an external bank as our banking platform, meaning our customers’ money wasn’t kept at Varo, but at our partner bank. We had bigger dreams, though. We wanted to be independent and become our own bank — Varo Bank.
Why did we build a new banking app? Well, we’ve been working on getting a bank charter to become an independent national bank in North America. The work on the charter began in 2018, and we received conditional approval from the Office of the Comptroller of the Currency in September 2018. We kept working on the charter while working on the new product and we finally received the national charter in July 2020, just before launching the new Varo Bank platform. Shortly after releasing Varo Bank, we started phasing out Varo Money and migrating all of its existing users to the new platform.
When we started our journey in October 2015, we were a rather small company, so we had to start small. Our initial release was an iOS app in June 2017. Releasing our first app was a big milestone, but iOS is only about 45% of the U.S. mobile market, according to our analytics and public data. To include the majority of the rest of the market, we had to build an Android app. Our first Android app was released at the end of 2018 and was a big success because our brand was already known.
Up to this point, we were very much focused on the mobile market, but any serious bank also needs a desktop and mobile web app. That way, customers can use Varo from the comfort of their desk on a bigger screen, or on their phone without needing to install an app. We started working on our web app for Varo Money in early 2019 and released it in July 2019. This app had limited functionality because we had shifted our focus to work on Varo Bank at that point. We were aiming to be ready for the big reveal later in 2020 when we intended to have a fully featured web app in addition to the Android and iOS apps.
Making Multi-Platform App Development Easier
At that point, we knew our requirements were to build multiple apps that consume the same set of data, so we started thinking about the best way to do so. We knew we were going to have at least three client apps, and we wanted to let each of them consume only the data it needed. To do so, we harnessed the powers of GraphQL, which allowed us to define a contract between the client and the API layer, and allowed each client to tell the server exactly what data it needs in each request.
All of our clients (iOS, Android, and web), use the Apollo Client library, which lets us write GraphQL queries easily. That includes type safety checks, validating the queries, mutations, and returned data, as well as handling caching.
On the GraphQL server-side, we are using the Apollo Engine, which lets us secure our client/server communications by validating that only expected queries and mutations are getting through. How does this work? On each release, each client extracts all the queries it is using and reports them to Apollo Engine, then the Engine makes a check each time it receives a call and validates that the query is allowed.
Apollo Engine also allows us to have multiple GraphQL microservices and connects them all together for easy use by the clients. It does that by the use of Apollo Federation, which lets the GraphQL server developer break down the server into a GraphQL Gateway server. The GraphQL Gateway server is the server that combines all the other GraphQL servers’ schemas into one big schema, and then allows the GraphQL clients to connect to it and forwards all the requests to the right back-end service. You can read more about that on the Apollo website.
We also developed internal tools to verify the different clients are using similar queries and mutations. We write unit, integration, and end-to-end tests automation to verify our functionality keeps on working properly and that we have no regression or other bugs in our software on all levels (server, APIs, GraphQL and clients).
Build Once and Reuse
We decided to have a native application for each of our platforms (Android and iOS), and a web app for whoever cannot use our native apps. We are still trying to make the process easier by reusing as much of our code as we can when it does not hurt the user experience. To provide a little more context, our mobile apps are truly native, the Android app is written in Kotlin, and our iOS app is written in Swift to gain the best performance and functionality. Lastly, our web app is written in React using TypeScript. We are trying to keep everything up to date.
As I mentioned, we are using the same server functionality for all of our clients, which saves us a lot of time instead of having different APIs for each client. But that’s not all. In some cases, we are also using web-views in our native apps. This way we can build flows once in our web app and use them in our native apps as well. This transition is almost seamless when done well, and the user doesn’t even know they are looking at a “non-native” screen but on a web page.
In most cases, this works very well, but there are some limitations. Some examples are slower loading times (especially on the first load), inconsistencies in designs, and an unideal user experience. We did our best to address all of these problems. For example, we made sure our bundle size is the smallest possible. We got it down to 1MB, which loads quickly enough for it to not be too frustrating. We’ve also added custom loading transitions and we load some of the views on the background to make it seem like it is loading slightly faster. We dealt with the inconsistencies by using similar components, themes, and color schemes on all the apps. Lastly, we put a lot of effort into creating the best user experience possible for our customers.
On the other hand, some of these limitations can be overcome. For example, the design differences can be minimized with some additional work on the web side. Bottom line: The ability to build once and reuse, plus the maintenance benefits, like the ability to upgrade the experience without having users download a new version, outnumber the cons.
We are very happy with our decisions, and all these solutions worked well for us. We had an amazing opportunity to rewrite and redo a lot of our code and infrastructure, which many companies don’t have. Also, our engineering team is fairly large at this point, which is another thing that allowed us to learn from a lot of specialists and let everyone use their experience to bring this product to life. We started with only four mobile engineers and eight server engineers in 2017. Today, each of our client teams, that is iOS, Android, and web, includes 10 engineers. On average, each one of our six server teams includes eight engineers.
The journey wasn’t easy, and we did run into many issues we had to overcome. No project is perfect, and neither is ours. We have a lot of smart people on our engineering team and the rest of the company, and they are all trying to improve our product every day.