Running it locally
This page is for a developer setting up SBDMS on their own machine for the first time. Follow it top to bottom once; after that you only need the three “run” commands.
Prerequisites
Section titled “Prerequisites”| Tool | Version | Check |
|---|---|---|
| .NET SDK | 10.0 or newer | dotnet --version |
| Node.js | 24 (the docs site image uses node:24; the app frontend builds on any current LTS) |
node --version |
| Git | any | git --version |
| sqlite3 CLI | any, optional | sqlite3 --version |
Clone the repository (govtprojs/swarnabindu) and check out staging, which is what production runs.
Backend
Section titled “Backend”The backend reads backend/Sbpdms.Backend/appsettings.json. Two settings matter locally:
ConnectionStrings:DefaultConnectionpoints at a database path that is probably not valid on your machine. Do not edit the file; override it with theSBPDMS_DB_PATHenvironment variable, which takes a plain file path and wins over the setting.Jwt:Keymust be at least 32 characters. The checked-in development value works locally. The backend refuses to start when the key is missing. Never reuse the development key anywhere public.
Start it:
cd backend/Sbpdms.BackendSBPDMS_DB_PATH=$PWD/dev.db ASPNETCORE_ENVIRONMENT=Development dotnet run --launch-profile httpWhat happens on first start, in order:
- The SQLite file is created if it does not exist.
SchemaManagercreates every table and runs the additive migrations.- The seeder creates one SuperAdmin user with username
admin. Its password comes fromAdmin:InitialPasswordinappsettings.json(or theADMIN_INITIAL_PASSWORDenvironment variable). Look it up there; it is not printed in this guide. - Lookup data (regions, districts, local levels) is seeded when
SeedLookupData:Enabledis true, which it is by default. - The log shows the absolute path of the database it bound to. Check it once; a wrong path is the most common local mistake.
The API is now at http://localhost:5090. Useful checks:
curl http://localhost:5090/healthzandcurl http://localhost:5090/readyz- Swagger UI at
http://localhost:5090/swagger(Development only)
To start over, stop the backend and delete the .db file; the next start recreates the schema and the admin.
Frontend
Section titled “Frontend”cd frontendnpm ciPUBLIC_API_URL=http://localhost:5090/api npm run devOpen http://localhost:4321 and log in as admin. The dev server listens on all interfaces, so a phone on the same Wi-Fi can open it using your computer’s IP.
PUBLIC_API_URL is read when the frontend starts or builds, not per request. Forget it and the default points at https://localhost:5091/api: pages render, data calls fail. There are no frontend unit tests; npm run build is the gate, so run it before you push.
The backend has an xUnit integration test project. Each test class starts the real application against a fresh SQLite file in your temp folder, with a fixed test JWT key and a known admin password, so tests are independent and can run in parallel.
cd backenddotnet test Sbpdms.IntegrationTests/Sbpdms.IntegrationTests.csproj# one class:dotnet test Sbpdms.IntegrationTests/Sbpdms.IntegrationTests.csproj --filter RoleManagementTestsPitfalls that cost time before: read JSON into typed DTOs, not dynamic; camp-day create returns its id in the Location header; a dose-prep create needs a preparer assigned to that site; registration needs client-generated UPCs.
The docs site
Section titled “The docs site”cd docs-sitenpm installnpm run dev # http://localhost:4321 (stop the app frontend first, or pass --port)npm run check:words # the compliance gate; the Docker build runs it toonpm run buildPages are plain Markdown under src/content/docs/. Read docs-site/WRITING.md before writing; it holds the style rules and the list of words that must never appear.
Coding conventions you will see
Section titled “Coding conventions you will see”Follow the pattern of the file you are in. These are the ones that matter:
- Comments explain why, not what. Many methods carry a paragraph naming the incident that caused a rule (a date, a site, a count). Keep that habit; the next person needs the reason, not a restatement of the code.
- All SQL goes through
IDbService(GetAsync,GetAllAsync,EditData) with parameters. The only values ever inlined into SQL are integer ids that the server itself read from the database. - Every endpoint that touches a child, guardian, camp day, batch or staff row calls
IPermissionServicebefore reading or writing, and returnsNotFound()on failure. Never write your own scope check. Call_perm.LogDenial(...)so the refusal is in the logs. - Every write calls
_audit.LogAsync(table, id, action, User, before, after), with the row read before and after the change. - Soft delete only. Set
IsDeleted = 1; filter it in every read. - Schema changes are additive. Add a
CREATE TABLE IF NOT EXISTSor anALTER TABLE ... ADD COLUMNguarded byPRAGMA table_infoinSchemaManager, then any index. Never drop or rename. - Errors are
BadRequest(new { Error = "..." })with a sentence a health worker can act on. - Bilingual strings. Where a page supports both languages (the registration form, the dose wizard, the reports pages), each label appears twice: once inside
x-show="$store.i18n.showEn"and once insidex-show="$store.i18n.showNe". New user-facing pages should do the same. Do not call$store.i18n.t(...); that helper exists only on the QC branch. - Authenticated calls run in the browser. Use
ApiServicefromsrc/services/api.jsinside a<script>or Alpine component, never in Astro frontmatter. Mark polling calls{ silent: true }or they trigger the global “Working” overlay. - Do not mix
??and||without parentheses; it breaks the build.
Checklist: adding an endpoint
Section titled “Checklist: adding an endpoint”- Put it in the controller for its area under
Features/, or make a new folder there. Set[Authorize]on the class and[Authorize(Roles = ...)]on write actions. - Validate the input and return
400with{ Error }for anything wrong. - Call the right
IPermissionServicecheck; returnNotFound()when it fails. For list endpoints, append the...ScopeWhereAsyncfragment to yourWHERE. - Write SQL with parameters through
IDbService. FilterIsDeleted = 0. - For writes: read the row before, write, read after, call
_audit.LogAsync. - If you touched a lookup table that the public
/api/lookupsserves, callILookupCache.Invalidate(...). - Add an integration test in
Sbpdms.IntegrationTeststhat covers the happy path and one scoped-user denial. - Run
dotnet test, then try it from the frontend against your local backend.
Checklist: adding a page
Section titled “Checklist: adding a page”- Create the
.astrofile undersrc/pages/in the folder that matches the URL. UseLayout.astro. - Load data in a client-side script through
ApiService; show theSkeletoncomponent or an empty state until it arrives. - Labels in both languages with the
showEnandshowNeflags; dates throughutils/dateFormat.jsso AD and BS both appear. - Hide the page or its buttons from roles that cannot use them, but remember the server is the real gate.
- Show API
Errormessages withwindow.toast.error. Usewindow.confirmDialogbefore anything destructive. - Check the page at 414 px wide and with keyboard only.
npm run buildmust pass. Check the page on QC as well as locally; the two differ in the i18n store.- If the page changes what users see, update this guide.