Authors: Eduardo Martinez · Raschid Llamas
Course: Aprendizaje Automático para Grandes Volúmenes de Datos
Professor: Dr. Juan Carlos López Pimentel — Universidad Panamericana
A custom SQL workbench that lets you write and execute raw SQL against a remote PostgreSQL database through a secured REST API — no heavy clients like DBeaver or pgAdmin needed. You type SQL in the browser, hit Execute, and get results back as a table.
Think of it as a lightweight database IDE built from scratch.
RemoteSQL/
├── backend/
│ ├── main.py ← FastAPI app (routes, CORS, logging)
│ ├── security.py ← Query validator (whitelist + blocklist)
│ ├── database.py ← psycopg2 connection & result serialization
│ ├── requirements.txt
│ └── Dockerfile
│
├── database/
│ ├── schema.sql ← CREATE TABLE statements (5 tables, 3NF)
│ ├── seed.sql ← Base mock data
│ └── seed_extra.sql ← Extended mock data (20 users, 34 accounts, 92 transactions)
│
├── frontend/
│ ├── index.html ← Workbench layout
│ ├── styles.css ← Dark IDE theme
│ └── app.js ← All frontend logic
│
├── docs/
│ ├── ERD.pdf ← Entity Relationship Diagram
│ └── Architecture.pdf ← Architecture and communication flow diagram
│
├── docker-compose.yml
└── README.md
There are two ways to run the project: Docker (recommended, one command) or local (manual setup).
Requirements: Docker Desktop installed and running.
docker compose upThat's it. Docker will:
- Start a PostgreSQL 16 container and load the schema + all seed data automatically.
- Build and start the FastAPI backend, waiting for the database to be ready.
Once running:
- Open
frontend/index.htmlin your browser. - Enter the API key
remotesql-dev-2026-eduin the topbar and click Save.
To stop:
docker compose downTo do a full reset (wipes the database volume and reloads everything from scratch):
docker compose down -v && docker compose upRequirements: PostgreSQL, Python 3.10+, a browser.
psql -U postgres -c "CREATE DATABASE remotesql;"
psql -U postgres -d remotesql -f database/schema.sql
psql -U postgres -d remotesql -f database/seed.sql
psql -U postgres -d remotesql -f database/seed_extra.sqlCreate backend/.env with your credentials:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=remotesql
DB_USER=postgres
DB_PASSWORD=your_password_here
API_PORT=8000
API_KEY=remotesql-dev-2026-edu
cd backend
pip install -r requirements.txtpython main.pyYou should see:
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
Open frontend/index.html in your browser. Enter the API key from your .env in the topbar and click Save.
If you run into CORS issues opening the file directly, serve it with:
cd frontend && python -m http.server 3000Then visit
http://localhost:3000.
| Feature | How to use |
|---|---|
| Run a query | Type SQL in the editor, press Execute or Ctrl+Enter |
| Browse tables | Schema Explorer on the left — double-click a table to auto-fill a SELECT |
| Save a script | Click Save, enter a name, press Enter — appears in the Saved tab |
| Load past query | Click any entry in the History tab |
| Export results | Click ↓ Export CSV after a SELECT |
-- All users
SELECT * FROM users;
-- Transactions with JOIN across 4 tables
SELECT t.id, u.first_name, c.name AS category, t.amount, t.type, t.created_at
FROM transactions t
JOIN user_accounts ua ON ua.id = t.account_id
JOIN users u ON u.id = ua.user_id
JOIN categories c ON c.id = t.category_id
ORDER BY t.created_at DESC;
-- Net cash flow per account (CTE)
WITH flow AS (
SELECT
t.account_id,
SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) AS total_in,
SUM(CASE WHEN t.type = 'debit' THEN t.amount ELSE 0 END) AS total_out,
COUNT(t.id) AS tx_count
FROM transactions t
GROUP BY t.account_id
)
SELECT
u.first_name || ' ' || u.last_name AS customer,
p.name AS product,
ua.account_number,
ua.balance,
f.total_in,
f.total_out,
f.total_in - f.total_out AS net_flow,
f.tx_count
FROM flow f
JOIN user_accounts ua ON ua.id = f.account_id
JOIN users u ON u.id = ua.user_id
JOIN products p ON p.id = ua.product_id
ORDER BY net_flow DESC;
-- Customers above average balance with last transaction date (2 CTEs + CROSS JOIN)
WITH avg_bal AS (
SELECT AVG(balance) AS overall_avg FROM user_accounts
),
last_tx AS (
SELECT account_id, MAX(created_at) AS last_activity
FROM transactions
GROUP BY account_id
)
SELECT
u.first_name || ' ' || u.last_name AS customer,
u.email,
p.type AS product_type,
ua.account_number,
ua.balance,
ROUND((ua.balance - a.overall_avg)::numeric, 2) AS above_avg_by,
lt.last_activity
FROM user_accounts ua
JOIN users u ON u.id = ua.user_id
JOIN products p ON p.id = ua.product_id
LEFT JOIN last_tx lt ON lt.account_id = ua.id
CROSS JOIN avg_bal a
WHERE ua.balance > a.overall_avg
ORDER BY ua.balance DESC;| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/health |
No | Liveness check |
GET |
/api/schema |
Yes | All tables and columns |
POST |
/api/query |
Yes | Execute a SQL statement |
All protected endpoints require the X-API-Key header.
Request:
{ "query": "SELECT * FROM users LIMIT 5;" }Success:
{
"type": "select",
"columns": ["id", "email", "first_name"],
"rows": [[1, "ana.garcia@mail.com", "Ana"]],
"row_count": 1
}Error:
{
"error": "Query rejected by security filter.",
"detail": "Statement type 'DROP' is not permitted."
}Every query passes through a pipeline before touching the database:
- API Key check —
X-API-Keyheader must match the server'sAPI_KEYenv var. Returns401if wrong. - Rate limiting —
POST /api/querycapped at 30 req/min,GET /api/schemaat 60 req/min. Returns429if exceeded. - SQL Validator — strips comments, checks the statement type against a whitelist (
SELECT,INSERT,UPDATE,WITH), scans for blocked keywords, rejects stacked statements, and enforces a 4,000-character limit. - Execution — runs inside a
try/exceptwith automatic rollback on error. Only a sanitized message is returned to the client — no stack traces.
Blocked keywords: DROP, DELETE, TRUNCATE, ALTER, CREATE, COPY, EXEC, EXECUTE, SLEEP, PG_SLEEP, and others.
Five tables in Third Normal Form (3NF):
users ──────────< user_accounts >────────── products
│
transactions >──────────── categories
| Table | Rows | Purpose |
|---|---|---|
users |
20 | Customer profiles |
categories |
10 | Transaction category lookup |
products |
5 | Financial products (savings, checking, credit, loan, investment) |
user_accounts |
34 | Links users to products; holds balance |
transactions |
92 | Financial movements |
- Added API key authentication (
X-API-Keyheader) and rate limiting. - SQL syntax highlighting in the editor (CodeMirror 5).
- Client-side pagination (50 rows/page) and execution time in results toolbar.
- Saved Scripts panel — name and persist queries in localStorage.
- Docker Compose support for one-command deployment.
- Extended seed data (20 users, 34 accounts, 92 transactions).
- Initial build. All three phases (DB, API, Frontend) complete and functional.
- 5-table schema in 3NF with realistic fintech mock data.
- Security layer with whitelist/blocklist and comment stripping.
- Dark workbench UI with schema explorer, history, and CSV export.