Frontend First Development: A Senior Developer's Approach To Coding
Building mobile frontend client side first has tremendous benefits over building backend APIs first. Here’s my approach, flow, and sequence of building out new features.
Frontend First Development
Over the years I found it to be the most effective to start building the mobile client side and its UI first and then the rest of the business logic and backend (BE) API implementation.
This is contrary to a more common approach where the BE API is built first, the client side implementation second (or in parallel) and then the client side is “integrated” with the API.
There are many benefits to building the client side first. The main one is that you get to test out user interaction and client side logic sooner and get rapid feedback from your product manager, client, or other stakeholders by showing them a “working” app with mock data. You cut the time and effort on building the BE as you narrow down the client side solution by identifying what’s really needed from the BE API to make the feature work. And then you build only that.
There are lots of downsides in building the BE first - the API tends to be built more generically and “broader” than actually needed because there is little understanding on what the feature, the UI, and data needed for it should look like in the first place. And of course the integration step with the client side rarely goes smoothly because of the over-mentioned reasons.
Here is the flow and sequence of development that I usually end up following on my projects, which helps me work through the problem without experiencing the challenges above.
Client Side Apps
While high level projects tend to be built BE API first (the same way many client side developers tend to start building features with the network requests first), I take the opposite approach where I start the UI first and the API last:
First get the design, make it myself, work with designers, use mocks or sketches, whatever. No testing needed here as it’s a pre-coding step, but there are lots of scenarios and edge case identifications to cover and consider.
Second build the UI layer & user interaction hook points. There is a lot of write-the-code-you-wish-you-had-first and then re-rendering UI for different states to get the desired result. Data mocking here will be “in-line”, i.e. viewmodels (dumb data structs) necessary for the UI to render certain states will be identified and stubbed for faster dev cycle. No tests or TDD here, as it’s pointless to test the UI.
Third build the business logic on the client side, i.e. what needs to happen upon user input or other events - what services that do network requests or local DB queries will be called (but not the actual requests themselves), what UI states need to be rendered and when, etc.
Here there is a lot of write-the-code-you-wish-you-had-first and I sometimes TDD, sometimes I don’t. It depends on how trivial the problem is. If there is a clearly established standardized strict architecture, such as RIBs, I likely would not test anything except the algorithmic code or complex data mappers.
Trivial tests like “upon start of the screen this service needs to be called and then the UI rendered” I won’t bother to write.
Fourth implement networking requests and DB calls/queries. At this point everything on the client side will be known from the high level perspective (i.e. the business logic) plus the UI will be built out already. What’s left is to fill in the gaps and code up the network requests. As usual, there is a lot of write-the-code-you-wish-you-had-first. The BE API implementation may or may not be done at this point - it doesn’t matter, because the business logic and the UI driving it is known now, and I know what params I can and should pass. I don’t mock or TDD anything here, as this is the outer layer of the architecture and it’s pointless to test (you do not test someone else’s code).
Fifth add the UI tests. If the backend is ready then this should be it, the full integration is done and the feature should be ready to go. Now would be the time to add UI tests.
The UI tests + business logic unit-tests (if any) become the backbone of regression testing and refactoring in the future. There is way less testing code than in a normal TDD’ed or in a “unit-test-cover-everything” codebase (unless UI tests go out of hand of course).
Backend API & Business Logic
I don’t have to deal with implementing the BE API on most of the projects I work on, but if and when I do, here’s the approach I take:
These steps come after I build out the client side, because it would give the full picture of what kind of data can and should be passed and received from the client side to/from the BE APIs.
Build out integration tests for the API contract outlined in fourth step of the client side implementation (the step where I implemented the networking requests code). Issue actual HTTP requests to local/staging endpoints of a running app. Cover happy path and error cases. Again, the client side implementation should guide me.
Implement the business logic of processing the requests and doing whatever needs to be done on the BE for the whole system to work correctly. Most likely, this will be data mapping, processing, sending out requests to other backends and services, and of course saving or retrieving data from the DB the backend is using.
Unit-tests here as needed, but more so than in the client side’s business logic implementation. Likely BE business logic is not as trivial as on the client side. I may or may not TDD here depending on complexity. Integration tests remain the backbone of testing though.Work on implementing the network requests and database queries identified in the previous step. There likely won’t be any test for network request services implementation as they would connect to 3rd party APIs, but there will be lots of integration tests for the DB layer here.
Typically these steps are implemented sequentially per endpoint: steps 1, 2, 3 are implemented for the first endpoint and then for another and so on.
At the very end of it, there will be an “integration” step. Chances are, it won’t be noticeable at all, and there will be little to no work on the client side since the API contract was driven by the frontend implementation. That is, unless there are any changes and alterations identified during the BE implementation.
Write-the-code-you-wish-you-had-first vs TDD
Write-the-code-you-wish-you-had-first is rather self-explanatory, but I want to explain more.
Over the years, I found TDD to be less useful than I previously thought. The reason I put emphasis on the testing part in the description of my coding approach is because I find most of the unit-testing in “unit-test-cover-everything” codebases to be pointless. It brings no value because a) some of the tests shouldn’t even be written in the first place if you have a good strict standardized architecture, or b) they either test too much, or c) test the wrong things. I’ll expand on this topic more in a future article.
But these days, I choose standardized architectures like RIBs and use the write-the-code-you-wish-you-had-first tactic to write my code. It’s worth expanding on further in its own article but in short:
I write a method call of a method that doesn’t exist on an object in which I wish to have it
… the compiler complains that the method is not in the interface declaration of that object
I add the method signature to the interface
… the compiler complains that the method is not implemented in the objects that adopt the interface
I add the method and its implementation to the objects that need it
After that, I’m done. I’m ready to write the next method I wish I had on my object. The compiler will guide me again. Rinse and repeat the steps above.
Conclusion
That’s it. This is how my flow and approach looks like these days, building new features either purely client side or fullstack.
What development approach do you take in your work? Do you start with the client side? Do you start with BE API implementation? Somewhere in between? Let me know by replying to this email or leave a comment on Substack!