Mahesh Chandra Gupta Official Portal
A polished full-stack public-service portal for Budaun, Uttar Pradesh built with plain HTML, CSS, vanilla JavaScript, Express.js, and SQLite through sql.js.
This project intentionally does not use React, Vite, Next.js, Tailwind, or any frontend framework.
What It Includes
- Protected civic portal pages: Home, About Budaun, Projects, Sevaks, Gallery, and Contact / Public Concern.
- Public entry pages: Login and Register.
- JWT login with bcrypt password hashing.
- Role-aware access with
userandadminaccounts. - Remember-me behavior using
localStorage; session-only login usingsessionStorage. - Frontend auth guard with logout,
/api/auth/metoken verification, admin badges, and admin-only controls. - REST API routes for auth, projects, sevaks, gallery, seva works, concerns, mandals, and villages.
- SQLite database file stored at
server/data/portal.sqlite. - Seed data for admin/user accounts, mandals, villages, projects, sevaks, gallery items, seva works, and public concerns.
- Wasmer Edge config with
wasmer.toml,app.yaml, health check, and/datavolume path.
Local Setup
npm install
cp .env.example .env
npm run seed
npm start
Open:
http://localhost:3000
First visit behavior:
- If you are not logged in,
/redirects to/login.html. - Register creates an account and redirects to login.
- Login redirects to
/index.html. - Remember me keeps you logged in until token expiry or logout.
- Logout clears both browser storage locations and returns to login.
Local Accounts
Admin:
admin@mcgportal.in
Admin@12345
User:
user@mcgportal.in
User@12345
Admin users can add, edit, and delete projects, gallery images, sevaks, and seva work cards. Admin users can also view submitted public concerns and update their status.
Normal users can view public portal content and submit concerns. They cannot see plus buttons, edit/delete controls, dashboard links, or admin concern tools.
Environment Variables
Copy .env.example to .env:
PORT=3000
NODE_ENV=development
DATABASE_URL=./server/data/portal.sqlite
JWT_SECRET=replace-with-a-long-random-secret
CORS_ORIGIN=*
ADMIN_SEED_PASSWORD=Admin@12345
USER_SEED_PASSWORD=User@12345
What each value means:
PORT: Local Express server port.NODE_ENV: Usedevelopmentlocally andproductionon Wasmer.DATABASE_URL: Path to the local SQLite database file.JWT_SECRET: Secret used to sign login tokens.CORS_ORIGIN: Allowed frontend origin. Use*locally or a specific domain in production.ADMIN_SEED_PASSWORD: Password used when creating the seeded admin account.USER_SEED_PASSWORD: Password used when creating the seeded normal user account.
SQLite does not require API keys. There is no database username, password, host, or cloud key for this local SQLite setup. The only database value needed is:
DATABASE_URL=./server/data/portal.sqlite
That path creates or reads a local SQLite database file.
Creating a JWT Secret
For development, use any long random string. This command creates one:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Copy the output into .env as JWT_SECRET.
Do not commit .env.
Useful Scripts
npm start
npm run dev
npm run seed
npm run seed:reset
npm run seed resets and recreates starter data. To fully reset local data manually, delete:
server/data/portal.sqlite
Then run:
npm run seed
API Routes
POST /api/auth/register
POST /api/auth/login
GET /api/auth/me
GET /api/projects logged-in users
GET /api/projects/:id logged-in users
POST /api/projects admin only
PUT /api/projects/:id admin only
DELETE /api/projects/:id admin only
GET /api/sevaks logged-in users
GET /api/sevaks/:id logged-in users
POST /api/sevaks admin only
PUT /api/sevaks/:id admin only
DELETE /api/sevaks/:id admin only
GET /api/gallery logged-in users
GET /api/gallery/:id logged-in users
POST /api/gallery admin only
PUT /api/gallery/:id admin only
DELETE /api/gallery/:id admin only
GET /api/works logged-in users
GET /api/works/:id logged-in users
POST /api/works admin only
PUT /api/works/:id admin only
DELETE /api/works/:id admin only
POST /api/concerns logged-in users
GET /api/concerns/mine current user's concern notifications
GET /api/concerns admin only
GET /api/concerns/resolved admin only
GET /api/concerns/:id admin only
PATCH /api/concerns/:id/status admin only
GET /api/concerns/stats logged-in users
GET /api/mandals
GET /api/villages
GET /api/health
Protected routes require:
Authorization: Bearer <jwt>
Admin-only routes also require the token user to have role: "admin".
Project Structure
public/
index.html
about.html
projects.html
sevaks.html
gallery.html
login.html
register.html
contact.html
css/
js/
assets/
server/
server.js
config/
routes/
controllers/
models/
middleware/
seed/
.env.example
package.json
wasmer.toml
app.yaml
README.md
Wasmer Edge Deployment
Install the Wasmer CLI and login:
wasmer login
Local preparation:
npm install
npm run seed
npm start
Wasmer test and deploy:
wasmer deploy --build-remote --non-interactive
wasmer app info aadityarajvarshney4/mcg
wasmer app logs aadityarajvarshney4/mcg
The Edge app is deployed as aadityarajvarshney4/mcg. Wasmer assigns its
wasmer.app hostname; run wasmer app info after deployment to see the active
URL. Store JWT_SECRET in Wasmer's app secrets, never in wasmer.toml or
app.yaml.
The app mounts the Wasmer volume portal-data at /data and stores SQLite at
/data/portal.sqlite. On the first boot of an empty volume, the backend creates
the complete seed dataset. Later deployments preserve existing records and only
restore a missing seeded admin account; they do not reset the database.
Deployment reminder:
- Remove
.git. - Remove
node_modules. - Remove
.env. - Do not upload local secrets.
- Deploy using Wasmer CLI only after local testing.
The Wasmer config uses:
DATABASE_URL=/data/portal.sqlite
The /data path is mounted as a Wasmer volume so the SQLite file can persist. Wasmer documents app environment variables, health checks, and volumes in its official Edge configuration and persistent storage docs.
Packaging Hygiene
Do not include these in a deployment ZIP or commit:
node_modules/
.git/
.env
server/data/*.sqlite
server/data/*.sqlite-*
Use .env.example as the public template and create .env locally.