Skip to main content
  1. Posts/

Maximizing Efficiency with UI-First Development: A Client-Centric Approach to Project Success

One of the challenges for start-ups or any new project is to reduce the amount of work while still delivering a full-featured product. Agile methodologies address this challenge on the project management level. Let’s discuss another approach to address it on the architecture level: UI-first development.

Delivering a prototype to the client early is crucial for project success. Clients may have only a general idea of the product they need, and prototyping can save time and effort by reducing unnecessary work. Building applications ground-up might be a bad idea.

The issue with the ground-up approach (from data model to UI) is that clients cannot see the product immediately, leaving many user scenarios hidden. Consequently, developers may implement some not-the-cases because they (and the product owner) usually don’t fully understand user requirements due to miscommunication, which is hard to avoid.

By the time the client receives the first working prototype, a significant amount of work has already been done on both front-end and back-end sides. If the prototype doesn’t meet the client’s expectations, then this work is wasted. A telltale sign of such a situation is if significant changes are made to the data model after the first version is presented to the client.

Agile methodologies can mitigate this problem:

In iterative development with short iterations, each iteration adds value. Client-side, server-side, and persistence levels are changed together to add new functionality. The client should always be satisfied, even with a fraction of the Minimum Viable Product (MVP).

The question is: “Should the client be happy with a skateboard?” In the real world, clients may need to try a “bicycle” before they can say it resembles what they expect to have in the end.

Under such uncertain conditions, the top-down approach—i.e., UI-first Development—might be a better solution. It’s an even more “agile” way since it collects the client’s feedback earlier, reducing the team’s unnecessary work.

Development Plan #

Let’s assume we’re developing a web application that consumes a REST/WebSockets API from a back-end server. The following diagram shows how development phases can be scheduled on a timeline:

Application Development Schedule

1. UI Prototype #

Actual development begins with web application prototyping. First, UI mockups are created and presented to the client. This is typically a single-page application (SPA) written using a component framework like React, Vue.js or Svelte. Visual prototypes or screen mockups can help create the initial version quickly.

This phase of development is enjoyable: you create something that looks real and make it quickly, like this:

Front end is not what you think

In their book “The Pragmatic Programmer”, Andrew Hunt and David Thomas distinguish between “prototype” and “tracer code” or “tracer bullets”. A prototype must be replaced with real production code, while tracer code is not: you write it for keeps. The pragmatic approach is to mix prototype and tracing code, then refactor and rewrite the prototype over time.

2. Add Some Static Data #

Once the initial application structure is clear, it’s time to add some data. Real data isn’t necessary, as there’s no real backend to provide it. It’s enough to create static JSON files and configure development server (express.js) to serve them from /assets or /data folder along with the JS application.

You can start by using a cloud provider for static data, such as a static site built on GitHub Pages, GitLab Pages,, or an alternative.

3. Start Defining API Contract #

Static data lays the foundation for future API specifications (contracts). The most popular format for writing API specifications is OpenAPI/Swagger. It suits most common cases well. Some aspects, like inheritance, are not clear enough in the specification, but this format is widely accepted in the industry, making it the default choice. Other formats for describing REST APIs are API Blueprint, Mashape, and Mashery I/O Docs.

You can automatically generate and publish API reference documentation and SDKs from API specifications. You can use it internally, and you may publish it later when you decide to make your API public. Every specification format of your choice has tools you can use to generate HTML documentation.

At this stage you may see something like a Walking Skeleton (or just a Flying Ghost).

“A Walking Skeleton is a tiny implementation of the system that performs a small end-to-end function. It need not use the final architecture, but it should link together the main architectural components. The architecture and the functionality can then evolve in parallel.” – Alistair Cockburn

4. Time for Testing #

Now that you feed your UI application with static data, it’s time to write some tests. You may start testing some base functionality you’re confident with. Web developers can start testing web components using JS frameworks like mocha, jasmine, or similar frameworks.

It’s impossible to cover all cases without a real application server. Also, it’s challenging to test requests sent by the UI application. However, you can test simple scenarios like: “WHEN the user requests a specific URL, THEN the expected data is shown on the page.”

5. …Even for System Integration Testing #

Web-application functional end-to-end testing with test data can be done by web developers.

System integration testing is performed by the same team or by a QA team, along with web and back-end developers, if you have separate teams for front-end and back-end. It usually covers complex user interaction scenarios.

A common tools used for system integration functional testing are Selenium, Selenide, Cypress, Cucumber and the like. Sometimes it’s necessary to develop extra tools for direct access to underlying data for setting the expected state for tests and test doubles to emulate external systems. Often, a team may end up designing custom test DSLs to simplify writing this kind of test.

It’s a long way to go, but even now, you can start writing some simple tests.

6. Establish Test Automation and CI #

We have a contract (API specification) and test data in static files (data should match the contract or be auto-generated). We also have some system integration tests.

Now is a good time to set up deployment and testing automation, so your prototype is always deliverable. You don’t need to implement services and data layers so far.

7. Starting Back-End: Mock Controllers #

We now need a back-end and a full deployment cycle to test both front-end and back-end together. From an API specification, we can generate data transfer objects and front controller interfaces. Then, we should implement controllers so they return the same test data. Mock controllers are sufficient; they can serve the same static data you already have.

The most important thing here is that after completing this step, our system integration tests should run against a real UI working with a real server. And the tests should be “green.”

8. Continuing Back-End: Controllers and Mock Data Access Layer #

Now it’s time to implement services, one by one. A database is still not necessary—you can mock the persistence layer.

The tests should still be green, and we can add more tests now since we have services.

9. Continuing Back-End: Real Database and Data Access Layer #

Now we should design our database schema, create data access layer, and add (the same) test data to the database so the tests are still green. After that, we’ll have all components in our system:

  • End-to-end Tests
  • Web Application
  • REST API Specification
  • Backend: Controllers, Services, Repositories
  • Database

Now, when the initial setup is completed (“iteration zero”), we can continue with short sprints, affecting all system layers in each iteration.

That’s a funny time

Final Notes #

This is just an idea of how to minimize unnecessary work in conditions of business uncertainty. Don’t use this instruction blindly; it may not be applicable to your case.