# Tour of the codebase

> Where everything lives in the AppointMe .NET 10 solution. The API host, four module projects with their contracts and tests, the Aspire AppHost, the React SPA, and the files that pin versions.

Source page: https://appointme.dev/docs/start/codebase-tour/

* **Solution:** `AppointMe.sln`, also as `AppointMe.slnx`
* **Projects:** 17 .NET projects, plus the SPA
* **Per module:** `.Contracts`, the module, `.Tests`
* **Conventions:** Written down in `CLAUDE.md`

## The solution at a glance

**One API host loads four modules. Everything else in the repo runs, tests, ships or packages that host.**

* .github/ # CI, CodeQL, gitleaks, Dependabot

  * …

* .template.config/ # the `dotnet new` rename rules

  * …

* docker/keycloak/ # dev-cert export for Compose

  * …

* docs/ # the identity-resolution deep dive

  * …

* infra/ # Bicep for Azure, a Cloudflare Worker

  * …

* src/

  * AppointMe.Api/ # the host: Program.cs and cross-cutting code

    * …

  * AppointMe.Api.Tests/

    * …

  * AppointMe.Aspire/ # the AppHost and the Keycloak realm

    * …

  * AppointMe.Frontend/ # the React 19 SPA

    * …

  * AppointMe.Shared/ # the shared kernel

    * …

  * AppointMe.Shared.Tests/

    * …

  * Booking/ # appointments, attendees, staff, dashboard

    * …

  * CRM/ # customers

    * …

  * Identity/ # users, sign-up, login

    * …

  * Organizations/ # companies, employees, invitations, roles

    * …

* templates/ # NuGet packaging and the smoke test

  * …

* AppointMe.sln

* CLAUDE.md # architecture and conventions

* compose.yaml # the backing services without Aspire

* Directory.Build.props

* Directory.Packages.props

* global.json

* NuGet.config

| Project            | Holds                                                                                                                                                                                                     |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AppointMe.Api`    | `Program.cs`, which registers the modules, plus the host’s cross-cutting folders: `Authentication`, `Authorization`, `MultiTenancy`, `ErrorHandling`, `Wolverine`, `Hangfire`, `OpenApi`, `Demo` and more |
| `AppointMe.Aspire` | The AppHost: containers, ports, the realm import (`appointme-realm.json`) and the dev-certificate check                                                                                                   |
| `AppointMe.Shared` | Types every module uses: `CompanyId` and `ICurrentCompany`, `Permission`, `AggregateRoot`, value objects, `IEndpoint`, the migration runner and the job abstractions                                      |
| `Identity`         | Users, sign-up, login and logout, and the Keycloak and Entra identity providers                                                                                                                           |
| `Organizations`    | Companies, onboarding, employees, invitations, role permission overrides and `GET /me`                                                                                                                    |
| `CRM`              | Customers                                                                                                                                                                                                 |
| `Booking`          | Appointments, attendees, service providers and the dashboard queries                                                                                                                                      |

`AppointMe.Frontend` isn’t in the solution. It’s an npm project that the AppHost starts with `AddViteApp`.

## Anatomy of a module

**Every module is three projects: a public `.Contracts` project, the module itself, and its `.Tests`.**

| Project                   | Holds                                                                                                                                             | Referenced by                               |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| `AppointMe.Crm.Contracts` | Integration events such as `CustomerRegisteredEvent`, `CustomerSnapshot`, and interfaces other modules call, such as `ICustomerRehydrationSource` | Other modules. Booking references it.       |
| `AppointMe.Crm`           | Everything else: aggregates, slices, the `DbContext`, migrations, permissions                                                                     | The API host and `AppointMe.Crm.Tests` only |
| `AppointMe.Crm.Tests`     | xUnit.net v3 tests, in folders that mirror the slices                                                                                             | —                                           |

Inside the module, CRM is the smallest complete example:

* src/CRM/AppointMe.Crm/

  * Authorization/

    * CrmDefaultGrantPolicy.cs # which roles get which permissions

  * Configuration/

    * CrmModule.cs # `AddCrmModule()`

  * Customers/ # one feature folder

    * Customer.cs # the aggregate root

    * CustomerId.cs

    * CustomerPermissions.cs

    * Database/ # EF config, query filters, Dapper repository

      * …

    * RegisterCustomer/ # one slice per use case

      * RegisterCustomer.cs # the domain factory
      * RegisterCustomerCommand.cs
      * RegisterCustomerCommandHandler.cs
      * RegisterCustomerEndpoint.cs
      * RegisterCustomerRequest.cs
      * RegisterCustomerResponse.cs

    * GetCustomers/

      * …

    * …

  * Database/

    * CrmDbContext.cs # schema `crm`

    * Migrations/

      * …

  * ModuleAssembly.cs

| Folder                     | What goes there                                                                                   |
| -------------------------- | ------------------------------------------------------------------------------------------------- |
| `Authorization/`           | The module’s `IDefaultGrantPolicy`: the permissions each role starts with                         |
| `Configuration/`           | One `Add<Module>Module()` extension that registers everything the module owns                     |
| `Database/` (module level) | The `DbContext`, with its own schema and its own `__EFMigrationsHistory` table, and `Migrations/` |
| `<Feature>/`               | The aggregate, its ID type, its `Permissions` class, and anything several slices share            |
| `<Feature>/Database/`      | EF type configurations, named query filters, and the Dapper repository and queries                |
| `<Feature>/<Slice>/`       | One use case: command or query, handler, endpoint, request and response                           |

`AddCrmModule()` shows the whole registration recipe. Each module repeats it with its own types:

src/CRM/AppointMe.Crm/Configuration/CrmModule.cs

```cs
return services
    .AddDbContext<CrmDbContext>((serviceProvider, options) =>
    {
        options.UseSqlServer(serviceProvider.GetRequiredService<ConnectionStrings>().AppointMeSql,
            builder =>
            {
                builder.MigrationsHistoryTable("__EFMigrationsHistory", CrmDbContext.DefaultSchema);
            });
    })
    .AddDatabaseMigration<CrmDbContext>()
    .AddEndpoints(ModuleAssembly.Instance)
    .AddSingleton<IDbConnectionFactory, SqlConnectionFactory>(serviceProvider =>
        new SqlConnectionFactory(serviceProvider.GetRequiredService<ConnectionStrings>().AppointMeSql))
    .AddScoped<CustomersRepository>()
    .AddScoped<ICustomerRehydrationSource, CustomerRehydrationSource>()
    .AddScoped<SeedDemoCustomers>()
    .AddScoped<TopUpDemoCustomersJob>()
    .AddPermissions(ModuleAssembly.Instance)
    .AddRecurringJobs(ModuleAssembly.Instance);
```

Endpoints, permissions and recurring jobs are found by scanning the module’s assembly, so a new endpoint or permission needs no registration line. [Vertical slices](/docs/concepts/vertical-slices/) explains the slice rules, and [Add a module](/docs/guides/add-a-module/) builds a fifth one.

## I want to change X: look in Y

**Most changes touch one slice folder. The rest have one obvious home, listed here.**

| I want to change                                     | Look in                                                                            |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------- |
| An endpoint’s route, verb or response                | `<Module>/<Feature>/<Slice>/<Slice>Endpoint.cs`, an `IEndpoint`                    |
| What a permission is called                          | `<Feature>/<Entity>Permissions.cs`, for example `Customers/CustomerPermissions.cs` |
| Which roles get a permission by default              | `<Module>/Authorization/<Module>DefaultGrantPolicy.cs`                             |
| A table, column or index                             | `<Feature>/Database/<Entity>TypeConfiguration.cs`, then add a migration            |
| A tenant or soft-delete filter                       | `<Module>/Database/<Module>DbContext.cs`, in `OnModelCreating`                     |
| A read query                                         | `<Feature>/Database/`: the Dapper repository and its `<Entity>Queries.cs`          |
| An event other modules consume                       | `<Module>.Contracts/<Feature>/Events/`                                             |
| How an exception maps to an HTTP status              | `src/AppointMe.Api/ErrorHandling/`                                                 |
| The tenant header                                    | `src/AppointMe.Api/Program.cs`, `AddAppointMeMultiTenancy(...)`                    |
| Wolverine’s transport or handler policies            | `src/AppointMe.Api/Wolverine/WolverineHostBuilderExtensions.cs`                    |
| Local containers, ports or images                    | `src/AppointMe.Aspire/Program.cs`, and `compose.yaml` to match                     |
| Keycloak clients, users or email settings            | `src/AppointMe.Aspire/appointme-realm.json`                                        |
| Local connection strings, Keycloak URLs or demo mode | `src/AppointMe.Api/appsettings.Development.json`                                   |
| A frontend route                                     | `src/AppointMe.Frontend/src/app/router/router.tsx`                                 |
| Lint or format rules                                 | The `lint` and `fmt` blocks of `src/AppointMe.Frontend/vite.config.ts`             |
| A package version                                    | `Directory.Packages.props`                                                         |

## Build files at the root

**Four root files set the SDK, the language, every package version and the package source for all 17 projects.**

| File                       | What it sets                                                                                                                                        |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Directory.Build.props`    | `net10.0`, C# 14, nullable and implicit usings for every project, plus the `Company`, `Authors` and `RepositoryUrl` metadata                        |
| `Directory.Packages.props` | Central package management with transitive pinning. Every `PackageVersion` lives here, so a `.csproj` has `PackageReference` items with no version. |
| `global.json`              | SDK `10.0.0`, `rollForward: latestMajor`, `allowPrerelease: false`                                                                                  |
| `NuGet.config`             | Clears all sources and maps every package (`*`) to nuget.org. Add a private feed here, with its own mapping.                                        |

Directory.Build.props

```xml
<PropertyGroup>
  <Company>BravoDev</Company>
  <Authors>BravoDev</Authors>
  <RepositoryUrl>https://github.com/bravodev-hub/appointme.git</RepositoryUrl>
  <RepositoryType>Git</RepositoryType>
  <TargetFramework>net10.0</TargetFramework>
  <LangVersion>14</LangVersion>
  <ImplicitUsings>enable</ImplicitUsings>
  <Nullable>enable</Nullable>
  <WarningsAsErrors>false</WarningsAsErrors>
  <NoWarn>$(NoWarn);NU1608</NoWarn>
</PropertyGroup>
```

One version lives outside these files. `src/AppointMe.Aspire/AppointMe.Aspire.csproj` names the `Aspire.AppHost.Sdk` version (`13.6.4`), and it has to move by hand with the `Aspire.*` packages. `Directory.Packages.props` also holds one security pin, `System.Security.Cryptography.Pkcs`, with a comment on when to drop it.

## Beyond `src/`

**The frontend, the infrastructure, the Docker helpers, the template packaging and CI each have one folder.**

| Folder                               | What’s there                                                                                                                                                                  | Read more                                                       |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `src/AppointMe.Frontend/`            | The SPA: pages in `src/app`, the generated client in `src/api`, the axios mutator in `src/lib/axios.ts`, `vite.config.ts` and `orval.config.ts`                               | [Frontend overview](/docs/frontend/overview/)                   |
| `infra/`                             | `main.bicep` with eleven modules in `modules/`, `main.bicepparam`, and `cloudflare-worker/`                                                                                   | [Azure with Bicep](/docs/deploy/azure/)                         |
| `docker/keycloak/`                   | `export-dev-cert.sh` and a `certs/` folder for its git-ignored output                                                                                                         | [Local services & ports](/docs/reference/local-services/)       |
| `templates/` and `.template.config/` | The package project, the nuget.org README, the generated project’s README in `overlay/`, `smoke-test.sh`, and `template.json` with its rename design in `templates/README.md` | [Rename your project](/docs/guides/rename-your-project/)        |
| `.github/`                           | `devtest.yml` (build, test, deploy on `v*`), `codeql.yml`, `secret-scan.yml` (gitleaks), `template.yml`, `dependabot.yml`                                                     | [GitHub Actions CI/CD](/docs/deploy/ci-cd/)                     |
| `docs/`                              | `identity-resolution.md`, a two-part write-up on identity and principal                                                                                                       | [Identity & principal](/docs/concepts/identity-and-principal/)  |
| `.claude/` and `CLAUDE.md`           | Conventions for AI coding agents, and the `regenerate-api` and `verify` skills                                                                                                | [Working with AI agents](/docs/guides/ai-assisted-development/) |

### Why a separate .Contracts project for every module?

A module may reference another module’s `.Contracts` project, never the module itself. Booking references `AppointMe.Crm.Contracts` and `AppointMe.Organizations.Contracts`. Only the API host and each module’s own tests reference a module project.

So the compiler stops Booking from reaching into CRM’s `DbContext` or handlers. What crosses the boundary is events, snapshots and a few interfaces, all in a project small enough to review.

The cost is an extra project per module. [Modular monolith](/docs/concepts/modular-monolith/) covers the rest of the boundary rules.

## Next

* [Modular monolith](/docs/concepts/modular-monolith/) (Concept)
* [Vertical slices](/docs/concepts/vertical-slices/) (Concept)
* [Build your first feature](/docs/start/first-feature/) (Tutorial)
