🏷️ Cloud-Native, Metadata-Driven & Schema-Driven Master Data Management (MDM) Engine (FastAPI + Python + Prisma + PostgreSQL + Next.js 16 + TypeScript)
Enterprise-grade master data management UI and API layer over 29 PostgreSQL master tables, implemented as a generic CRUD pipeline using FastAPI, Prisma (Python), and Next.js 16.
The Master Table Management Platform is a full-stack master data management solution designed to centralize
and standardize CRUD operations across 29 PostgreSQL “master” tables such as
mast_country, mast_state, mast_region, mast_pincode,
mast_place, mast_sector, mast_skills, mast_user, mast_zone, and user_template.
Instead of per-table API implementations and screens, the system exposes a single, parameterised REST endpoint on the backend
and a schema-driven admin UI on the frontend.
The backend, implemented with FastAPI and the Prisma Python client, connects to a PostgreSQL database
(Supabase in development). It performs dynamic, safe CRUD using query_raw with fully parameterised SQL
and runtime primary-key discovery from PostgreSQL catalogs. All operations are restricted to an explicit
allowlist of tables (ALLOWED_TABLES) to prevent arbitrary table access.
The frontend is a Next.js 16 + TypeScript single-page admin UI. It does not contain table-specific business logic. Instead, it:
- Loads table rows and associated metadata from the backend.
- Infers column structure from returned JSON.
- Renders a reusable data grid with search, pagination, Add/Edit/Delete, and modal-based forms.
Configuration is environment-driven via DATABASE_URL (backend) and NEXT_PUBLIC_API_URL (frontend),
enabling split deployment: the UI can be hosted on Vercel while the API runs on a VPS behind a reverse proxy.
Multiple master tables (geographical, classification, user traits, skills, etc.) are required across products and internal workflows. Historically, each table often comes with:
- Individual CRUD forms.
- Per-table database scripts.
- Duplicated logic and inconsistent validation.
This increases maintenance cost, onboarding time, and risk of data inconsistency.
- Provide a single, generic admin interface for all master tables in PostgreSQL.
- Provide a single, generic CRUD API powered by FastAPI and Prisma.
- Restrict operations to a curated list of master tables (
ALLOWED_TABLES). - Support flexible deployment with clear separation of frontend and backend concerns.
- Expose CRUD operations over 29 master tables via
/api/data. - Support pagination, free-text search, and dynamic schema handling.
- Allow quick inclusion of new master tables with minimal configuration.
- Ensure safe database access with parameterised SQL and table allowlisting.
- Achieve clean, composable architecture that supports split deployment (VPS API + Vercel UI).
- Provide a developer-friendly experience with predictable behaviour and standard tooling.
| Area | Criteria |
|---|---|
| CRUD Behaviour |
|
| Table Safety | Requests to tables not in ALLOWED_TABLES must return HTTP 400 with an explanatory message. |
| Primary Key Handling | Primary key discovery from PostgreSQL catalogs must work for numeric and non-numeric keys. |
| UI Behaviour |
|
| Integration & CORS | Cross-origin requests from http://localhost:3000 and http://127.0.0.1:3000 succeed without CORS errors. |
/ and /api/data.
DataTable component.
mast_* and user_template tables.
- User selects a table from the Next.js dashboard.
- Next.js calls
GET /api/data?table=<TABLE>&limit=<L>&offset=<O>&search=<S>. - FastAPI validates the table, queries PostgreSQL with Prisma
query_raw, and discovers the primary key. - The response
{ data, total, limit, offset, primaryKey }is used to render the grid and infer columns. - On Create/Update/Delete, the frontend calls the same endpoint with appropriate method and payload.
| Layer | Technology | Role |
|---|---|---|
| Backend Framework | FastAPI | HTTP routing, request/response lifecycle, automatic OpenAPI docs. |
| DB Client | Prisma (Python) | Typed access to PostgreSQL, executing query_raw for dynamic SQL. |
| Database | PostgreSQL / Supabase | Source of truth for all master data tables. |
| Frontend Framework | Next.js 16 (App Router) | React-based SPA with server-side and client-side rendering. |
| Language | TypeScript | Static types for UI code and data contracts. |
| Styling | Tailwind CSS | Utility-first styling for DataTable and layout. |
| UX Utilities | react-hot-toast, lucide-react | Notifications and iconography. |
The platform manages 29 master tables, including but not limited to:
- Location hierarchy:
mast_country,mast_state,mast_district,mast_region,mast_pincode,mast_place,mast_zone - Human attributes & skills:
mast_ability,mast_activity,mast_aptitude,mast_knowledge,mast_lang,mast_skills,mast_trait - Business-related classifications:
mast_industry,mast_leadtype,mast_outlook,mast_pathway,mast_preference,mast_sector,mast_status,mast_stem,mast_task,mast_technology,mast_tools - User & template:
mast_user,user_template, plus supporting tables likemast_contact,mast_data.
Primary keys vary by table and may be numeric (for example regionid, stateid, districtid, pinid) or textual
(for example country_code). A key design decision is not to hardcode these names in the backend;
instead, they are discovered dynamically from PostgreSQL catalogs for every table in ALLOWED_TABLES.
Development base URL: http://127.0.0.1:8000.
FastAPI automatically exposes Swagger UI at /docs, showing / and /api/data.
| Method | Path | Description |
|---|---|---|
| GET | / | Healthcheck: confirms API is running and DB connectivity is available. |
| GET | /api/data | Read table rows with optional search and pagination. |
| POST | /api/data | Create a new row in a specified table. |
| PUT | /api/data | Update a row identified by primary key. |
| DELETE | /api/data | Delete a row identified by primary key. |
- Query parameters:
table(required) – target table name, must exist inALLOWED_TABLES.limit(optional, default 10) – maximum number of rows.offset(optional, default 0) – starting offset for pagination.search(optional) – case-insensitive search string.
- Behaviour:
- Validate that
tablebelongs to the allowlist. - Compute
totalusingSELECT COUNT(*)::int FROM "table". - If
searchis not empty, filter using:CAST(row_to_json(t) AS TEXT) ILIKE $1with placeholders for value. - Otherwise, fetch unfiltered rows using
LIMITandOFFSET. - Discover primary key column name via
get_primary_key_column(table). - Return
{ data, total, limit, offset, primaryKey }.
- Validate that
- Query parameters:
table(required).
- Request body: JSON object where keys are column names and values their corresponding data.
- Behaviour:
- Validate table allowlist.
- Parse payload; ensure non-empty body.
- Build parameterised insert query:
placeholders
$1..$nmapped to values from payload. - Execute
INSERT INTO "table" (...) VALUES (...) RETURNING *viaquery_raw. - Return inserted row with message
Created successfully.
- Query parameters:
table(required).id(required) – primary key value as string (PostgreSQL handles casting where appropriate).
- Behaviour:
- Validate table allowlist.
- Read payload and compute assignments
"col" = $ifor each updated field. - Resolve primary key column name via
get_primary_key_column(table). - Append primary key comparison as last parameter:
WHERE "pk" = $n. - Execute
UPDATE ... RETURNING *. If no rows returned, raise HTTP 404.
- Query parameters:
table(required).id(required) – primary key value as string.
- Behaviour:
- Validate table allowlist.
- Determine
pk_columnviaget_primary_key_column. - Execute
DELETE FROM "table" WHERE "pk_column" = $1. - Return
{ "message": "Deleted successfully" }.
| Status | Example Scenario | Notes |
|---|---|---|
| 200 | Successful CRUD / healthcheck. | Standard success path. |
| 400 | Table not in ALLOWED_TABLES or invalid request payload. | Returned via HTTPException with message. |
| 404 | PUT/DELETE target row not found. | No rows from UPDATE ... RETURNING *. |
| 422 | Missing required query parameters. | FastAPI built-in validation error. |
| 500 | DB errors such as type mismatches or constraints. | Exception surfaced as generic HTTP 500 with error detail. |
frontend/app/page.tsx– landing dashboard, renders tiles for each master table using static metadata fromfrontend/lib/tables.ts.frontend/components/DataTable.tsx– core reusable component providing dynamic CRUD functionality.
The component is parameterised by tableName and an onClose callback.
- State:
data,columns,loading,search,page,total.showModal,editingRow,formData, constantlimit = 10.
- API base URL: derived from
NEXT_PUBLIC_API_URLenv or defaults tohttp://127.0.0.1:8000. - Column inference: deduces table schema from keys of first row in
data. - Networking:
- GET – for listing and search.
- POST – create.
- PUT – update (requires PK).
- DELETE – delete (requires PK).
- PK heuristic in UI: first column with
"id"substring; current implementation can be enhanced by using backend-providedprimaryKey. - Forms: text inputs for all columns; type-awareness is intentionally minimal and delegated to DB constraints.
- Backend connection string is stored in
backend/.envasDATABASE_URL(not committed). - Supabase keys or DB credentials are environment-level secrets.
All operations are constrained by an explicit ALLOWED_TABLES list in main.py.
If a table parameter is not present in this list, the API returns HTTP 400.
This prevents runtime access to any non-master tables and mitigates risk from malicious table names.
CORSMiddleware in FastAPI permits calls from the frontend origins
http://localhost:3000 and http://127.0.0.1:3000, and in production can be restricted
further to the deployed Vercel domain(s).
- Clone repository on VPS and navigate to
backend. - Create and activate virtual environment.
- Install dependencies and run
prisma generate. - Run application using:
uvicorn app.main:app --host 0.0.0.0 --port 8000, supervised bysystemdor another process manager. - Optionally place Nginx in front as TLS terminator and reverse proxy for
/and/api/data.
- Push repository to GitHub.
- Import project into Vercel with root directory
frontend/. - Configure env variable
NEXT_PUBLIC_API_URLto the VPS API URL (for examplehttps://api.saubhtech.com/master-data). - Vercel runs
npm installandnpm run buildby default; produced static assets and serverless functions host the Next.js app.
- Clone:
git clone ...andcd master-data-management-platform/backend. - Virtualenv:
python -m venv envthen activate. - Install:
pip install -r requirements.txt. - Configure DB: create
.envwithDATABASE_URL. - Prisma: run
prisma db pull(if schema changed) andprisma generate. - Run:
uvicorn app.main:app --reload.
- Navigate:
cd ../frontend. - Install:
npm install. - Configure: create
.env.localwithNEXT_PUBLIC_API_URL=http://127.0.0.1:8000. - Run Dev:
npm run devand openhttp://localhost:3000.
| ID | Area | Action | Expected Result |
|---|---|---|---|
| T1 | Health | curl http://127.0.0.1:8000/ | JSON with status and db keys. |
| T2 | Docs | Open /docs in browser. | Swagger UI listing / and /api/data. |
| T3 | GET | curl "/api/data?table=mast_country&limit=5&offset=0" | Array of ≤5 rows plus total. |
| T4 | Search | curl "/api/data?table=mast_country&search=Ind" | Rows where JSON text contains "Ind". |
| T5 | POST | Insert into mast_region with valid payload. | Created successfully with inserted row. |
| T6 | PUT (UI) | Edit a row via DataTable. | Success toast; row updated in table and DB. |
| T7 | DELETE (UI) | Delete selected row. | Success toast; row removed from UI and DB. |
| T8 | Allowlist | Call with disallowed table. | HTTP 400 with Table not allowed. |
| T9 | CORS | Trigger requests from Next.js dev server. | No CORS errors in browser console. |
- Pagination:
limit/offsetparameters prevent fetching full-table datasets. - Server-side search: executed in PostgreSQL using ILIKE on
row_to_jsontext representation.
- Implement keyset pagination for very large tables to avoid deep offsets.
- Create dedicated indexes on commonly searched text columns instead of full-row JSON search.
- Introduce connection pooling at DB layer (for example, via Prisma options or PG pool configuration).
- Move from row-to-JSON search to column-specific predicates for improved performance and optimizer friendliness.
- PowerShell virtualenv error: resolved by using
.\env\Scripts\Activate.ps1. - Prisma keyword conflict (column named
pass): handled inschema.prismawith appropriate mapping. - CORS missing header: fix via proper
CORSMiddlewareconfiguration. - Type mismatch errors (for example
smallint = text): enforce numeric types in frontend for PK fields or cast on backend. - PK detection in UI: current heuristic based on
idsubstring; recommended enhancement to consume backendprimaryKey.
- Keep
ALLOWED_TABLESaligned with database schema and business rules. - When adding a new master table:
- Update DB schema and seed files.
- Re-run
prisma db pullandprisma generateif Prisma models are used elsewhere. - Add table to
ALLOWED_TABLES. - Configure a corresponding tile entry in
frontend/lib/tables.ts.
- Monitor DB logs and error messages for recurring type issues; reflect corrections either in UI validations or backend casting.
- Expose
primaryKeyfrom backend GET responses and update frontend DataTable to rely on it. - Introduce field-level metadata to render appropriate input types (numeric, boolean, dates, enums).
- Add authentication and role-based access control around the CRUD endpoint.
- Implement audit logging for all Create/Update/Delete operations for compliance and traceability.
- Provide export/import capabilities (CSV) per master table.
- Unified CRUD API for 29 master tables with a single endpoint and dynamic PK handling.
- Fully generic Next.js admin UI requiring no per-table components or forms.
- Strict table allowlisting and environment-based configuration for controlled access and portability.
- Completed validation of CRUD flows, CORS configuration, and PK discovery logic.
- Ready for deployment to VPS + Vercel with documented installation and operational steps.
project-root/ ├── backend/ │ ├── app/ │ │ └── main.py │ ├── prisma/ │ │ └── schema.prisma │ ├── requirements.txt │ └── .env # DATABASE_URL (local only) │ ├── database/ │ ├── schema.sql │ └── seed.sql │ ├── frontend/ │ ├── app/ │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── globals.css │ │ └── favicon.ico │ ├── components/ │ │ └── DataTable.tsx │ ├── lib/ │ │ └── tables.ts │ ├── package.json │ ├── tsconfig.json │ ├── next.config.ts │ ├── tailwind.config.js │ └── .env.local # NEXT_PUBLIC_API_URL (local only)
- Start backend:
uvicorn app.main:app --reload. - Start frontend:
npm run devinfrontend. - Open
http://localhost:3000. - Select a table (for example Country) from dashboard.
- Perform the following in order:
- Search for an existing record.
- Create a new record using modal.
- Edit the newly created record.
- Delete the test record.
- Optionally, verify the operations directly in PostgreSQL (psql or Supabase UI).
The Master Table Management Platform consolidates 29 heterogeneous master tables into a single, coherent CRUD pipeline with a generic FastAPI endpoint and a dynamic Next.js admin UI. It leverages Prisma for safe, parameterised PostgreSQL access and discovers table primary keys at runtime, avoiding per-table backend code. The project adheres strictly to the behaviour defined in the current repository and notes, and is structured for enterprise deployment via a split architecture (VPS backend + Vercel frontend).
Future enhancements—particularly type-aware forms, audit trails, and role-based access control—can be layered on without disrupting the core generic CRUD design, making this platform a solid foundation for broader master data governance initiatives.