All projects
01Productv0.1.0 live

Sewa Track

Ward recommendation letters for all of Nepal, and a map of where they run late.

A full-stack tracker for Nepal's ward recommendation letters (sifaris): citizens apply and upload documents online, ward officers work through a strict, audited workflow, and a national map shows which districts are overdue against the citizen charter.

Role
Full-stack engineering
Year
2026
Status
Shipped
Sewa Track interface

Overview

Sewa Track is a demo / portfolio project, not affiliated with any government body; all of its data is fake. Online sifaris already exists in parts of Nepal, but each palika uses its own system and the data isn't connected. Sewa Track explores an open, national version: one data model for all 753 local levels, every request measured against the promised service time, and a national service-delay map built from that. A citizen picks a service, applies and attaches documents, and follows it with a tracking code. The ward officer sees one clear next step, asks for specific documents, accepts or rejects each file, and moves the request through a workflow where every change is audited. Palika and system admins see reports and the map.

Problem

A ward recommendation letter is one of the most common things a Nepali citizen asks a local government for, and the citizen charter promises a time for each one. Where it is online at all, it lives in separate systems per palika, so nobody can see where requests are late across the country, and citizens still visit the office to find out where their own request is.

Approach

One data model for all 753 local levels, so every request is comparable. Measure each request against its charter time and make 'overdue' the headline number. Keep the workflow strict and auditable, because a real office would need to defend every decision. Build it like production software: tests against a real database, measured performance, documented decisions, and a live deployment, while keeping it clearly a demo with fake data only.

Architecture

A pnpm monorepo in strict TypeScript. The API is Express with hand-written, parameterized SQL on PostgreSQL and its own migration runner, layered as routes → controllers → services → repositories, with the business rules in a pure domain module that has no database or HTTP code. The web app is Next.js with TanStack Query. Zod schemas live in a shared package, so the browser and the API validate the same input with the same rules. Deployed as web on Vercel, API on Render and Postgres on Neon; the API only accepts calls that come through the website.

Key interactions

  • One data model for 7 provinces, 77 districts, 753 local levels and 6,743 wards
  • Status rules in one pure, unit-tested function; anything else is refused with 422
  • Every status change, fee payment and document review is audited in the same database transaction
  • Optimistic locking: two officers acting at the same moment get one success and one 409, never a silent overwrite
  • On 100k fake requests, indexes chosen with EXPLAIN ANALYZE took the officer queue from 7.9 ms to 0.5 ms
  • Uploaded files checked from their first bytes, so a renamed .exe is refused; downloads sent sandboxed
  • 370 tests: unit tests for the rules, and API tests that run against a real Postgres database; layout shift measured under 0.01 on every page

Technical decisions

The workflow lives in one pure function

Which status can follow which, who may make the move, and what has to be true first (fee paid, field visit done, every requested document uploaded) is one table in one module with no database code. The API, the buttons on the staff screen and the tests all ask the same function, so the UI can never offer a move the server would refuse.

Audit in the same transaction, enforced by the database

A status change and its history row are written in one transaction, so either both are saved or neither is. The history table also has a trigger that rejects any UPDATE or DELETE, so 'append-only' holds even for someone connected to the database directly.

Optimistic locking instead of row locks

Each change sends the version the officer was looking at, and the update only matches if that version is still current. Postgres guarantees exactly one winner when two officers click at the same moment; the other gets a clear 'someone else saved first' and fresh data.

Documents stored in Postgres, treated as hostile

For small demo files, keeping them in the database meant no extra storage service to run or secure, and the upload, its audit row and the document limits share one transaction. The file type is decided from its bytes, not its name, and downloads are sent with nosniff and a sandbox policy.

Only the website can call the API

The web app adds a shared secret to every API request, and the API refuses anything without it. That also let the API trust the visitor's real IP from the web app, which made the login and tracking rate limits work per person instead of per proxy.

What I took from it

  • The hard part of a public-service system is not the forms; it's being able to show exactly who changed what, and when.
  • Measuring before indexing mattered: the national map reads every row, so no index helps it, and the performance report says so.
  • A test that passed for the wrong reason taught me to make concurrency tests prove the race actually happened.