# AppointMe vs FullStackHero

> AppointMe and FullStackHero are both MIT .NET 10 modular-monolith SaaS templates. FullStackHero ships more modules and tests; AppointMe adds external OIDC, Wolverine messaging and multi-company users.

Source page: https://appointme.dev/docs/compare/fullstackhero/

* **Both are:** MIT, .NET 10, modular monolith, React 19
* **FSH leads on:** Module count, tests, per-tenant databases
* **AppointMe leads on:** External OIDC, durable messaging, multi-company users
* **Pick FSH if:** You want the most built-in modules today

## At a glance

**Both start from the same shape, a .NET 10 modular monolith with vertical slices, and differ most in identity, messaging and breadth.**

|                           | AppointMe                                                                                                                                                            | FullStackHero                                                                                                                                                                                                 |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Architecture              | Modular monolith, vertical slices. 4 modules (Identity, Organizations, CRM, Booking), each with its own `DbContext`, schema and `.Contracts` project.                | Modular monolith, vertical slices. 10 modules (Identity, Multitenancy, Auditing, Files, Chat, Notifications, Webhooks, Billing, Catalog, Tickets) on shared BuildingBlocks, each with a `.Contracts` project. |
| Multi-tenancy             | One shared database with a `CompanyId` on every row. The company comes from the `X-Company-Id` header on each request, and one user can belong to several companies. | Finbuckle.MultiTenant 10. A signed-in caller’s tenant comes from the `tenant` claim in its JWT. Shared database by default, with an optional connection string per tenant.                                    |
| Auth                      | OIDC with Keycloak locally and Entra External ID on Azure. JWT Bearer for the API, cookies for browser flows, sign-up through the app.                               | ASP.NET Core Identity issuing its own JWTs with rotating refresh tokens. TOTP two-factor, sessions, password policies and impersonation.                                                                      |
| Permissions               | `Permission` fields auto-discovered from code, 4 built-in roles, per-company role overrides. Checked in the handler with `principal.Require(...)`.                   | A flat permission registry, checked with `.RequirePermission(...)` on endpoints. Roles and user groups; permissions resolved server-side.                                                                     |
| Data access               | EF Core 10 for writes, Dapper for reads. SQL Server.                                                                                                                 | EF Core 10 with the specification pattern and soft-delete and audit interceptors. PostgreSQL.                                                                                                                 |
| Messaging                 | Wolverine 6: domain events and handlers on durable local queues in SQL Server. Azure Service Bus by config.                                                          | Its own outbox, inbox and dispatcher. In-memory bus by default, RabbitMQ as an option.                                                                                                                        |
| Frontend                  | One React 19 SPA: TypeScript 6, Vite+, Tailwind 4, shadcn/ui, TanStack Query. Hooks and types generated by orval.                                                    | Two React 19 apps, an operator console and a tenant app: Vite 8, TypeScript 6, TanStack Query, Radix and Tailwind 4. Hand-written typed client, SignalR and SSE.                                              |
| Local dev                 | Aspire runs SQL Server, Keycloak, Mailpit, the API and the SPA, then migrates and seeds demo data. `compose.yaml` as an alternative.                                 | Aspire runs Postgres, Valkey, RustFS (S3), a migrator, a demo seeder, the API and both apps. An `fsh` CLI scaffolds and checks your setup.                                                                    |
| Deploy                    | Bicep for Azure App Service, Azure SQL and Key Vault. GitHub Actions with OIDC deploys on a `v*` tag.                                                                | Docker Compose production stack, Terraform for AWS (ECS Fargate, RDS, ElastiCache, S3 and CloudFront), and Dokploy.                                                                                           |
| Tests                     | 221 xUnit unit tests, with no database or HTTP host. No architecture, integration or frontend tests.                                                                 | 1,600+ backend tests, including NetArchTest architecture tests and Testcontainers integration tests, plus 200+ Playwright tests, per its README.                                                              |
| Docs                      | This site, about 50 pages.                                                                                                                                           | 93 pages at fullstackhero.net/docs.                                                                                                                                                                           |
| License                   | MIT. A PRO add-on is planned.                                                                                                                                        | MIT. No paid tier.                                                                                                                                                                                            |
| Maintainers and community | One main maintainer (BravoDev). About 50 GitHub stars. Public since June 2026.                                                                                       | Led by Mukesh Murugan, with more than 50 contributors. About 6.9k stars. Public since 2021, sponsored through Open Collective.                                                                                |
| Live demo                 | [One click](https://app.appointme.dev/api/v1/login/demo), no sign-up.                                                                                                | None hosted. You run it locally with seeded accounts.                                                                                                                                                         |

AppointMe facts are from commit `2ccbe87`. FullStackHero facts are from its `main` branch, README and docs (see [Sources](#sources)).

## Where AppointMe is stronger

**AppointMe goes deeper on three foundations: who the user is, which company they act for, and how messages survive a restart.**

* **Identity lives in an identity provider.** Users sign in through OIDC: Keycloak locally, Entra External ID on Azure. Password storage, reset and email verification happen in the provider, and the app keeps its own `User` record. See [Identity & principal](/docs/concepts/identity-and-principal/).
* **One user, many companies.** The company is picked per request from `X-Company-Id`, and access is checked against the user’s employee record for that company. A receptionist who works for two clinics switches between them without signing in again. See [Multi-tenancy](/docs/concepts/multi-tenancy/#how-a-request-gets-its-company).
* **Messaging on a maintained library.** Wolverine publishes domain events from EF Core and runs handlers on durable queues stored in SQL Server, and the company and caller travel on the message envelope. FullStackHero has an outbox too, but its dispatcher and bus are code you own and maintain. See [Domain events & messaging](/docs/concepts/messaging/).
* **Hand-written SQL for reads.** Commands go through EF Core aggregates, while lists, search and the dashboard are Dapper queries you can read and tune. See [CQRS](/docs/concepts/cqrs/).
* **A generated frontend client.** The API build writes its OpenAPI document, and orval turns it into TypeScript types and TanStack Query hooks, so a changed endpoint becomes a compile error in the SPA. See [Typed API client](/docs/frontend/api-client/).
* **A smaller core and a public demo.** Four modules, two of which (CRM and Booking) are examples you can [remove](/docs/guides/remove-a-module/). The live demo logs you in with one click.
* **An Azure path.** Bicep, Key Vault references, a managed identity and secret-less GitHub OIDC are wired up. See [Azure with Bicep](/docs/deploy/azure/).

## Where FullStackHero is stronger

**FullStackHero is broader, better tested and more established, and those gaps are real.**

* **More modules, ready now.** Auditing, files on S3 with presigned URLs, signed webhooks, chat, a notification inbox, tickets and a catalog. Its Billing module tracks plans, invoices and usage, but doesn’t take payments: its docs say “no Stripe SDK, no card webhooks”. AppointMe’s billing and notifications are [planned for PRO](/docs/start/free-vs-pro/).
* **A much larger test suite.** Architecture tests enforce module boundaries, Testcontainers runs integration tests against real PostgreSQL, and Playwright covers both apps. AppointMe has unit tests only, and its module boundaries rely on project references. See [Testing](/docs/testing/overview/).
* **More tenancy options.** Each tenant can have its own connection string. Creating a tenant runs a provisioning job (database, migrations, seeding, cache warm-up) that resumes from the failed step. AppointMe supports one shared database.
* **Account features inside the app.** TOTP two-factor, session management and time-limited impersonation across tenants. AppointMe has no impersonation, and anything like 2FA is whatever you configure in Keycloak or Entra.
* **An operator console.** A separate admin app for whoever runs the platform. In AppointMe, super admins (the `Authentication:SuperAdmins` email list) only get the Hangfire dashboard.
* **AWS and production plumbing.** Terraform for AWS, a Docker Compose production stack, and a one-shot DbMigrator so the API never migrates on startup. AppointMe applies migrations when the API starts and ships Bicep for Azure only.
* **Track record.** Public since 2021, about 6.9k stars, more than 50 contributors and 93 docs pages. AppointMe went public in June 2026 and has one main maintainer, so its bus factor is lower.

### Why an external identity provider instead of ASP.NET Core Identity?

Issuing your own tokens means you own password hashing, lockout, reset flows, two-factor and signing-key rotation. Keycloak and Entra External ID already do that work. AppointMe only validates their tokens and keeps a local `User` row for its own data.

The cost is one more moving part. Keycloak runs as a container in local dev, and production needs an Entra External ID tenant created in the portal. Sign-up and invitations go through the app’s `IIdentityProvider`, so changing provider touches one interface.

If you want users, tokens and data in one database and one deployable, FullStackHero’s in-app Identity is simpler to run.

## Choose FullStackHero if… / choose AppointMe if…

**Decide on the identity model and on breadth: FullStackHero for the most in-app features, AppointMe for an external IdP and a smaller core.**

| Choose FullStackHero if…                                    | Choose AppointMe if…                                                             |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------- |
| You want auditing, files, webhooks or chat on day one       | You’d rather add modules to a smaller core you understand end to end             |
| You want users, passwords and tokens in your own database   | You want sign-in handled by Keycloak, Entra External ID or another OIDC provider |
| Some tenants need their own database                        | One shared database with a `CompanyId` column fits your scale                    |
| Each user belongs to one tenant                             | Users work for several companies and switch between them                         |
| You deploy to AWS or a single Docker host                   | You deploy to Azure                                                              |
| You run PostgreSQL                                          | You run SQL Server                                                               |
| A large test suite and an established community matter most | You want durable messaging and Dapper reads already wired                        |

## Migrating from FullStackHero

**There’s no migration tool: you port each slice onto AppointMe’s equivalent, and the biggest step is moving users to the identity provider.**

| FullStackHero                             | AppointMe                                                                           | Notes                                                                                                                                                   |
| ----------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Tenant (`AppTenantInfo`, Finbuckle)       | `Company` in the Organizations module, keyed by `CompanyId`                         | One shared database. There are no per-tenant connection strings.                                                                                        |
| `tenant` claim or header                  | `X-Company-Id` header, checked against the user’s employee record                   | The SPA sends the header on every call.                                                                                                                 |
| `IGlobalEntity` (not tenant-scoped)       | An entity with no `CompanyId` filter, such as `User`                                |                                                                                                                                                         |
| Global EF tenant filter                   | A named filter per entity, plus `[CompanyId] = @CompanyId` in every Dapper read     | See [writes](/docs/concepts/multi-tenancy/#writes-ef-core-query-filters) and [reads](/docs/concepts/multi-tenancy/#reads-dapper-carries-the-predicate). |
| Module and `.Contracts` project           | Module and `.Contracts` project                                                     | Same shape. Other modules reference only `.Contracts`.                                                                                                  |
| Mediator command and handler              | A Wolverine handler with a `HandleAsync` method                                     | Add `CompanyId` and `IPrincipal` parameters to get the tenant and the caller.                                                                           |
| FluentValidation validator                | Value-object factories such as `Email.CreateOrNull`                                 | They throw `ValidationException`. See [IDs & value objects](/docs/concepts/ids-and-value-objects/).                                                     |
| `.RequirePermission(...)` on the endpoint | `principal.Require(CustomerPermissions.View)` in the handler                        | See [Add a permission](/docs/guides/add-a-permission/).                                                                                                 |
| Integration event through the outbox      | An event record in the publisher’s `.Contracts` project, handled on a durable queue | See [Cross-module events](/docs/guides/cross-module-events/).                                                                                           |
| ASP.NET Core Identity users               | Users in Keycloak or Entra External ID, plus a local `User` row                     | Plan how existing users set a new password.                                                                                                             |
| PostgreSQL                                | SQL Server                                                                          | Regenerate migrations. Raw SQL uses `[brackets]`.                                                                                                       |

Gotcha

FullStackHero modules you rely on (auditing, files, webhooks, chat) have no AppointMe equivalent. Port them as new modules, or keep FullStackHero.

## Sources

**Every FullStackHero fact on this page comes from the project’s own repository and docs, accessed Nov 2026.**

| Source                                                                                                       | Used for                                                                                                              |
| ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| [github.com/fullstackhero/dotnet-starter-kit](https://github.com/fullstackhero/dotnet-starter-kit)           | README (modules, stack, test counts, deploy), repository tree, `src/Directory.Packages.props`, stars and contributors |
| [fullstackhero.net](https://fullstackhero.net/)                                                              | No paid tier, Open Collective sponsorship, no hosted demo                                                             |
| [fullstackhero.net/docs/modules/multitenancy/](https://fullstackhero.net/docs/modules/multitenancy/)         | Tenant resolution, isolation model, provisioning                                                                      |
| [fullstackhero.net/docs/modules/identity/](https://fullstackhero.net/docs/modules/identity/)                 | JWT and refresh tokens, permissions, two-factor, impersonation                                                        |
| [fullstackhero.net/docs/modules/billing/](https://fullstackhero.net/docs/modules/billing/)                   | Billing scope and “no Stripe SDK”                                                                                     |
| [fullstackhero.net/docs/building-blocks/eventing/](https://fullstackhero.net/docs/building-blocks/eventing/) | Outbox, inbox and bus providers                                                                                       |
| [fullstackhero.net/sitemap-0.xml](https://fullstackhero.net/sitemap-0.xml)                                   | Docs page count                                                                                                       |

AppointMe facts are from [bravodev-hub/appointme](https://github.com/bravodev-hub/appointme) at commit `2ccbe87`.

## Next

* [How to choose a .NET SaaS template](/docs/compare/overview/) (Compare)
* [Multi-tenancy](/docs/concepts/multi-tenancy/) (Concept)
* [Free vs PRO](/docs/start/free-vs-pro/) (Overview)
