Property Planets is a web-based property database management system. It replaces spreadsheets (e.g. Excel) with a secure, role-based application where staff can register, admins approve users, and everyone can search, add, edit, and manage property records at scale.
- Purpose: Manage property records (owner, contacts, IC, etc.) with search, filters, CSV import/export, and full audit (who created/updated what).
- Users: Two roles — Admin (full access, user management, approvals) and Staff (view/add properties, no delete or user management). New staff register and wait for admin approval (Pending list).
- Scale: The app supports large datasets: web CSV import up to 50,000 rows per file, and a separate bulk-import script for millions of rows directly into the database.
| Layer | Technology |
|---|---|
| Frontend | HTML, CSS, JavaScript (vanilla) — no framework; static files served by the same Worker. |
| Backend / API | TypeScript — runs on Cloudflare Workers (serverless). |
| API framework | Hono — lightweight web framework for routes, middleware, CORS, and security headers. |
| Database | Neon — serverless PostgreSQL. All application data (users, properties) is stored in Neon. |
| Database access from Workers | Cloudflare Hyperdrive — connection pooler that connects Workers to your Neon database efficiently and securely. |
| Hosting | Cloudflare — one Worker serves both the REST API and the static frontend (HTML/CSS/JS). |
- Cloudflare Workers: Global edge runtime; low latency, no servers to manage, pay-per-request.
- Neon PostgreSQL: Familiar SQL, scalable, branching and backups; ideal for structured property and user data.
- Hyperdrive: Connects Workers to Neon with connection pooling and caching so each request gets a fast, reliable database connection without opening a new one every time.
┌─────────────┐ HTTPS ┌─────────────────────────────────────┐ Hyperdrive ┌──────────────────┐
│ Browser │ ──────────────► │ Cloudflare Worker (Property Planets) │ ─────────────────► │ Neon PostgreSQL │
│ (HTML/JS) │ │ • Hono API (/api/auth, /api/users, │ (pooled │ • users │
│ │ ◄────────────── │ /api/properties) │ connection) │ • properties │
└─────────────┘ JSON / HTML │ • Static assets (/, /login.html, …) │ └──────────────────┘
└─────────────────────────────────────┘
- Browser loads the app from the Worker (e.g.
index.html,login.html,properties.html). - Frontend calls REST API on the same origin (
/api/auth/*,/api/users/*,/api/properties/*) with cookies for auth. - Worker (Hono) validates the JWT cookie, then uses Hyperdrive to get a connection string and runs queries with the
pg(node-postgres) client against Neon. - Neon holds all persistent data; Hyperdrive handles connection pooling and optional caching so the Worker does not open a new TCP connection to Neon on every request.
Property-Planets/
├── public/ # Static frontend (served by Worker)
│ ├── index.html # Landing / redirect
│ ├── login.html # Login page
│ ├── register.html # Staff registration
│ ├── setup.html # First-time admin creation (no users yet)
│ ├── dashboard.html # Dashboard after login
│ ├── properties.html # Property list, search, filters, import/export
│ ├── property-add.html # Add new property
│ ├── property-edit.html # View / edit property
│ ├── pending.html # Admin: approve/reject pending users
│ ├── users.html # Admin: user management (edit/delete)
│ ├── css/
│ │ └── styles.css # Global styles (light/dark theme)
│ ├── js/
│ │ ├── api.js # API client (fetch, credentials)
│ │ └── auth.js # Auth state, login/logout, theme
│ └── images/
│ └── logo.png
├── worker/ # Cloudflare Worker (TypeScript)
│ ├── index.ts # Hono app, routes, static asset fallback
│ ├── types.ts # Env, User, Property, JwtPayload
│ ├── db.ts # withClient() — Hyperdrive → Neon via pg
│ ├── middleware.ts # JWT auth, requireAdmin, getJwtSecret
│ ├── lib/
│ │ └── auth.ts # Password hashing, JWT sign/verify
│ ├── auth.ts # /api/auth (login, register, setup, logout, theme)
│ ├── users.ts # /api/users (list, me, PATCH, DELETE)
│ └── properties.ts # /api/properties (CRUD, search, import, export)
├── scripts/
│ ├── bulk-import-properties.js # CLI: import huge CSVs into Neon (direct pg)
│ └── copy-logo.js # Copy logo into public before dev/deploy
├── migrations/ # (Optional) D1 migrations; app uses Neon + Hyperdrive
├── NEON_FULL_SCHEMA.sql # Full Neon schema — run once in Neon SQL Editor
├── wrangler.toml # Worker config: name, assets, Hyperdrive binding
├── package.json # Scripts: dev, deploy, db:* (D1), dependencies
├── DEPLOY.md # Step-by-step deployment (Cloudflare + Neon)
└── README.md # This file
- Frontend: Static HTML/CSS/JS in
public/; no build step. The Worker serves these files and the app uses the same origin for API calls. - Backend: All API and auth logic live in
worker/(TypeScript). Database access goes throughworker/db.ts, which uses the Hyperdrive binding to get a connection string and then uses Neon (PostgreSQL) for every query.
The application uses Neon as its only primary database for users and properties.
The file NEON_FULL_SCHEMA.sql defines the full schema. Run it once in the Neon Console → SQL Editor (or via psql) to create:
users— id, username, first_name, last_name, phone, email, password (hashed), role (Admin/Staff), status (Pending/Active/Rejected), theme_preference, created_at, approved_by, approved_at.properties— id, property_name, property_owner_name, phone_01, phone_02, ic_number, created_by, created_at, updated_by, updated_at.
Indexes are included for search and lookups. No application data is stored in Cloudflare D1; D1 is not used for the main app flow.
Workers cannot hold long-lived TCP connections. Cloudflare Hyperdrive sits between the Worker and Neon:
- In the Cloudflare dashboard, you create a Hyperdrive config and point it to your Neon database (connection string from Neon Console).
- In
wrangler.tomlyou add a[[hyperdrive]]binding (e.g.binding = "HYPERDRIVE") and attach that Hyperdrive config by ID. - At runtime, the Worker reads
env.HYPERDRIVE.connectionStringand passes it to thepgclient inworker/db.ts. Every API request that needs the database callswithClient(env, async (client) => { ... }), which opens a connection, runs your queries, and closes it. Hyperdrive handles pooling and optional caching on the Cloudflare side.
So: coding language for the backend is TypeScript; database is Neon PostgreSQL; integration with Cloudflare is Workers + Hyperdrive (Worker talks to Neon via Hyperdrive’s connection string).
| Component | Role |
|---|---|
| Cloudflare Workers | Run the Hono app: serve the REST API and static assets (HTML/CSS/JS) from public/. |
| Assets binding | assets = { directory = "public", binding = "ASSETS" } — Worker serves files from public/ for non-API routes. |
| Hyperdrive | Provides a pooled connection string from the Worker to your Neon PostgreSQL database; configured in the dashboard and bound in wrangler.toml. |
| Wrangler | CLI for local dev (wrangler dev) and deploy (wrangler deploy). |
The app is one Worker: same URL for the website and the API. No separate “frontend host” or “API host.”
- Authentication: Login, logout, JWT in HTTP-only cookie; first-time setup creates the first admin.
- Roles: Admin (full access); Staff (view/add properties; no delete, no user management).
- Staff registration: New users register; they appear in Pending until an Admin approves or rejects.
- Properties:
- List with quick search (single bar: name, owner, phone, IC).
- Search Builder: multiple conditions (column, operator, value), And/Or.
- Add / edit / delete (delete only for Admin).
- CSV import (web): Admin uploads CSV (Property Name, Owner, Phone 01; optional Phone 02, IC). Up to 50,000 rows per file in the UI.
- Export to Excel: download current search results as
.xlsx(e.g. up to 5,000 rows). - Pagination; audit fields: Created By/At, Updated By/At.
- User management (Admin): List users; edit password, email, phone; delete users (except self).
- Theme: Light/dark mode; preference stored per user in Neon.
- Bulk import (millions of rows): Use the Node script with Neon connection string and CSV files; see Bulk import below.
npm install- Create a Neon project at neon.tech and run
NEON_FULL_SCHEMA.sqlin the SQL Editor. - In Cloudflare Dashboard → Workers & Pages → Hyperdrive, create a config that points to your Neon connection string.
- In
wrangler.toml, set the Hyperdrive binding and reference your Hyperdrive config (e.g.id = "..."). The Worker uses this to get the Neon connection via Hyperdrive.
npm run devOpen http://localhost:8787.
When there are no users, open http://localhost:8787/setup.html and create the first admin. Then log in from the main app.
See DEPLOY.md for step-by-step instructions. In short:
- Neon: Schema applied (e.g. run
NEON_FULL_SCHEMA.sql). - Cloudflare: Wrangler login, create Hyperdrive config for Neon, add binding in
wrangler.toml. - Deploy:
npm run deploy. - Open
https://<your-worker>.workers.dev/setup.htmlto create the first admin if needed.
For very large datasets (e.g. 14M+ rows), use the bulk import script (direct connection to Neon; no browser/Worker timeout):
node scripts/bulk-import-properties.js --conn "postgres://user:pass@host/db?sslmode=require" --created-by 1 --file part1.csv --file part2.csvOr a folder of CSVs:
node scripts/bulk-import-properties.js --conn "postgres://..." --created-by 1 --dir ./my-csv-folder- Neon connection string: From Neon Console → your project → Connection string (direct, not Hyperdrive).
--created-by: An existing user ID in the app (e.g.1for the first admin).- CSV: First row = headers. Required: Property Name (or Name), Owner, Phone 01. Optional: Phone 02, IC Number. Save as CSV UTF-8.
Web import remains available for smaller batches (up to 50,000 rows per file) from the Properties page.
| Script | Description |
|---|---|
npm run dev |
Run Worker + static assets locally (Wrangler dev). |
npm run deploy |
Deploy Worker and assets to Cloudflare. |
npm run copy-logo |
Copy logo into public/ (runs before dev/deploy). |
npm run db:create |
Create D1 database (optional; app uses Neon). |
npm run db:migrate |
Apply D1 migrations (optional). |
npm run db:migrate:local |
Apply D1 migrations locally (optional). |
npm run db:studio |
D1 CLI for local DB (optional). |
node scripts/bulk-import-properties.js |
Bulk import from CSV into Neon (see above). |
- What it is: A web app to manage property records with roles (Admin/Staff), approval flow, search, filters, CSV import/export, and optional bulk import for millions of rows.
- Coding languages: TypeScript (backend/API on Cloudflare Workers), HTML/CSS/JavaScript (frontend, no framework).
- Integration with Cloudflare: The app runs entirely on Cloudflare Workers (one Worker serves both the API and the static site). Cloudflare Hyperdrive connects the Worker to your Neon PostgreSQL database with connection pooling.
- Database: Neon PostgreSQL stores all users and properties; schema is in
NEON_FULL_SCHEMA.sql. The Worker talks to Neon only via Hyperdrive’s connection string. - Full structure and flow: Frontend in
public/, backend inworker/, database in Neon; request flow: Browser → Worker (Hono) → Hyperdrive → Neon, as described in the Architecture and Request Flow section above.
For deployment details and troubleshooting, see DEPLOY.md.