Turning your web app into a platform

Martin Benvenuti
White Prompt Blog
Published in
6 min readSep 9, 2022

--

Catchy title huh? In this series of posts we’ll explore the idea of turning an already existing web application into a full blown platform. But, first things first, let’s get through the basics.

Disclaimer

This will be a series where we will talk about the app transformation from a conceptual point of view. We will go through some code snippets whenever there’s the need for them, but we won’t build an app from scratch.

We will try to keep everything as agnostic as possible from the underlying technologies, however we might mention some framework, tool or language if it makes it easier to explain what we are talking about. Remember, we are looking at this from a conceptual point of view and things like app state, components, state management, rendering, and memoization, for example, exist in pretty much every front-end application no matter the framework you are using. So, the ideas that we share should be familiar to you.

If you are in need of efficient and powerful processes for the success of your company, check out our capabilities at White Prompt, and we will make it happen. Get in touch with us today.

This is based on an actual transformation for an app that serves over 20 million unique monthly active users. Why am I bringing this up? As I mentioned above, there will be some topics that are biased by the actual implementation. This should not be considered the only route to take. What do I mean? For instance, using prop drilling, Redux, hooks or context for state management is an architectural decision and you might not always have the ability to change that decision. The fact that I use one of them does not mean the others are wrong, just apply common sense and go with what you are already using in your app.

Developing a platform from a web app

Let’s go

So, having all that said, let’s turn our app into a platform. But, wait… what is a platform? To be fair, the word is quite overloaded, but we will use it to describe a modular application that allows developers to write new features as if they were independent pieces of code. This will be our requirements:

From a development point of view:

  • A new feature should be scoped to its own folder within the code base.
  • Removing a feature should be as simple as removing the folder where it lives. There should be no traces of it after the removal (i.e. if there was code that belonged to the feature outside that folder, our structure was wrong).
  • Deploying a new feature should not affect the rest of the application.
  • External development should be as simple as internal development (i.e. we should be able to expose what we are creating to third parties and allow them to build on top of our “platform”).

From a functional point of view:

  • Teams should be autonomous. They should own and make decisions about their development and deployment pace as well as be able to ship as often as they require while monitoring the features they own.
  • Newly deployed features should not be available immediately for end users even though the code is served.
  • We should be able to turn features on and off on a (at least) per-user basis. Think about A/B testing for a bit.

The app

To create our platform we will use an e-commerce SPA. For the sake of simplicity, we will focus on these four routes:

  • Home: where all the items we can buy are listed.
  • Shopping cart: where there’s a list of the items we have added.
  • Checkout: where we pay for those items.
  • Admin: where users with a certain profile can add new items to list in the Home page.
  • Profile: a simple user-profile management form.

Our app will have a pretty common structure:

Wire-frame representation of the Home route
  • A top bar, with our logo, title and most main actions.
  • A side bar, which will allow us to navigate through routes.
  • A content section, which will render the current route.

Finally, let’s discuss our capabilities. Our app is auth protected using JWTs. We have a REST API and also a WebSocket channel which is created once we are logged in. We can think of them as transport channels that can be functionally expanded to suit our needs (i.e. we can add whatever endpoint, new message or protocol change we want to, as long as they stick to our requirements).

Thinking about the problem

What is a Feature? Let’s start by defining an interface for it. It’ll be a function that will receive a Context and will return nothing. Everything a Feature has to do, it’ll do once it’s executed when we bootstrap the application. We’ll populate the Context as we move forward.

The second step will be to create a dummy folder under src/ called features. Inside that folder we will create one extra folder per feature we are developing to keep our requirements fulfilled. This will be our first convention.

Now, let’s say we want to add a new route to our application: the way to start doing so is by adding a new button to our sidebar, something along the lines of:

But… that code lives in the sidebar and we explicitly mentioned in our requirements that new features should not modify already existing code. Looks like our context needs some kind of mechanism to register new sidebar buttons.

On a broader level, our UI should be something like a placeholder where we can make “space” for new things. We talked about the sidebar, but the same principle applies to the topbar, to dropdowns on each item displayed in the Home page, and to options in every contextual menu. Interesting…

As we move forward with the design for our new route, we notice that it will need to act upon receiving certain WebSocket messages, and even react to them. We are not only dealing with the registration of visual components, but also with the registration of behavioral portions of the application, something that the context should handle as well. Looks like a different type of “space” that we need to come up with.

We mentioned a couple times that new features should not be enabled by default for end-users. We need a mechanism to be able to turn them on/off, both at the init level when we are bootstrapping the app, as well as during runtime.

We seem to have an overall idea of the shape of a feature, where to put it within the code base and when to turn it on/off. But, what about the how: how do we articulate all the new code we have and make it part of the old code?

What about app state? If we want new features to interact with our application they will definitely need to consume (and probably modify) our UI state. Another thing to consider for our context.

What happens with everything that is not a feature? We mentioned the UI turning into a “shell”. We will have a data-layer that will talk to our REST API and our WS server. We will have some kind of business layer in charge of restricting certain actions (e.g. should a user be able to access the /admin route? Where does all of that code live? How does it relate to the context? Should consumers worry about it?

Enough problems for today

We now have a clearer picture of all the things we need to start creating a new feature. Let’s call it a day and try to digest everything we’ve thought about. In the second post of this series we will start populating the context while refactoring the main app to make the “space” we need for the new code, while facing some interesting new challenges in the process.

We think and we do!

Do you have a business that requires an efficient and powerful data architecture to succeed? Get in touch with us at White Prompt, and we will make it happen!

--

--