Directory Structure of the Application

How to design a clear and scalable directory structure for projects in Nette Framework? We'll show you proven practices that will help you organize your code. You'll learn:

  • how to logically structure the application into directories
  • how to design the structure to scale well as the project grows
  • what are the possible alternatives and their advantages or disadvantages

It's important to mention that Nette Framework itself doesn't insist on any specific structure. It's designed to be easily adaptable to any needs and preferences.

Basic Project Structure

Although Nette Framework doesn't dictate any fixed directory structure, there is a proven default arrangement in the form of Web Project:

web-project/
├── app/              ← application directory
├── assets/           ← SCSS, JS files, images..., alternatively resources/
├── bin/              ← command line scripts
├── config/           ← configuration
├── log/              ← logged errors
├── temp/             ← temporary files, cache
├── tests/            ← tests
├── vendor/           ← libraries installed by Composer
└── www/              ← public directory (document-root)

You can freely modify this structure according to your needs – rename or move folders. Then you just need to adjust the relative paths to directories in Bootstrap.php and possibly composer.json. Nothing else is needed, no complex reconfiguration, no constant changes. Nette has smart autodetection and automatically recognizes the application location including its URL base.

Code Organization Principles

When you first explore a new project, you should be able to quickly orient yourself. Imagine clicking on the app/Model/ directory and seeing this structure:

app/Model/
├── Services/
├── Repositories/
└── Entities/

From this, you'll only learn that the project uses some services, repositories and entities. You won't learn anything about the actual purpose of the application.

Let's look at a different approach – organization by domains:

app/Model/
├── Cart/
├── Payment/
├── Order/
└── Product/

This is different – at first glance it's clear that this is an e-commerce site. The directory names themselves reveal what the application can do – it works with payments, orders and products.

The first approach (organization by class type) brings several problems in practice: code that is logically related is scattered across different folders and you have to jump between them. Therefore, we will organize by domains.

Namespaces

It is conventional that the directory structure corresponds to namespaces in the application. This means that the physical location of files corresponds to their namespace. For example, a class located in app/Model/Product/ProductRepository.php should have namespace App\Model\Product. This principle helps in code orientation and simplifies autoloading.

Singular vs Plural in Names

Notice that we use singular for main application directories: app, config, log, temp, www. The same applies inside the application: Model, Core, Presentation. This is because each represents one unified concept.

Similarly, app/Model/Product represents everything about products. We don't call it Products because it's not a folder full of products (that would contain files like iphone.php, samsung.php). It's a namespace containing classes for working with products – ProductRepository.php, ProductService.php.

The folder app/Tasks is plural because it contains a set of standalone executable scripts – CleanupTask.php, ImportTask.php. Each of them is an independent unit.

For consistency, we recommend using:

  • Singular for namespaces representing a functional unit (even if working with multiple entities)
  • Plural for collections of independent units
  • In case of uncertainty or if you don't want to think about it, choose singular

Public Directory www/

This directory is the only one accessible from the web (so-called document-root). You may often encounter the name public/ instead of www/ – it's just a matter of convention and doesn't affect the functionality. The directory contains:

  • Application entry point index.php
  • .htaccess file with mod_rewrite rules (for Apache)
  • Static files (CSS, JavaScript, images)
  • Uploaded files

For proper application security, it's crucial to have correctly configured document-root.

Never place the node_modules/ folder in this directory – it contains thousands of files that may be executable and shouldn't be publicly accessible.

Application Directory app/

This is the main directory with application code. Basic structure:

app/
├── Core/               ← infrastructure matters
├── Model/              ← business logic
├── Presentation/       ← presenters and templates
├── Tasks/              ← command scripts
└── Bootstrap.php       ← application bootstrap class

Bootstrap.php is the application startup class that initializes the environment, loads configuration and creates the DI container.

Let's now look at individual subdirectories in detail.

Presenters and Templates

We have the presentation part of the application in the app/Presentation directory. An alternative is the short app/UI. This is the place for all presenters, their templates and any helper classes.

We organize this layer by domains. In a complex project that combines e-commerce, blog and API, the structure would look like this:

app/Presentation/
├── Shop/              ← e-commerce frontend
│   ├── Product/
│   ├── Cart/
│   └── Order/
├── Blog/              ← blog
│   ├── Home/
│   └── Post/
├── Admin/             ← administration
│   ├── Dashboard/
│   └── Products/
└── Api/               ← API endpoints
	└── V1/

Conversely, for a simple blog we would use this structure:

app/Presentation/
├── Front/             ← website frontend
│   ├── Home/
│   └── Post/
├── Admin/             ← administration
│   ├── Dashboard/
│   └── Posts/
├── Error/
└── Export/            ← RSS, sitemaps etc.

Folders like Home/ or Dashboard/ contain presenters and templates. Folders like Front/, Admin/ or Api/ are called modules. Technically, these are regular directories that serve for logical organization of the application.

Each folder with a presenter contains a similarly named presenter and its templates. For example, the Dashboard/ folder contains:

Dashboard/
├── DashboardPresenter.php     ← presenter
└── default.latte              ← template

This directory structure is reflected in class namespaces. For example, DashboardPresenter is in the namespace App\Presentation\Admin\Dashboard (see presenter mapping):

namespace App\Presentation\Admin\Dashboard;

class DashboardPresenter extends Nette\Application\UI\Presenter
{
	// ...
}

We refer to the Dashboard presenter inside the Admin module in the application using colon notation as Admin:Dashboard. To its default action then as Admin:Dashboard:default. For nested modules, we use more colons, for example Shop:Order:Detail:default.

Flexible Structure Development

One of the great advantages of this structure is how elegantly it adapts to growing project needs. As an example, let's take the part generating XML feeds. Initially, we have a simple form:

Export/
├── ExportPresenter.php   ← one presenter for all exports
├── sitemap.latte         ← template for sitemap
└── feed.latte            ← template for RSS feed

Over time, more feed types are added and we need more logic for them… No problem! The Export/ folder simply becomes a module:

Export/
├── Sitemap/
│   ├── SitemapPresenter.php
│   └── sitemap.latte
└── Feed/
	├── FeedPresenter.php
	├── amazon.latte         ← feed for Amazon
	└── ebay.latte           ← feed for eBay

This transformation is completely smooth – just create new subfolders, divide the code into them and update links (e.g. from Export:feed to Export:Feed:amazon). Thanks to this, we can gradually expand the structure as needed, the nesting level is not limited in any way.

For example, if in the administration you have many presenters related to order management, such as OrderDetail, OrderEdit, OrderDispatch etc., you can create a module (folder) Order for better organization, which will contain (folders for) presenters Detail, Edit, Dispatch and others.

Template Location

In previous examples, we saw that templates are located directly in the folder with the presenter:

Dashboard/
├── DashboardPresenter.php     ← presenter
├── DashboardTemplate.php      ← optional template class
└── default.latte              ← template

This location proves to be the most convenient in practice – you have all related files right at hand.

Alternatively, you can place templates in a templates/ subfolder. Nette supports both variants. You can even place templates completely outside the Presentation/ folder. Everything about template location options can be found in the Template Lookup chapter.

Helper Classes and Components

Presenters and templates often come with other helper files. We place them logically according to their scope:

1. Directly with the presenter in case of specific components for the given presenter:

Product/
├── ProductPresenter.php
├── ProductGrid.php        ← component for product listing
└── FilterForm.php         ← form for filtering

2. For module – we recommend using the Accessory folder, which is placed neatly at the beginning of the alphabet:

Front/
├── Accessory/
│   ├── NavbarControl.php    ← components for frontend
│   └── TemplateFilters.php
├── Product/
└── Cart/

3. For entire application – in Presentation/Accessory/:

app/Presentation/
├── Accessory/
│   ├── LatteExtension.php
│   └── TemplateFilters.php
├── Front/
└── Admin/

Or you can place helper classes like LatteExtension.php or TemplateFilters.php in the infrastructure folder app/Core/Latte/. And components in app/Components. The choice depends on team conventions.

Model – Heart of the Application

The model contains all business logic of the application. For its organization, the same rule applies – we structure by domains:

app/Model/
├── Payment/                   ← everything about payments
│   ├── PaymentFacade.php      ← main entry point
│   ├── PaymentRepository.php
│   ├── Payment.php            ← entity
├── Order/                     ← everything about orders
│   ├── OrderFacade.php
│   ├── OrderRepository.php
│   ├── Order.php
└── Shipping/                  ← everything about shipping

In the model, you typically encounter these types of classes:

Facades: represent the main entry point into a specific domain in the application. They act as an orchestrator that coordinates cooperation between different services to implement complete use-cases (like “create order” or “process payment”). Under their orchestration layer, the facade hides implementation details from the rest of the application, thus providing a clean interface for working with the given domain.

class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// validation
		// order creation
		// email sending
		// writing to statistics
	}
}

Services: focus on specific business operations within a domain. Unlike facades that orchestrate entire use-cases, a service implements specific business logic (like price calculations or payment processing). Services are typically stateless and can be used either by facades as building blocks for more complex operations, or directly by other parts of the application for simpler tasks.

class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// price calculation
	}
}

Repositories: handle all communication with the data storage, typically a database. Their task is to load and save entities and implement methods for searching them. A repository shields the rest of the application from database implementation details and provides an object-oriented interface for working with data.

class OrderRepository
{
	public function find(int $id): ?Order
	{
	}

	public function findByCustomer(int $customerId): array
	{
	}
}

Entities: objects representing main business concepts in the application that have their identity and change over time. Typically these are classes mapped to database tables using ORM (like Nette Database Explorer or Doctrine). Entities can contain business rules concerning their data and validation logic.

// Entity mapped to database table orders
class Order extends Nette\Database\Table\ActiveRow
{
	public function addItem(Product $product, int $quantity): void
	{
		$this->related('order_items')->insert([
			'product_id' => $product->id,
			'quantity' => $quantity,
			'unit_price' => $product->price,
		]);
	}
}

Value objects: immutable objects representing values without their own identity – for example, a money amount or email address. Two instances of a value object with the same values are considered identical.

Infrastructure Code

The Core/ folder (or also Infrastructure/) is home to the technical foundation of the application. Infrastructure code typically includes:

app/Core/
├── Router/               ← routing and URL management
│   └── RouterFactory.php
├── Security/             ← authentication and authorization
│   ├── Authenticator.php
│   └── Authorizator.php
├── Logging/              ← logging and monitoring
│   ├── SentryLogger.php
│   └── FileLogger.php
├── Cache/                ← caching layer
│   └── FullPageCache.php
└── Integration/          ← integration with ext. services
	├── Slack/
	└── Stripe/

For smaller projects, a flat structure is naturally sufficient:

Core/
├── RouterFactory.php
├── Authenticator.php
└── QueueMailer.php

This is code that:

  • Handles technical infrastructure (routing, logging, caching)
  • Integrates external services (Sentry, Elasticsearch, Redis)
  • Provides basic services for the entire application (mail, database)
  • Is mostly independent of specific domain – cache or logger works the same for e-commerce or blog.

Wondering if a certain class belongs here or in the model? The key difference is that code in Core/:

  • Knows nothing about the domain (products, orders, articles)
  • Can usually be transferred to another project
  • Solves “how it works” (how to send mail), not “what it does” (what mail to send)

Example for better understanding:

  • App\Core\MailerFactory – creates instances of email sending class, handles SMTP settings
  • App\Model\OrderMailer – uses MailerFactory to send emails about orders, knows their templates and when they should be sent

Command Scripts

Applications often need to perform tasks outside of regular HTTP requests – whether it's background data processing, maintenance, or periodic tasks. Simple scripts in the bin/ directory are used for execution, while the actual implementation logic is placed in app/Tasks/ (or app/Commands/).

Example:

app/Tasks/
├── Maintenance/               ← maintenance scripts
│   ├── CleanupCommand.php     ← deleting old data
│   └── DbOptimizeCommand.php  ← database optimization
├── Integration/               ← integration with external systems
│   ├── ImportProducts.php     ← import from supplier system
│   └── SyncOrders.php         ← order synchronization
└── Scheduled/                 ← regular tasks
	├── NewsletterCommand.php  ← sending newsletters
	└── ReminderCommand.php    ← customer notifications

What belongs in the model and what in command scripts? For example, logic for sending one email is part of the model, bulk sending of thousands of emails belongs in Tasks/.

Tasks are usually run from command line or via cron. They can also be run via HTTP request, but security must be considered. The presenter that runs the task needs to be secured, for example only for logged-in users or with a strong token and access from allowed IP addresses. For long tasks, it's necessary to increase the script time limit and use session_write_close() to avoid locking the session.

Other Possible Directories

In addition to the mentioned basic directories, you can add other specialized folders according to project needs. Let's look at the most common ones and their use:

app/
├── Api/              ← API logic independent of presentation layer
├── Database/         ← migration scripts and seeders for test data
├── Components/       ← shared visual components across the application
├── Event/            ← useful if using event-driven architecture
├── Mail/             ← email templates and related logic
└── Utils/            ← helper classes

For shared visual components used in presenters across the application, you can use the app/Components or app/Controls folder:

app/Components/
├── Form/                 ← shared form components
│   ├── SignInForm.php
│   └── UserForm.php
├── Grid/                 ← components for data listings
│   └── DataGrid.php
└── Navigation/           ← navigation elements
	├── Breadcrumbs.php
	└── Menu.php

This is where components with more complex logic belong. If you want to share components between multiple projects, it's good to separate them into a standalone composer package.

In the app/Mail directory you can place email communication management:

app/Mail/
├── templates/            ← email templates
│   ├── order-confirmation.latte
│   └── welcome.latte
└── OrderMailer.php

Presenter Mapping

Mapping defines rules for deriving class names from presenter names. We specify them in the configuration under the key application › mapping.

On this page, we've shown that we place presenters in the app/Presentation folder (or app/UI). We need to tell Nette about this convention in the configuration file. One line is enough:

application:
	mapping: App\Presentation\*\**Presenter

How does mapping work? To better understand, let's first imagine an application without modules. We want presenter classes to fall under the App\Presentation namespace, so that the Home presenter maps to the App\Presentation\HomePresenter class. This is achieved with this configuration:

application:
	mapping: App\Presentation\*Presenter

Mapping works by replacing the asterisk in the mask App\Presentation\*Presenter with the presenter name Home, resulting in the final class name App\Presentation\HomePresenter. Simple!

However, as you see in examples in this and other chapters, we place presenter classes in eponymous subdirectories, for example the Home presenter maps to class App\Presentation\Home\HomePresenter. We achieve this by doubling the colon (requires Nette Application 3.2):

application:
	mapping: App\Presentation\**Presenter

Now we'll move on to mapping presenters into modules. We can define specific mapping for each module:

application:
	mapping:
		Front: App\Presentation\Front\**Presenter
		Admin: App\Presentation\Admin\**Presenter
		Api: App\Api\*Presenter

According to this configuration, the presenter Front:Home maps to class App\Presentation\Front\Home\HomePresenter, while presenter Api:OAuth maps to class App\Api\OAuthPresenter.

Because modules Front and Admin have a similar mapping method and there will probably be more such modules, it's possible to create a general rule that will replace them. A new asterisk for the module will be added to the class mask:

application:
	mapping:
		*: App\Presentation\*\**Presenter
		Api: App\Api\*Presenter

It also works for deeper nested directory structures, such as presenter Admin:User:Edit, where the segment with asterisk repeats for each level and results in class App\Presentation\Admin\User\Edit\EditPresenter.

An alternative notation is to use an array consisting of three segments instead of a string. This notation is equivalent to the previous one:

application:
	mapping:
		*: [App\Presentation, *, **Presenter]
		Api: [App\Api, '', *Presenter]
version: 4.0 3.x 2.x