HomeProjectsBlogContact

How to Structure a Full Stack Project So It Survives Growth

Folder structure feels unimportant until the project hits fifty files and nobody can find anything. Layer-based vs feature-based, monorepo or not, and a layout that scales calmly.

How to Structure a Full Stack Project So It Survives Growth

Every project starts tidy. Ten files, obvious names, everything fits on one screen. Six months later there's a utils folder with 40 unrelated files, three different places that call the API, and a new teammate asking where the login logic lives. Folder structure doesn't matter on day one. It matters a lot on day two hundred.

Here's how to structure a full stack project so it stays easy to work in as it grows.

The two main approaches

Layer-based (group by technical role):

src/
  components/
  hooks/
  services/
  utils/
  pages/

Simple and familiar. It works well for small apps. The problem shows up at scale: one feature, say "invoices," ends up spread across five folders, and every change means jumping around the tree.

Feature-based (group by what the code does for the user):

src/
  features/
    auth/
      LoginForm.jsx
      useSession.js
      auth.api.js
    invoices/
      InvoiceList.jsx
      InvoiceForm.jsx
      invoices.api.js
      invoices.test.js
  shared/
    ui/
    lib/

Everything about invoices lives together. You can understand, change or delete a feature by looking at one folder.

The practical answer: start layer-based if the app is tiny, then move to feature-based once you have more than a handful of distinct features. Most real projects end up as a hybrid: features for domain code, plus a shared area for truly generic pieces.

A layout that works for most full stack apps

For a single repo with a frontend and a backend:

my-app/
  apps/
    web/            # frontend (React, Next.js, Vue...)
    api/            # backend (Express, Fastify, Django...)
  packages/
    shared/         # types, validation schemas, constants used by both
  database/
    migrations/
    seeds/
  .github/workflows/
  docker-compose.yml
  .env.example
  README.md

If you use a full stack framework like Next.js, Nuxt or SvelteKit, frontend and backend live in one app, and the feature-based idea applies inside it:

src/
  app/              # routes / pages
  features/         # domain logic grouped by feature
  components/ui/    # generic, reusable UI
  lib/              # db client, auth helpers, config

Rules that matter more than the exact layout

1. Keep the database and external APIs behind one layer. Components shouldn't write SQL or call third-party APIs directly. Put that in a data or service layer (lib/db, features/invoices/invoices.server.js). When you swap providers or add caching, you change one place.

2. Share validation between client and server. Define a schema once (with Zod, Valibot, Yup or similar) and use it for both form validation and API input validation. The frontend gets instant feedback; the backend still enforces it.

3. Make "shared" earn its place. Code goes into shared only when at least two features genuinely use it. Premature sharing creates a junk drawer that everything depends on.

4. Keep config in one module. Read environment variables in one file, validate them, export a typed object. (Here's the pattern.)

5. Put tests next to the code they test. invoices.test.js beside invoices.js gets updated. A distant tests/ mirror tree tends to rot.

6. Name things for what they are. formatCurrency.js beats helpers2.js. If you can't name a file clearly, it's probably doing too many things.

Monorepo or separate repos?

A monorepo (one repository, multiple apps and packages) shines when the frontend and backend share types and change together. One pull request can update the API and the UI at once. Tools like npm/pnpm workspaces, Turborepo or Nx help with builds and caching.

Separate repos make sense when different teams own different services, or when they deploy on completely independent schedules.

For a solo developer or small team building one product, a monorepo is usually less friction. Don't introduce heavy tooling until build times actually hurt.

Signs your structure needs attention

  • You regularly can't find where something lives.
  • Changing one feature touches files in five or more top-level folders.
  • utils or helpers has become the biggest folder in the project.
  • Circular imports keep appearing.
  • New teammates need a guided tour just to make a small change.

Refactoring structure doesn't need to be a big-bang rewrite. Move one feature into its own folder, see if it feels better, then move the next.

Don't forget the boring files

  • README.md: how to install, run, test and deploy. A new developer should get the app running from it alone.
  • .env.example: every variable the app needs, with safe placeholders.
  • A single command to start everything locally, whether that's npm run dev or docker compose up (Docker basics here).

FAQ

What is the best folder structure for a React project?

For small apps, a simple components, hooks, pages split is fine. As the app grows, group code by feature (features/auth, features/billing) with a small shared folder for generic UI and utilities.

Should frontend and backend be in the same repo?

For most small and medium projects, yes. A monorepo keeps shared types in sync and lets one change update both sides. Split them when teams or deployment schedules are truly independent.

How do I organize a Node.js Express backend?

Group by feature or resource (users, orders), each with its routes, service logic and data access, and keep app-wide middleware, config and the database client in a shared lib folder.

Keep reading

More from the workbench

All articles →