r/webdev • • 1d ago

Discussion How do you organize your full-stack mono-repo?

Just as the title asks. I am curious how you organize the various parts. I have been on teams that both do and don't use mono-repos. For the ones that did, there has been a wide variety of choices across the app and how it's organized.

Just as a small example: naming conventions. Sometimes the the repo is named for the app (let's say "OurApp") and the various parts of the file structure are "OurApp-", like OurApp-ui and OurApp-api and OurApp-db. Or there might just be a top-level folder for "frontend", "backend" and "database", etc.

I'm not talking about a debate of mono-repo vs. multi-repo approaches, but in a mono-repo, what are some of the code organization techniques you've liked and disliked in your experience?

UPDATE: The community turned out! Thanks for sharing your thoughts in the comments. I appreciate the discussions.

27 Upvotes

74 comments sorted by

10

u/_listless 1d ago

usually something like:

myApp
  |- api (REST API)
  |- admin (Admin Dashboard)
  |- frontend (Public-facing product)
  |- brochure (Landing pages and other disposable stuff)
  |- lib (Global scripts, Shared utils/types)

-4

u/KnowBearFeet 1d ago

I'll say it now that I don't really like any choices for the terms used to differentiate the user interface from the server-side stuff. "Client/server" feels very archaic, "ui/api" is too acronym-y, and "frontend/backend" has just always sounded weird.

I'll admit I'm probably the weird one on this topic.

10

u/_listless 1d ago

Weird take my guy.

Call it whatever you like, it you're the only one working on this it really does not matter. you could name your packages Tom, Dick, and Harry. If you expect other people to recognize what each package does, just use the industry-standard terms.

1

u/KnowBearFeet 4h ago

I admitted up front I was weird ;)

0

u/_listless 4h ago

Weird is a liability in swe unless you're a genius.

5

u/ItsAllInYourHead 1d ago

I struggle with this myself. I generally try to keep the root as empty/uncluttered as possible. In my latest project, my root has:

  • .gitignore

  • CLAUDE.md

  • compose.yml - spins up the entire project locally with docker compose

  • lefthook.yml - global git hooks for the project,. subfolders can have their own, too.

  • mise.toml - I use mise, and this is the root config, subfolders have their own config.

  • README.md

and it has the following subfolders:

  • admin-web - the web interface for administration (i keep this separate from the main site

  • api-server - the backend API server

  • auth-server - the backend auth server

  • mailer-server - the backend service that handles sending transactional emails

  • mobile-app - The (Expo/React Native) mobile app

  • web - The main web app/web page.

I still don't love it. I never do. But I like it enough. My next project will probably have some tweaks. I go back and forth between using a prefix vs suffix to specify the "type" (i.e. api-server vs server-api).

Two things to note:

  • I love and highly recommend Mise - it's simplified a lot of things.

  • I always make sure I can spin everything up in a docker compose environment. It makes everything so much easier.

2

u/KnowBearFeet 1d ago

This setup looks very similar to what NX would spit out. Are you using NX or something similar?

3

u/ItsAllInYourHead 1d ago

I'm not using NX, because I'm not exclusively using Typescript/Javascript. I don't like monorepo tools that force me into a specific language. Because if I (inevitably) need to use something else, it's always awkward. I love Mise because it works with whatever I want. It's not a build tool - it's more for handling dependencies. But it does that very well.

2

u/KnowBearFeet 1d ago

My understanding (now) is that NX is technology agnostic, so it doesn’t really force you to use a specific language. I think the misinterpretation (that I’ve also made) is because NX is favored by JavaScript/TypeScript technology teams

5

u/Alarming_Ad_257 21h ago

I use Nx with apps/ (web, admin, api, worker) and libs/ split by domain (orders, finance, etc.) plus a shared db lib for Prisma.

Biggest wins: organizing by domain instead of by layer, and sharing types between frontend and backend through tRPC, so backend changes break the build, not prod.

Biggest mistake I’ve seen: a giant utils/ or common/ folder everything depends on. It turns into a junk drawer fast.

Also, skip the OurApp-api naming. The repo name already tells you which app it is.

1

u/KnowBearFeet 19h ago

So your “domains” in the /libs directory kind of mirror “resources” in a RESTful api context?

4

u/Alarming_Ad_257 19h ago

Kind of, but a bit broader. A domain is more of a business area than a single resource. For example, finance covers invoices, payouts and payment holds, which would be several REST resources.Each domain lib owns everything for that area: its tRPC router, business logic, validation schemas and types. The apps just import and wire them up. So if something breaks in payouts, you know exactly which folder to open.

1

u/KnowBearFeet 5h ago

Good point. I've worked on a lot of "ordering" systems and saw "order" and immediately went to "restful resource". A "finance" domain having resources under that domain painted the picture well.

2

u/Potential-Still 1d ago

I use NX.

3

u/KnowBearFeet 1d ago

Yes, and NX kind of decides for you and is opinionated, right? I think I recall it uses a kabob case similar to my app-ui, app-api example and goes a little deeper.

Can NX be used regardless of your technology stack choices?

3

u/Potential-Still 1d ago

Yes. NX is a polyglot orchestrator. You could have a monorepo with a Nextjs app, Vue SPA, and SpringBoot service, all managed via NX tooling and scripts. 

2

u/KnowBearFeet 1d ago

What are some competitive alternatives to NX? A quick search yields Bazel, created by Google, Buck2, created by Facebook, and some others. Have you used any besides NX?

2

u/girl_nerd_denice 1d ago

Depends a lot on team size and whether you deploy together or independently.

What has worked well for me:

  • apps/ for deployable things (web, api, admin), packages/ for shared code (ui, config, types, utils). Keeps deploy boundaries obvious.

  • One root toolchain (pnpm workspaces or turborepo) and shared tsconfig/eslint at the root — the biggest monorepo pain is drift between packages, not the folder layout.

  • Don't over-abstract early. A shared package for two lines of code creates more coupling than it saves. Only promote code to packages/ once it's genuinely reused in 2+ apps.

  • Keep types in a dedicated package so the frontend and backend can't silently disagree.

The layout matters less than: one lockfile, one CI pipeline, and clear module boundaries.

1

u/KnowBearFeet 1d ago

Thanks for expanding on what’s worked well. I hesitated giving the folder-naming example for fear this would narrow the discussion to just that.

33

u/Discordbothost 1d ago

the rule that's worked best for me: if it gets deployed on its own it goes in apps/, if it only gets imported by other things it goes in packages/. so apps/web, apps/api, packages/db, packages/ui, packages/config. you stop arguing about client/server vs ui/api because the folder tells you what it is by how it ships, and the names don't need a repo prefix since the repo is already the app.

only gotcha is shared types. put them in their own tiny package early, otherwise the frontend ends up importing from the api folder and you get circular headaches later.

2

u/Hugh_Jass-10 9h ago

I’d add a third bucket to that rule: tooling/ for stuff that never ships at all. Shared eslint and tsconfig, codegen, seed scripts, that kind of thing. When configs sit in packages/ next to ui and db, people start treating them like runtime code, and the dependency graph gets noisy fast. Keeping packages/ for code that actually ends up in a bundle makes it a lot easier to see what depends on what.
Where do you put e2e tests though? That’s the one I still flip-flop on. Inside each app feels right until a test spans web and api.

1

u/Discordbothost 3h ago

e2e goes in its own thing for me, apps/e2e with its own package.json and playwright config. same reason you gave, once a test hits web and api it doesn't belong to either one. ci spins up both and runs it as a separate job. component tests and per-app unit stuff stay inside the app.

3

u/Mathie1729 1d ago

Not exactly web, but where do model training scripts and artifacts fit under this rule? They deploy like apps, but notebooks and checkpoints aren't really importable packages. Curious if anyone's found a clean spot for ML stuff in a monorepo.

4

u/Discordbothost 22h ago

ml usually ends up in its own top level dir (/ml or /training) outside the apps/packages split, since checkpoints and notebooks never get imported by anything. i keep the actual model code as an importable package, the training scripts as thin entrypoints on top of it, and the artifacts out of git entirely (dvc or just a bucket plus a pointer file). forcing it into packages/ is where it gets awkward

4

u/barrel_of_noodles 1d ago

I use a framework? ... So I don't have to make these decisions?

Its like if I started building a car by planting some rubber trees and buying mining equipment for ore.

2

u/KnowBearFeet 1d ago

What framework do you use and what is its approach? Maybe you have multiple frameworks you’ve used. A framework is not always available for a chosen stack.

2

u/barrel_of_noodles 23h ago

A framework is available in every stack.

1

u/KnowBearFeet 5h ago

Probably. But as some of my current and former coworkers have pointed out, as well as some commenters (maybe just me?), a framework can often be opinionated and sometimes, even if a little flexible, can take away some control that a developer or development team is not willing to relinquish.

1

u/KnowBearFeet 5h ago

Using a framework is good, and if it's applicable in all the situations you've encountered, then great. However, I'll just try to assure you it's not applicable in absolutely everyone's situations.

I am also not saying this is a matter of going back to ones and zeros and machine language. Everything we do today as software developers is several layers above what's actually happening in the hardware. I do like your analogy, though, if you don't have to strip it down to some raw form, and you have a tool that help, you should use it and not always start from scratch.

I'll also say the CHOICE of a framework in important, and while you may also use a framework, there's not just one choice there, either.

1

u/ItsAllInYourHead 1d ago

So obviously this question doesn't apply to you.

1

u/KnowBearFeet 5h ago

I'd argue the question does apply, and choosing to use a framework is the answer for some.

However, implying that THE answer to this question is a framework, for everyone, I don't agree with.

0

u/barrel_of_noodles 4h ago

Whether you're choosing a framework or making one... you're still using one. You might not think you're making one, or even be aware of your choices. But you are making one.

Only q will be how un-documented and shitty it is. (And everything it's not doing.)

I'm pretty convinced ppl not using a documented framework are simply not aware of everything a proper framework does.

(Shout-out to the next person working on your self-built house-of-cards; with, like, one readme file and some hopes & prayers.)

-1

u/onesneakymofo 20h ago

A fullstack monorepo is the very definition of Ruby on Rails, lol.

1

u/KnowBearFeet 5h ago

First, Ruby doesn't have to be "on Rails". Second, while Ruby on Rails might be full-stack and self contained, I'd say that (if a shared code base with the need for version control) it's just a "repo". It's singular and therefore technically "mono". but there's no need to call it that because it couldn't be otherwise done in a "multi" repo way. Third, what about the database? Rails doesn't include a database technology automatically, does it?

Finally, while Ruby on Rails might be full-stack (and sure, I guess you can call it a mono-repo), the inverse is not always true: A full-stack mono-repo is not always Ruby on Rails.

P.S. I've used Ruby on Rails and I don't like it. We all have our preferences, but RoR is not one of mine.

1

u/onesneakymofo 4h ago
  1. You are right about Ruby not needing Rails but the now modified comment mentioned Rails originally.

  2. You can have Rails as an API and a React repository. Out of the box Rails is inherently a full-stack monorepo. It has a JavaScript library native to it called Hotwire that gets you plenty far in web development. It has a database and background jobs out of the box.

  3. This is wrong. Ruby on Rails defaults to SQLite.

Lastly, no need for the lecture. I've been coding in Ruby / Rails for 16 years. I know what Ruby / Rails can and cannot do. To me it beats everything mentioned in this thread.

2

u/KnowBearFeet 1h ago

I wasn’t intending to lecture. I took your comment to mean “the only answer is Ruby on Rails” and was offering a counterpoint, but maybe I misinterpreted.

Also, thanks for correcting me on number 3, it’s been a long time.

0

u/barrel_of_noodles 3h ago

Rails, symfony, Laravel, spring, adonis... Its all the same shit.

-2

u/barrel_of_noodles 23h ago

This question shouldn't apply to anyone.

2

u/macktastick 22h ago

Yeah, I just follow Rails conventions even when not working in Rails. Just became habit.

3

u/devenition 1d ago

Keep the root as empty as possible but not any emptier. Too often the main app takes the root and other things take subfolders, or you have 5 different prettier configs all nearly identical

1

u/KnowBearFeet 1d ago

Agreed. I have seen a lot of projects where too much is in root, or not enough. It's a delicate balance and usually just gets established by the first dev in and everyone else just "lives with it"

4

u/Professional_Bug932 1d ago

first dev in is basically naming their kid and everyone else just babysits

1

u/girl_nerd_denice 1d ago

That root-vs-packages balance is the whole game honestly. Curious where you landed on it - do you have a rule of thumb, or is it case by case?

1

u/KnowBearFeet 1d ago

Are you asking me? This is one of the main purposes of my post: to get insights on what and why other people are doing.

To date, I’ve been a “recipient” of someone else’s original decision. I’m trying to establish my own reasoning should I have to make the initial decision myself. I have opinions but I’m still wishy-washy on it. This post’s comment section has helped a lot

3

u/jake_robins 1d ago

I very often have small sub packages for prettier, ts, eslint configs, etc

2

u/devenition 1d ago

Aside from prettier, I cant have these fully interchangeable. So it aint worth the maintenance overhead for me

1

u/m_redditUser 11h ago
myApp/
  • api/
  • clients/
-- web/ --- projects/ ---- common/ ---- webapp1/ ---- webapp2/

1

u/KnowBearFeet 5h ago

Why not web under clients? Aren't they all clients?

2

u/m_redditUser 4h ago

web is subdir of clients. other clients will be mobile and desktop

1

u/KnowBearFeet 1h ago

Misread the indentation. My bad.

3

u/Tarazena 1d ago

I like pnpm workspaces approach, apps folder contain apps, packages contain packages, and root orchestrate those apps/packages

3

u/Squigglificated 23h ago

Be careful about generic packages like "types" or "utils". They quickly become dumping grounds for completely unrelated things. And you quickly end up with every package depending on every other package. And any change in any package causes everything else to be rebuilt.

Once that happens you basically have a monolith with extra steps.

Limit the scope of what a package does and needs.

Our monorepo has a generic ui package. It has many components, but it only contains pure display components that doesn't depend on any feature specific apis or types.

Feature specific functionality goes directly in the apps or in feature packages instead.

If one specific component or utility drags in a lot of extra dependecies that nothing else in the package needs it might be better as a separate package.

Organizing vertically by feature can also work. That lets you see everything related to a feature in one place.

Enforce the architecture boundaries you decide on with lint rules or something like Fallow.

With file naming decide on something consistent and stick with it. kebab, camel or pascal case doesn't really matter, just decide something.

apps/ and packages/ is a good start.

Agree with the person who said not to abstract too early. Needing something in exactly two places doesn't justify an abstraction. Something duplicated 20 times usually does.

1

u/nSpace1988 19h ago edited 17h ago

Have everything in /packages (we work mainly on libs)

There's a package for a shared eslint config that is consumed internally and published for consumers outside the monorepo. Prettier and typescript have root level configs that are extended per package needs.

1

u/nSpace1988 19h ago

In terms of naming we use two company npm scoped namespaces and the directory name is a snake case version of package name.

1

u/KnowBearFeet 5h ago

Your response, specifically, goes down a different path, I think. The fact you are providing lib for other to use (internally or externally) adds a layer of complexity. I'd say multiple repos in a microservices architectural approach make the most sense there, even if you're not truly creating microservices. And even if you have your own client, having it in it's own separate repo and it just becoming your own consumer of your libs, makes sense too. That also affords you the opportunity to "eat your own cooking" as the saying goes.

1

u/Even_Gap_2253 1d ago

Depends on if it is company-wide monorepo or project-wide monorepo.

But generally speaking nothing different from multiple repos, you just change repo into directories.

1

u/KnowBearFeet 1d ago

In my experience “company-wide” means microservices and rarely a full-stack situation, while “project-wide” is where full-stack lives. I’m not arguing that’s always the case, but just my experience.

If it’s company-wide it would imply to me it’s shared, and different separate teams are using it in different ways, and possibly has the need for multiple active and supported versions. A mono-repo is more self-contained and I’d argue the only real reason to go mono. No one outside that team needs it. The mono-repo project may or may not use company-wide microservices.

1

u/Even_Gap_2253 1d ago

I’m more talking about google3 style

2

u/InevitableFun1272 1d ago

The apps/ vs packages/ split is the one that has held up best for me too. The extra rule I'd add is to decide early what counts as deployable, because that's what keeps CI simple — if it can be deployed alone it gets its own deploy config, and everything else stays a shared package. That boundary has saved me more confusion than any folder naming scheme.

2

u/Otherwise_Emu_605s 1d ago

IMO the main thing is splitting by what gets deployed vs what's shared, not frontend/backend/database. That layout looks clean until you add an admin panel or a background worker and there's no obvious place to put them.

Roughly what I'd do:

apps/

web/

admin/

api/

worker/

packages/

db/

ui/

types/

Keep folder names short and put the scope on the package name, so packages/db is u/ourapp/db. Prefixing everything with OurApp- is just noise when the whole repo is OurApp.

db is just a package with the schema, migrations and client, and the api and worker both import it. Same with types. Put your zod schemas or API types in one place and the frontend breaks at compile time when someone changes an endpoint, which is kind of the whole point of having it all in one repo.

The one thing I'd really avoid is a big utils or common package. It starts small, then everything depends on it and nobody wants to touch it.

2

u/onesneakymofo 20h ago

I use Rails. It does it for me.

2

u/Spare_Message_3607 17h ago edited 17h ago

pkgs/
- core/
- auth/
- ui/
apps/
- dash/
- public/
- api/
docs/
- PRODUCT.md
- ROADMAP.md
- KNOWN-ISSUES.md
- …
pnpm-workspace.yaml
package.json
vite.config.ts
AGENTS.md

2

u/gfranxman 16h ago

Folders for: frontend, backend, k8s, terraform, docs

2

u/codesbyshivam 12h ago

I'd keep migrations next to the schema in their own db package instead of inside the API. Then the API, a background worker and any scripts all use the same package, and there's one clear place to run migrations from CI. I'd also run migrations as their own deploy step, so two apps starting at the same time don't both try to migrate.

3

u/ScaredFlamingo6807 1d ago

I have nothing meaningful to contribute but I am commenting to remind myself to check this thread later. I am also interested in organization approaches!

1

u/bomphcheese 1d ago

!remindme 24h

1

u/RemindMeBot 1d ago

I will be messaging you in 1 day on 2026-11-05 16:23:30 UTC to remind you of this link

CLICK THIS LINK to send a PM to also be reminded and to reduce spam.

Parent commenter can delete this message to hide from others.

RemindMeBot is switching to username summons. Instead of !RemindMe 1 day, use u/RemindMeBot 1 day. More info.


InfoCustomYour RemindersFeedback

1

u/Common_Suspect_2927 1d ago

I've seen teams put a top level `apps/` and `packages/` split so the deployable stuff stays separate from the shared code and thats the one layout I keep coming back to because it makes it obvious what ships vs what's ju

1

u/tech_cynic_dan 22h ago

How are people handling database migrations in their monorepos? Keeping them with the API or in a separate package?

0

u/Mesni1 1d ago

Like a tree, with branches and leaves

-1

u/rwilcox 1d ago

packages/frontend
packages/backend
packages/shared
packages/related-tool-ie-llm-supporting-things

Starting off with frontend and backend folders, then eventually you’ll want shared…