Skip to content

Directory Structure

A new Maat application separates reusable Dart code from the files that assemble and run the application. This keeps the runtime explicit while making controllers, services, and models easy to import and test.

The Application Root

The full application preset has this shape:

blog/
├── bin/
│ ├── maat.dart
│ └── server.dart
├── bootstrap/
│ └── app.dart
├── config/
│ ├── app.dart
│ ├── database.dart
│ └── config.dart
├── database/
│ ├── migrations/
│ └── seeders/
├── lib/
│ └── app/
│ ├── console/
│ ├── http/
│ │ ├── controllers/
│ │ ├── middleware/
│ │ └── requests/
│ └── providers/
├── public/
├── resources/
│ ├── css/
│ └── views/
├── routes/
│ ├── api.dart
│ ├── channels.dart
│ └── web.dart
├── storage/
│ └── logs/
├── test/
│ └── feature/
├── .env
├── .env.example
├── Dockerfile
└── pubspec.yaml

The --api preset omits public/, resources/, and the view configuration, but keeps the same application lifecycle.

The lib/app Directory

Application classes live under lib/app and are imported with your package name, such as package:blog/app/http/controllers/post_controller.dart.

Directory Responsibility
console/commands Application-specific Sesh commands.
http/controllers Request handlers grouped by feature.
http/middleware Request and response pipeline behavior.
http/requests Reusable authorization and validation rules.
providers Container bindings and application boot logic.

You may add directories such as models, services, or repositories. Maat does not scan the filesystem, so the structure can follow your domain without hidden registration behavior.

The bootstrap Directory

bootstrap/app.dart creates the application and declares its lifecycle:

Future<Application> createApp() =>
Application.configure(
basePath:
Platform.environment['APP_BASE_PATH'] ?? Directory.current.path,
)
.withConfig(appConfig)
.withProviders([
AppServiceProvider.new,
DatabaseServiceProvider.new,
(app) => RouteServiceProvider(
app,
api: apiRoutes,
channels: channels,
web: webRoutes,
),
])
.withMiddleware(
(middleware) => middleware
.use([PublicFiles(publicPath()), TrimStrings(), Cors()])
.alias({'throttle': ThrottleRequests.factory}),
)
.withExceptions((exceptions) {})
.create();

The generated application registers additional mail, notification, broadcasting, WebSocket, and view providers. They are omitted above so the request lifecycle stays visible.

Provider registration is ordered: every provider’s register() method runs before any provider’s boot() method. See Service Providers for the complete lifecycle.

The bin Directory

bin/server.dart builds the application and starts the HTTP server. bin/maat.dart builds the same application and passes it to Sesh. Because both entry points call createApp(), HTTP requests and console commands share the same configuration and container bindings.

The config Directory

Each configuration file exports a map. config/config.dart imports those maps and registers them under the keys used by config('name.key').

Configuration is outside lib because it assembles the application rather than defining reusable package code. See Configuration.

The routes Directory

Route files keep endpoint declarations close together:

  • api.dart is mounted under /api.
  • web.dart registers browser routes without a prefix.
  • channels.dart defines private and presence-channel authorization.

The generated RouteServiceProvider loads all three files during boot.

Database, Resources, and Public Files

  • database/migrations contains schema changes registered by database/migrations.dart.
  • database/seeders contains repeatable data setup.
  • resources/views contains Khnum templates.
  • resources/css/app.css is the Tailwind source file.
  • public contains files served directly, including compiled CSS and images.
  • storage/logs holds file logs when app.log_path is configured.

Generated CSS belongs in public/css and is normally ignored by Git. See Static Files and Assets for serving and path-safety rules.

The test Directory

Feature tests build the real application through bootstrap/app.dart and send requests with TestClient. Unit tests can import classes directly from lib/app without booting the framework.

Terminal window
dart test

See Testing for HTTP assertions, database reset helpers, and framework fakes.

Why Some Directories Sit Outside lib

Dart treats lib/ as importable package code. Bootstrap, routes, configuration, and migrations describe one application instance, so Maat keeps them at the project root. This makes the dependency direction visible: entry points and tests import application assembly files; reusable classes under lib/ do not depend on how the process was started.