Blog

Structuring a Web App

Where should everything live?
Mar 31, 2025

Discoverability plays a big role when working. How do I find what I need to edit? Where should I put this new file? The truth is we shouldn't really need to think about it; as always, the less cognitive overhead the better.

We might be tempted to create a folder for each type of thing: routes, styles, components, services, etc but I say no. The problem biggest weakness with this approach (which is frameworks like Next.js wrongly push) is that you have to jump around so much. If we are working on a component and need to update the tests or styles we shouldn't need to spend time tracking it down, put it in the same folder. Using a modular pattern like described in the latter of the solutions scales well and gives us much better discoverability. I mocked up an example of what's ideal. Notated blow: The overview here shows a monorepo that breaks out each project. Api is the api, component-library is also self explanatory, shared has anything in it that will be used across projects, and web is our web UI.

The api has three top level folders: core, db, and user. Core is where anything central to the app can live, db has our interface to our database and migrations, and user has our DAL, mapper, services, routes and tests. So everything we need to make updates to the user api(s) is in one place. We can add as many folders like this as we need, we could also break them down further if needed. It should also be noted that they aren't named things like just "service.ts". While the folder essentially namespaces the files to be related to a user, the editor does not. When you have 10 service.ts files open you will got lost real quick.

The component library is a bit more simple, since it has relatively simple contents. It might only get consumed by web right now, but there could be a b2b2c portal we set up one day. YAGNI, I know, but ideally these files become pretty static, as your design system matures, so it's nice to put in a tiny bit more effort and get them out of the way, while setting us up for the future.

Shared contains pretty much anything that would be shared between the back and front ends (or more front ends in the future!). Having some common utils, shared types, and shared validators speeds things up and ensures we can stay in sync.

And lastly web is our React app, similar in structure to the API. We have a router instead of routes, we have CSS instead of data access layers, etc.

And that's about it. If we structure our projects in a way that is modular and sliced based on first the project and then each project by vertical slices then we can ensure all we need to work on a thing can be easily found, saving everyone time and frustration.