Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected
How-tos

How to Structure Folders and Files in a Large Web Project

Learn a practical, feature-based approach to organizing folders and files in large web projects. This guide covers grouping by domain, avoiding common pitfalls, and keeping your codebase manageable as it grows.

July 2026 8 min read 1 views 0 hearts

You’ve probably been there. You start a new web project, everything is clean and organized. Then, three months later, you open the project folder and feel a little sick. Files everywhere. A utils folder that has become a black hole. Nobody knows where the API routes live. You waste ten minutes just finding the right import path.

I’ve been there too. At PythonSkillset, we’ve worked on projects that grew from a few hundred lines to tens of thousands. The difference between a project that stays manageable and one that turns into a nightmare often comes down to one thing: how you structure your folders and files from the start.

Let’s talk about what actually works in the real world.

The Core Principle: Group by Feature, Not by Type

The most common mistake I see is grouping files by their technical role. You end up with folders like controllers, models, views, services. This works for tiny projects, but once you have more than a handful of features, it becomes a mess. You have to jump between five different folders just to understand one feature.

Instead, group by feature or domain. Each feature gets its own folder containing everything it needs: the logic, the templates, the tests, the API routes, the database models. This is sometimes called “vertical slicing.”

Here’s a real example from a project I worked on at PythonSkillset. We were building a content management system. The old structure looked like this:

project/
├── controllers/
│   ├── user_controller.py
│   ├── article_controller.py
│   └── comment_controller.py
├── models/
│   ├── user.py
│   ├── article.py
│   └── comment.py
├── views/
│   ├── user/
│   ├── article/
│   └── comment/
└── tests/
    ├── test_user.py
    ├── test_article.py
    └── test_comment.py

It looks organized, right? But here’s the problem. When you need to add a new feature like “article bookmarks,” you have to touch at least four different folders. You add a model, a controller, a view, and a test. And if you forget one, the feature breaks. Worse, if someone else is working on the same feature, you step on each other’s toes constantly.

We switched to this structure:

project/
├── features/
│   ├── users/
│   │   ├── models.py
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── templates/
│   │   └── tests/
│   ├── articles/
│   │   ├── models.py
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── templates/
│   │   └── tests/
│   └── comments/
│       ├── models.py
│       ├── routes.py
│       ├── services.py
│       ├── templates/
│       └── tests/
├── shared/
│   ├── database.py
│   ├── auth.py
│   └── utils.py
├── config/
│   ├── settings.py
│   └── secrets.py
└── main.py

This changed everything. Now, when you work on the “articles” feature, you open one folder. Everything you need is right there. The model, the routes, the templates, the tests. You don’t have to hunt around.

Why This Works

The biggest win is locality of reference. When you’re debugging a feature, you don’t want to context-switch between folders. You want everything in one place. This also makes it much easier to onboard new developers. They can understand a feature by looking at one folder, not ten.

Another huge benefit is deletability. When a feature is no longer needed, you delete one folder. That’s it. No worrying about leftover files scattered across the project. This sounds small, but in practice, it saves a ton of cleanup time.

What Goes in the Shared Folder

Not everything belongs in a feature folder. Some code is truly global. Database connections, authentication logic, logging setup, and common utility functions. These go in a shared or core folder.

But be careful. The shared folder can become a dumping ground. A good rule of thumb: if a function is used by three or more features, it probably belongs in shared. If it’s used by one or two, keep it in the feature folder. Don’t prematurely extract things.

The Config Folder

Configuration is its own beast. Database URLs, API keys, environment variables. Keep these in a config folder. Use environment variables for secrets, not hardcoded values. At PythonSkillset, we use a settings.py file that reads from .env files. This keeps configuration clean and testable.

How Deep Should You Go?

A common question is how many levels of nesting to use. My rule of thumb: no more than three levels deep from the project root. So features/articles/routes.py is fine. features/articles/v1/admin/routes.py is probably too deep. If you need that much nesting, consider splitting into sub-features or using a different naming convention.

Naming Conventions Matter

Be consistent. If you use snake_case for files, use it everywhere. If you use kebab-case, stick with it. At PythonSkillset, we use snake_case for Python files and kebab-case for static assets like CSS and images. The key is to pick a convention and document it in your project’s README.

Also, avoid generic names like utils.py or helpers.py. These become junk drawers. Instead, name files after what they do. date_formatter.py is better than utils.py. email_sender.py is better than helpers.py.

The Tests Folder

Tests should mirror your source structure. If you have a features/articles/ folder, put its tests in features/articles/tests/. This keeps tests close to the code they test. It also makes it easy to run tests for a single feature without running the whole suite.

Some people prefer a top-level tests folder. That’s fine for smaller projects. But for large projects, co-located tests are a lifesaver. You can delete a feature and its tests together.

Handling Shared Dependencies

Sometimes two features need to share a model or a service. For example, both the “articles” and “comments” features might need the user model. The solution is not to put the user model in a shared folder. Instead, the user feature exports its model, and other features import it.

This creates a clear dependency direction. Features can depend on other features, but not in a circular way. If you find yourself with circular imports, that’s a sign you need to rethink your boundaries.

A Real-World Example

Let me show you a structure that worked well for a large e-commerce project at PythonSkillset. The project had about 200,000 lines of Python code and a team of twelve developers.

project/
├── features/
│   ├── accounts/
│   │   ├── models.py
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── validators.py
│   │   ├── templates/
│   │   └── tests/
│   ├── products/
│   │   ├── models.py
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── inventory.py
│   │   ├── templates/
│   │   └── tests/
│   ├── orders/
│   │   ├── models.py
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── payment.py
│   │   ├── templates/
│   │   └── tests/
│   └── search/
│       ├── models.py
│       ├── routes.py
│       ├── services.py
│       ├── indexer.py
│       └── tests/
├── shared/
│   ├── database.py
│   ├── auth.py
│   ├── logging.py
│   └── pagination.py
├── config/
│   ├── settings.py
│   ├── development.py
│   └── production.py
├── static/
│   ├── css/
│   ├── js/
│   └── images/
├── templates/
│   └── base.html
└── main.py

Notice a few things. The shared folder is small. Only truly cross-cutting concerns live there. The config folder has environment-specific settings. The static and templates folders are top-level because they’re shared across features.

What About Large Files?

Sometimes a feature gets big. The services.py file in the orders feature might grow to 2,000 lines. That’s okay for a while, but eventually you’ll want to split it. When that happens, create a subfolder inside the feature. For example:

features/orders/
├── models.py
├── routes.py
├── services/
│   ├── __init__.py
│   ├── checkout.py
│   ├── refund.py
│   └── shipping.py
├── templates/
└── tests/

This keeps the feature boundary intact while allowing internal organization. The key is that the subfolder is still inside the feature. You don’t create a top-level services folder.

Handling Database Migrations

Migrations are tricky. They’re global by nature. I recommend keeping them in a top-level migrations folder. But name them clearly. Instead of 001_initial.py, use 001_create_users_table.py. This makes it easy to find the migration related to a feature.

What About Configuration Files?

Dockerfiles, CI/CD configs, linter settings, and so on. These go in the project root. Don’t bury them in subfolders. Developers need to find them quickly. A clean root with a few well-named files is better than a deep hierarchy.

A Practical Tip for Starting

If you’re refactoring an existing project, don’t try to do it all at once. Pick one feature and move it to the new structure. Test it. Then move the next one. This incremental approach reduces risk and helps you learn what works for your specific project.

At PythonSkillset, we did this over a few weeks. Each week, we moved one or two features. By the end, the project was cleaner, and the team was happier. We also found bugs that had been hiding in the old structure because the new layout made dependencies obvious.

The One Thing to Avoid

Don’t create a misc or other folder. I know it’s tempting. But that folder will grow and become the new junk drawer. If something doesn’t fit anywhere, ask why. Maybe it should be its own feature. Maybe it belongs in shared. Maybe it’s dead code that should be deleted.

Final Thoughts

Folder structure is not glamorous. But it’s one of the few things that affects your productivity every single day. A good structure makes your codebase feel smaller than it is. A bad one makes it feel bigger.

Start with feature-based grouping. Keep shared code minimal. Be consistent with naming. And don’t be afraid to refactor when the structure stops working. Your future self—and your teammates—will thank you.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.