The UC Davis Software Catalog starter, based on ucdavis/web-app-template, with a .NET 10 backend, React/TypeScript frontend, and Microsoft Entra ID authentication. It currently provides template examples; catalog-specific data and authorization remain application work.
Start from a checkout of this repository:
git clone https://github.com/ucdavis/software-catalog.git
cd software-catalogChoose the workflow that fits your task:
- Try the app: use the Docker sandbox. Docker supplies the app, database, fictional users, and mail inbox.
- Edit with hot reload: follow development setup for your host or VS Code DevContainer.
- Run checks: see Testing. The automated suites require no running database or external services.
For a new deployment, follow the customization guide and Azure deployment setup. See the template baseline for the upstream revision and runtime requirements.
With Docker running and Compose available, run these commands from the repository root:
export SANDBOX_PROJECT=software-catalog-local
export SANDBOX_PORT=5280
export SANDBOX_MAIL_PORT=8025
docker compose -p "$SANDBOX_PROJECT" -f .devcontainer/docker-compose.sandbox.yml up --build --waitUse a unique SANDBOX_PROJECT for every checkout and different app and inbox ports for sandboxes running concurrently. Keep these values for subsequent commands, including in a new terminal. Compose rejects an unset or empty SANDBOX_PROJECT. See multiple sandboxes for an example.
Open the sandbox and choose Sign in as Sample User. The image builds this checkout's frontend and backend. SQL Server starts first, then the app applies migrations and seeds ten weather records dated January 1–10, 2025. No host Node.js, .NET, .env, Entra registration, or SMTP account is needed. The first build needs internet access for images and dependencies.
The Mailpit inbox captures mail sent from the Notification page. Choose Basic User at local sign-in to exercise the weather API's 403 response, or sign out there. These fictional users have fixed identities and claims; there is no user table.
Stop the sandbox while preserving its database and sign-in keys:
docker compose -p "$SANDBOX_PROJECT" -f .devcontainer/docker-compose.sandbox.yml downRun up --build --wait again to start it or rebuild after source changes. The sandbox serves a published build and has no hot reload. Mailpit messages are not persisted when its container is removed.
To reset the database to the original fixtures, remove this sandbox's volumes and start again:
docker compose -p "$SANDBOX_PROJECT" -f .devcontainer/docker-compose.sandbox.yml down --volumes
docker compose -p "$SANDBOX_PROJECT" -f .devcontainer/docker-compose.sandbox.yml up --build --waitThis deletes the sandbox database and local sign-in keys. The regular development database uses separate volumes. See the sandbox guide for logs, ports, and investigation steps.
All commands below run from the repository root. Choose either host development or the DevContainer after configuring authentication.
For a new checkout, copy the example. Keep an existing server/.env if you already have one:
cp server/.env.example server/.envEdit server/.env before starting the backend or opening the DevContainer:
- Fictional local users: change
Auth__UseLocal="false"toAuth__UseLocal="true". This works only inDevelopment, which the local launch profiles enable. - Entra sign-in: keep local auth disabled and replace
Auth__ClientId="<client-guid>"with your app registration's client ID. Follow Auth Configuration for registration and redirect URIs.
Leave Smtp__Host empty until you configure email. External telemetry is optional. To populate an empty weather table, set DevelopmentData__SeedOnStartup="true". Local configuration files contain secrets and are ignored by Git.
Prerequisites:
- A .NET 10 SDK selected by global.json, with .NET and ASP.NET Core runtime 10.0.12 or later in the 10.0 line.
- Node.js 22.18+ and npm. With nvm,
nvm installandnvm useselect the Node 22 line from .nvmrc. - Docker with Compose for the local SQL Server container.
Restore dependencies and start the database:
npm ci
npm --prefix client ci
dotnet restore app.sln
dotnet tool restore
npm run db:upThe database may take a little time on first startup. Use npm run db:logs to wait for SQL Server to report that it is ready for client connections, then press Ctrl+C to stop following logs. Start the application:
npm startThis runs dotnet watch on port 5165, waits for /health to succeed, then starts Vite on port 5173 and opens the browser. Ctrl+C stops the app processes; npm run db:down separately stops SQL Server and preserves its data.
For editor debugging, use the same dependency and database setup, then choose one launcher:
- VS Code: install the recommended C# extensions, select Full Stack: VS Code in Run and Debug, and press F5. Select Backend: ASP.NET Core + Swagger for backend-only debugging.
- Visual Studio on Windows: use Visual Studio 2026 version 18.0 or later, open
app.sln, setserveras the startup project, and press F5. Itshttpprofile usesSpaProxyto launch Vite and redirect the browser.
Stop a running npm start session before launching the same app from an editor so the processes can use their configured ports.
With Docker and the VS Code Dev Containers extension installed, configure server/.env as above, then run Dev Containers: Reopen in Container. The container provides .NET 10, Node 22, and SQL Server; its setup script installs dependencies, and its start hook runs npm start automatically.
The DevContainer overrides DB_CONNECTION to reach SQL Server at sql:1433. Use its terminal for development commands. If the application needs restarting after changing configuration, stop the existing app processes before running npm start again.
| Service | Host or DevContainer development | Docker sandbox defaults |
|---|---|---|
| Frontend | localhost:5173 | localhost:5280 |
| Backend health | localhost:5165/health | localhost:5280/health |
| Swagger | localhost:5165/swagger | localhost:5280/swagger |
| Mailpit inbox | Requires separate SMTP configuration | localhost:8025 |
Use the frontend origin in the browser; Vite proxies backend API and authentication requests during development. Swagger is enabled only in Development.
The application is a React single-page app with an ASP.NET Core host and a shared backend library. The .NET solution contains server, server.core, and server.tests. The frontend is built by Vite and included when the server is published.
| Component | Responsibility |
|---|---|
client/ |
React 19 and TypeScript, built with Vite 7. TanStack Router provides file-based routes; Query handles server state; Form and Table support forms and data tables. Styling uses Tailwind CSS 4, DaisyUI 5, and UC Davis Gunrock. |
server/ |
ASP.NET Core 10 host for /api controllers, authentication, health checks, and published frontend assets. It owns application services and the optional notification examples, including their request DTOs and Razor templates. |
server.core/ |
Shared Razor class library containing domain models, the EF Core 10 SQL Server context, migrations and database initialization, plus reusable email composition types, Razor/MJML rendering, and MailKit SMTP delivery. |
Authentication uses Microsoft Identity Web with Entra ID/OIDC and a server-issued session cookie. Protected frontend routes load /api/user/me; the backend enforces authentication and role checks. Fictional local users are available only when explicitly enabled in Development. See Auth Configuration.
SQL Server stores application data locally; cloud deployments use Azure SQL. The current schema contains sample weather data, and application roles are still supplied by the template's placeholder UserService. Catalog-specific data and authorization remain application work. Registered options are validated before startup migrations, and sample seeding is opt-in.
The request and build flows depend on how the app is run:
- Local development: the browser uses Vite on
:5173, which proxies API, authentication, and health requests to ASP.NET Core on:5165.npm startcoordinates Vite anddotnet watch; Visual Studio profiles useSpaProxyto launch Vite and redirect the browser to it. - Published application:
dotnet publishruns the frontend build and copiesclient/distinto the publishedwwwroot. ASP.NET Core serves static assets, the SPA fallback, and backend endpoints from one origin. Vite and Node.js are build-time dependencies in this mode. - Docker sandbox: the published application runs in
Developmentwith local cookie authentication, SQL Server, and a Mailpit SMTP inbox. It serves the built frontend through the app port (default:5280); source changes require rebuilding the image.
OpenTelemetry provides server logs, traces, and metrics with OTLP exporters. The Azure deployment scaffold targets Linux App Service and Azure SQL and defines Application Insights and Log Analytics resources. GitHub Actions validates PRs and builds/publishes the application for deployment.
See Development Architecture for request-flow diagrams and hosting details, and optional email notifications for the reusable email boundary and sample composition flow.
The backend loads appsettings.json, environment-specific JSON, server/.env, and an optional server/.env.<environment>. OS environment variables take precedence over these files. See the example configuration for supported local settings.
The backend requires SQL Server. Set DB_CONNECTION in server/.env or the environment to override ConnectionStrings:DefaultConnection from JSON configuration.
- Host development uses the SQL container on
localhost:14333with databaseAppDb. - The DevContainer sets
DB_CONNECTIONto usesql:1433with databaseAppDb. - The standalone sandbox uses its own SQL container and
SandboxDb.
Startup validates registered options, then applies EF Core migrations before serving requests. The database must be available and the configured account must have permission to apply those migrations. Sample weather data is inserted only when DevelopmentData__SeedOnStartup=true and the weather table is empty. The sandbox enables seeding; ordinary development opts in through .env.
Use npm run db:up, npm run db:logs, and npm run db:down to manage the regular development database. These commands use .devcontainer/docker-compose.yml; use the separate sandbox commands for its database.
The default mode uses OIDC with Microsoft Entra ID and a server-issued authentication cookie. Set Auth__ClientId to your own registration's client ID; startup rejects the template placeholder. Follow Microsoft Entra setup for registration, redirect URIs, and app-specific settings.
Auth__UseLocal=true enables fictional local users and bypasses Entra configuration. It defaults to false, and startup rejects it outside Development. The Docker sandbox enables it automatically.
The backend supplies application roles through UserService; its current role assignment is template behavior that needs replacing for catalog-specific authorization. To include the ucdPersonIAMID claim displayed on the home page, see the team's Authentication guide.
client/index.html contains the GA4 bootstrap, and AnalyticsListener sends page views on route changes. Replace the placeholder G-XXXXXXXXXX in both the script URL and gtag('config', ...) before using analytics for this application.
/health checks database connectivity through AppDbContext. Development launchers and Azure deployment checks use it for readiness. It does not verify Entra sign-in or SMTP delivery; test those flows separately.
Vite serves the browser on :5173 and proxies /api, /login, /logout, /signin-oidc, and /health to ASP.NET Core on :5165. Keep frontend requests relative to the current origin, such as /api/weatherforecast.
npm start coordinates both processes. To run them in separate terminals, use npm run start:server and npm run start:client. The backend command selects http-cli, which leaves frontend startup to npm or the editor. See Development Architecture for diagrams and hosting details.
Add API endpoints in server/Controllers/, application services in server/Services/, and shared domain/persistence code in server.core/. dotnet watch applies supported C# edits with hot reload and requests a restart when needed. Restart after changing startup configuration.
The EF Core context and migrations live in server.core/; server is the startup project. After changing the model, create a migration with a descriptive name:
dotnet ef migrations add DescribeSchemaChange --project server.core --startup-project serverReview the generated migration before running the app, because startup applies pending migrations. Backend tests live in tests/server.tests/.
Create route files in client/src/routes/, with protected pages under (authenticated)/. Vite's TanStack Router plugin generates client/src/routeTree.gen.ts; edit route files rather than the generated tree. Use the shared QueryClient and query definitions in client/src/queries/ for server state, and the helpers in client/src/lib/api.ts for API calls.
Vite provides React hot reload. Shared components live in client/src/shared/; styles use Tailwind, DaisyUI, and UC Davis Gunrock. npm --prefix client run build checks TypeScript and produces client/dist; it builds only the frontend.
.vscode/launch.json defines Full Stack: VS Code and Backend: ASP.NET Core + Swagger. Both use the http-cli launch profile. Full Stack starts Vite after backend health succeeds; the backend-only configuration opens Swagger. See host development for prerequisites.
- Protected routes load the current user through
/api/user/me. - The API helper redirects a
401response to/login?returnUrl=.... - The backend completes Entra sign-in or the enabled local-user flow and issues a cookie.
- Same-origin API requests include that cookie; the backend enforces authentication and role checks. A
403remains an authorization error.
After sign-in, returnUrl accepts local paths such as /fetch?sort=date. Missing or external destinations fall back to /.
Leave Smtp:Host empty to run without SMTP. The sandbox configures Mailpit automatically; its Notification page sends examples to the local inbox. Sample notification endpoints are available only in Development and test.
Application code parses recipient strings with EmailRecipients.Parse(to, cc, bcc) before rendering, then constructs messages with EmailMessage.Create(recipients, subject, textBody, htmlBody). These factories reject invalid input and produce immutable values. Table composition also snapshots rows before awaiting the renderer.
Notification:BaseUrl is optional. When set, it must be an absolute HTTP(S) URL without embedded credentials; invalid values fail startup validation. A blank value omits the email button. See optional email notifications for SMTP configuration, composition examples, and removal steps.
Build the complete application from the repository root:
dotnet publish server/server.csproj --configuration Release --output publish/webThe publish target installs frontend dependencies with npm ci, runs the Vite build, and includes its output in publish/web/wwwroot. Node.js is required on the build machine; the published application runs on ASP.NET Core with its database and authentication configuration. npm --prefix client run preview previews frontend assets only and is not the complete application host.
After restoring dependencies, run these checks from the repository root before opening a PR:
dotnet test app.sln --configuration Release
npm --prefix client test -- --run
npm --prefix client run lint
npm --prefix client run build
npm run deployment-settings:checkThe PR validation workflow builds and tests both projects, checks deployment settings, and compiles the Bicep templates for PRs targeting main. Frontend ESLint is currently a local check, not a workflow step. If you edit infrastructure, reproduce the Bicep checks with Azure CLI and Bicep installed:
az bicep build --file infrastructure/azure/main.bicep --stdout > /dev/null
az bicep build --file infrastructure/azure/github-oidc.bicep --stdout > /dev/nullRun npm --prefix client test -- --run once, or npm --prefix client run test:watch during development. Vitest uses jsdom, Testing Library, and MSW; the backend does not need to be running. See the client testing guide for fixtures and test conventions.
Run dotnet test app.sln --configuration Release, or target tests/server.tests/server.tests.csproj directly. The xUnit suite covers controllers, authentication helpers, notification parsing and composition, and Razor/MJML rendering. It requires no live Entra or SMTP service.
Database fixtures use EF Core's in-memory provider, so SQL Server is not required. These tests do not verify SQL Server migrations or relational constraints.
Use the Docker sandbox to exercise real HTTP middleware, SQL Server migrations, and SMTP delivery. Check that Sample User can access weather data, Basic User receives 403, and notification examples arrive in Mailpit. See the sandbox guide for investigation and cleanup commands. These manual checks complement the automated suites.
Dependabot groups weekly npm, NuGet, and GitHub Actions updates, including major versions. Verified dependency-only updates can receive automated approval and squash auto-merge after the required validation, security, CodeRabbit, and Codacy checks pass. Code-owner approval is not required. The security workflow runs dependency audits, actionlint, Zizmor, and Gitleaks; CodeQL analyzes C# and JavaScript/TypeScript separately. See dependency automation for local commands, policy safeguards, scanner scope, and rollout requirements.
GitHub Actions is the primary deployment path. The checked-in workflows have these responsibilities:
| Workflow | Trigger and behavior |
|---|---|
| CI/CD | Validates PRs targeting main; pushes to main deploy to test; manual runs select test or prod. |
| Configure Azure | Manually provisions or updates infrastructure and application settings for test or prod. |
| Deploy Azure App Service | Reusable build, test, publish, and package deployment workflow called by CI/CD. It deploys to an existing configured App Service and checks health. |
Before deploying, configure GitHub Environments, the Azure OIDC identity, application names, Entra settings, and SQL connectivity. A push to main attempts a test deployment, so these prerequisites must be in place for that job to succeed. Production deployment is manual. Start with the Azure deployment guide and its links to bootstrap instructions.
Edit app-specific settings in deployment-settings.json, then run:
npm run deployment-settings:sync
npm run deployment-settings:checkReview the generated workflow and deploy-script changes together with the settings file. The template defaults catalog and generated regions have separate ownership; do not hand-edit generated regions. Infrastructure/settings configuration is separate from package deployment. The Azure guide also covers local deployment scripts, production SQL networking, and first-deploy requirements.
The repository has separate root and client npm manifests and lockfiles. Inspect both:
npm outdated
npm --prefix client outdatedUse npm update and npm --prefix client update for updates within the declared ranges. For a deliberate version change, use npm install <package>@<version> in the owning directory (or npm --prefix client install <package>@<version>). Review changes to each manifest and lockfile together, then run the checks above.
Use the .NET 10 SDK to inspect outdated NuGet packages:
dotnet package list --project app.sln --outdatedUpdate package references in the owning .csproj, restore, and rerun the checks. Keep EF Core packages and the local dotnet-ef tool aligned; its version is recorded in .config/dotnet-tools.json. Use dotnet tool update dotnet-ef --local --version <matching-version> when changing that pin.
The SDK command needs no additional package-update tool. When changing the SDK/runtime baseline, review global.json, project targets, Dockerfiles, and workflow setup together.
Key source directories and tooling:
.
├── client/ # React/TypeScript frontend
│ ├── src/
│ │ ├── routes/ # TanStack Router routes and auth layout
│ │ ├── queries/ # TanStack Query options and hooks
│ │ ├── examples/ # Optional notification UI
│ │ ├── lib/ # API and CSV helpers
│ │ ├── shared/ # Auth context, forms, tables, and analytics
│ │ └── test/ # Vitest, Testing Library, and MSW fixtures
│ ├── package.json
│ └── vite.config.ts
├── server/ # ASP.NET Core host
│ ├── Controllers/ # Account, current-user, and weather endpoints
│ ├── Examples/Notifications/ # Sample endpoints, DTOs, composition, and views
│ ├── Helpers/ # Authentication, telemetry, and startup logging
│ ├── Services/ # Application services, including role lookup
│ ├── Views/Account/ # Development-only local sign-in page
│ ├── Properties/ # Launch profiles
│ ├── Program.cs # DI, configuration, database startup, middleware
│ └── server.csproj # Host dependencies and frontend publish target
├── server.core/ # Shared backend/Razor class library
│ ├── Data/ # EF Core context and database initialization
│ ├── Domain/ # Data/domain models
│ ├── Migrations/ # SQL Server schema migrations
│ ├── Notification/ # Immutable email values, rendering, and SMTP
│ ├── Views/Shared/ # Shared MJML layout and button templates
│ └── server.core.csproj
├── tests/server.tests/ # xUnit backend tests and EF InMemory fixtures
├── infrastructure/azure/ # Bicep, deployment settings, and deploy scripts
├── scripts/ # Deployment-settings synchronization/checks
├── docs/ # Architecture and sandbox guides
├── .devcontainer/ # DevContainer and standalone Docker sandbox
├── .github/workflows/ # PR validation, Azure setup, and deployment
├── global.json # .NET SDK selection
├── package.json # Root development and deployment-tool commands
└── app.sln # Server, shared library, and test projects
Commands below run from the repository root; --prefix client selects the frontend package.
| Command | Purpose |
|---|---|
npm start |
Run the backend watcher, wait for health, and start Vite with a browser. |
npm run start:server |
Run only the backend watcher with the http-cli profile. |
npm run start:client |
Run Vite and open the browser. |
npm run db:up |
Start the regular development SQL Server container. |
npm run db:logs |
Follow SQL Server logs. |
npm run db:down |
Stop SQL Server and retain its database volume. |
npm run deployment-settings:sync |
Regenerate deployment settings in owned workflow/script regions. |
npm run deployment-settings:check |
Type-check the settings tool and check generated output for drift. |
start:client:when-server-ready and start:client:debug are launch helpers used by npm orchestration and VS Code. deployment-settings:typecheck runs the settings tool's TypeScript check alone.
| Command | Purpose |
|---|---|
npm --prefix client run dev |
Run Vite without opening a browser. |
npm --prefix client run dev:open |
Run Vite and open the browser. |
npm --prefix client run build |
Type-check and build frontend assets. |
npm --prefix client run lint |
Run ESLint. |
npm --prefix client test -- --run |
Run the frontend test suite once. |
npm --prefix client run test:watch |
Run frontend tests in watch mode. |
npm --prefix client run preview |
Preview the existing frontend build. |
From the repository root, use dotnet build app.sln to build all .NET projects and dotnet test app.sln to run backend tests. Use npm run start:server for backend development and the publish command for a deployable application. If working inside server/, dotnet run --launch-profile http-cli starts only the backend; the test project is outside that directory.
Updated to web-app-template 7510421 (September 24, 2026). This update adopts .NET 10 and Node.js 22.18+ and removes the old JavaScript project (client.esproj). The backend now builds and publishes the frontend directly. Generated obj files from upstream are excluded.
Published applications require .NET and ASP.NET Core 10.0.12 or later within the .NET 10 line. The server sets a minimum runtime version to preserve the template's XML security patch baseline when the SDK uses the shared-framework assembly instead of copying the NuGet assembly. Keep deployment hosts on current .NET 10 patches.
Existing development setups must configure their own Entra client ID or explicitly enable development-only local sign-in in server/.env; see Auth Configuration. SMTP, telemetry, and cloud deployment settings remain optional and require app-specific configuration.