AI-Powered Database Dashboard Builder
Connect your database, ask questions in natural language, get interactive charts in seconds.
Connect your database β Ask in natural language β Get beautiful, interactive charts β powered by Google Gemini.
- What is InsightNode?
- Use Cases
- Features
- Architecture
- Data Flow
- Tech Stack
- Project Structure
- AI Pipeline
- Database Adapter System
- Security Layer
- API Reference
- Authentication
- Internationalization (i18n)
- Theming
- localStorage Persistence
- Chart Types
- Export System
- Testing
- Smart Suggestions
- Streaming Pipeline
- Getting Started
- License
InsightNode is an AI-powered dashboard builder that allows users to query their databases without any SQL or MongoDB knowledge.
A user asks a question in natural language (e.g., "Show me the monthly sales trend"), and the system:
- Translates the question into a database query (SQL or MongoDB aggregation) using Google Gemini AI
- Passes the query through a multi-layer security sanitizer (SQL injection protection)
- Executes the query on the actual database
- Sends the results back to Gemini AI to automatically select the best chart type
- Presents it to the user as a beautiful, interactive chart
All of this happens in seconds from a single sentence typed into a text input.
| Principle | Description |
|---|---|
| Zero SQL Knowledge | Users only ask questions in natural language |
| Privacy-First AI | Only column names + 3 sample rows are sent to Gemini; all data stays on the server |
| Security-First | Every AI-generated query goes through multi-layer sanitization before execution |
| Multi-Database | PostgreSQL, MySQL, and MongoDB supported from a single interface |
| Real-Time | Streaming (NDJSON) provides instant feedback for each pipeline step |
InsightNode is designed for the following scenarios:
- Business Analytics: Analyze sales, revenue, and customer databases β without writing SQL
- Quick Data Exploration: Connect to a new database and ask "what's in this table?"
- Reporting: Quickly generate charts for meetings, export as PNG/CSV/JSON
- Education: For those learning SQL β type a question, inspect the generated query
- Multi-Turn Analysis: Ask follow-up questions like "Now show this by month", "Filter only 2024"
- Natural language β SQL/MongoDB query conversion
- Google Gemini
gemini-2.0-flashmodel - Function Calling mode guarantees structured output
- Multi-turn conversation support (last 10 messages sent as context)
- Bar Chart: For comparisons
- Line Chart: Time series trends
- Area Chart: Cumulative data
- Pie Chart: Proportional distributions (donut style)
- Scatter Plot: Correlation analysis
- KPI Card: Single-value indicator (change percentage + mini sparkline)
- Data Table: Sortable, paginated data table
- PostgreSQL (pg driver)
- MySQL (mysql2 driver)
- MongoDB (native driver β URI or manual connection)
- Real-time progress via NDJSON
- 4 steps: Generating β Validating β Executing β Charting
- Each step shown with animated progress bar
- SQL injection protection (17 blocked keywords, INTO OUTFILE/DUMPFILE blocking)
- MongoDB operation whitelist (read-only operations only)
- MySQL nested comment attack detection
- Prompt sanitization (2,000 character limit)
- Query length limit (10,000 characters)
- Password-protected access (optional)
- English (EN) and Turkish (TR)
- All UI text, AI directives, and placeholders included
- One-click language switching
- Dark Mode (default) β Vercel/Linear inspired
- Light Mode β Full CSS variable set
- System Mode β Follows OS preference
- Persisted via localStorage
- Database connections saved to localStorage
- Passwords obfuscated with base64
- Active connection automatically restored on page refresh
- Quick access to saved connections via header dropdown
- PNG β html2canvas at 2x resolution
- CSV β BOM-enabled UTF-8 (Excel compatible)
- JSON β Pretty-printed
- After connecting, AI suggests 6 schema-specific questions
- Click-to-run suggestion chips
- Fallback: Default suggestions based on language
- Activated via
ADMIN_PASSWORDenvironment variable - httpOnly cookie-based session (7 days)
- Next.js Edge middleware protection
- Auth completely disabled when not set
- 61 tests with Vitest
- Query sanitizer security tests (38 test cases)
- Validator tests (23 test cases)
- Path alias support
InsightNode follows a layered architecture principle. Each layer carries a single responsibility:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRESENTATION LAYER β
β React 19 Components (Client) β
β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββ β
β β Header β β CommandInβ βChartCard β βEmptyState β β
β β Modal β β Progress β βDynChart β β Login β β
β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β CONTEXT LAYER β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β βLanguageCtx β β ThemeCtx β β Providers β β
β β (i18n EN/TR) β β (dark/light) β β (compose) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β API LAYER (Server) β
β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββ β
β β/api/queryβ β/api/conn β β/api/sugg β β /api/auth β β
β β(stream) β β(test+sch)β β(AI sug.) β β (login) β β
β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SERVICE LAYER β
β ββββββββββββββββββ ββββββββββββββββββ βββββββββββββββ β
β β QueryGenerator β βChartFormatter β βSuggestionGenβ β
β β (TextβSQL) β β(DataβChartCfg) β β(SchemaβTips)β β
β ββββββββββββββββββ ββββββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β AI LAYER β
β ββββββββββββββββββ ββββββββββββββββββββββββββββββββ β
β β GeminiClient β β FunctionDeclarations (3 tool)β β
β β (singleton) β β execute_query / render_chart β β
β β β β suggest_queries β β
β ββββββββββββββββββ ββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SECURITY LAYER β
β ββββββββββββββββββ βββββββββββββββ ββββββββββββββββββ β
β βQuerySanitizer β β Validators β β Middleware β β
β β(SQL/Mongo) β β (form+promptβ β (auth guard) β β
β ββββββββββββββββββ βββββββββββββββ ββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β DATABASE LAYER β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β DatabaseAdapter (Interface) β β
β β ββββββββββββ ββββββββββββ ββββββββββββββββ β β
β β βPostgreSQLβ β MySQL β β MongoDB β β β
β β β (pg) β β (mysql2) β β (native) β β β
β β ββββββββββββ ββββββββββββ ββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β STORAGE LAYER (Client) β
β ββββββββββββ ββββββββββββ ββββββββββββ β
β βConnectionsβ βChatHistoryβ βDashboardsβ β
β β(localStorageβ(localStorageβ(localStorage β
β ββββββββββββ ββββββββββββ ββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Pattern | Implementation |
|---|---|
| Adapter Pattern | DatabaseAdapter interface + 3 concrete implementations (PG, MySQL, MongoDB) + factory function |
| Function Calling (Structured Output) | Gemini AI returns structured JSON every time using FunctionCallingConfigMode.ANY |
| Streaming (NDJSON) | ReadableStream server-side, ReadableStreamDefaultReader client-side |
| Context Pattern | Theme + Language React Contexts with cascading providers |
| Barrel Exports | Each module re-exports via index.ts |
| Privacy-Preserving AI | Chart formatter sends only column names + 3 sample rows β full dataset never reaches AI |
| Security-First | AI output β Sanitizer β Database ordering ensures no AI-generated query runs directly |
| Composition over Inheritance | Radix UI primitives β shadcn wrappers β domain components β page assembly |
User: "Show me total sales by month"
β
βΌ
ββ CommandInput ββββββββββββββββββββββββββββββ
β onSubmit(prompt) β
β β page.tsx handleQuerySubmit() β
β β Add "user" message to chatHistory β
β β POST /api/query { streaming: true } β
ββββββββββββββββββββββ¬ββββββββββββββββββββββββ
β
βββββββββββββββ SERVER βββββββββββββββ
β
βΌ
ββ /api/query (ReadableStream) βββββββββββββββ
β β
β β NDJSON: {"step":"generating"} β
β sanitizePrompt(prompt) β
β createDatabaseAdapter(connection) β
β adapter.connect() β
β adapter.getSchema() β
β generateQuery(prompt, schema, β
β dbType, locale, conversationHistory) β
β βββ Gemini AI (Function Calling) β
β Tool: execute_database_query β
β β { query_string, query_type } β
β β
β β‘ NDJSON: {"step":"validating"} β
β sanitizeSQLQuery(query_string) β
β β’ 10,000 character limit β
β β’ Comment stripping β
β β’ 17 blocked keyword check β
β β’ INTO OUTFILE/DUMPFILE blocking β
β β’ MySQL nested comment detection β
β β’ Must start with SELECT or WITH β
β β
β β’ NDJSON: {"step":"executing"} β
β adapter.executeQuery(sanitizedQuery) β
β β { rows[], columns[], rowCount, ms } β
β β
β β£ NDJSON: {"step":"charting"} β
β formatChart(rows, columns, prompt) β
β βββ Gemini AI (Function Calling) β
β Input: column names + 3 rows β
β Tool: render_chart β
β β { chart_type, title, colors } β
β β
β β€ NDJSON: {"step":"done","data":{...}} β
β adapter.disconnect() β
β controller.close() β
ββββββββββββββββββββββ¬ββββββββββββββββββββββββ
β
βββββββββββββββ CLIENT βββββββββββββββ
β
βΌ
ββ page.tsx (Stream Reader) ββββββββββββββββββ
β reader.read() β Parse NDJSON lines β
β Each "step" β QueryProgress animation β
β "done" β Prepend to charts[] state β
β Add "assistant" message to chatHistory β
β Show toast notification β
ββββββββββββββββββββββ¬ββββββββββββββββββββββββ
β
βΌ
ββ ChartCard β DynamicChart ββββββββββββββββββ
β chartType'a gΓΆre dispatch: β
β bar β <BarChart> pie β <PieChart> β
β line β <LineChart> scatter β <Scatter> β
β area β <AreaChart> kpi β KPI component β
β table β DataTable β
β β
β + Export toolbar (PNG/CSV/JSON) β
β + SQL query viewer β
β + Delete button β
ββββββββββββββββββββββββββββββββββββββββββββββ
| Package | Version | Role |
|---|---|---|
next |
^16.0.0 |
React meta-framework (App Router, API routes, middleware) |
react |
^19.0.0 |
UI library |
react-dom |
^19.0.0 |
React DOM renderer |
@google/genai |
^1.41.0 |
Google Gemini AI SDK (Function Calling) |
recharts |
^3.7.0 |
Chart library (Bar, Line, Area, Pie, Scatter) |
framer-motion |
^12.34.0 |
Animation library |
pg |
^8.13.0 |
PostgreSQL client driver |
mysql2 |
^3.12.0 |
MySQL client driver (promise-based) |
mongodb |
^6.12.0 |
MongoDB native driver |
sonner |
^2.0.0 |
Toast notifications |
class-variance-authority |
^0.7.1 |
Variant-based component styling |
clsx |
^2.1.1 |
Conditional CSS class merging |
tailwind-merge |
^3.0.0 |
Tailwind class conflict resolver |
lucide-react |
^0.474.0 |
Icon set (200+ icons) |
@radix-ui/* |
Various | Accessible UI primitives (Dialog, Tabs, Label, Select) |
html2canvas |
^1.4.1 |
DOM β Canvas converter (PNG export) |
| Package | Version | Role |
|---|---|---|
typescript |
^5.7.0 |
Type system |
tailwindcss |
^4.0.0 |
Utility-first CSS framework (v4) |
@tailwindcss/postcss |
^4.0.0 |
Tailwind v4 PostCSS integration |
vitest |
^3.2.0 |
Test runner |
eslint |
^9.0.0 |
Linter (flat config) |
eslint-config-next |
^16.0.0 |
Next.js ESLint rules |
InsightNode/
βββ .env.local # Environment variables (GEMINI_API_KEY, ADMIN_PASSWORD)
βββ .gitignore # Git ignore rules
βββ package.json # Dependencies and scripts
βββ tsconfig.json # TypeScript configuration (@/* alias)
βββ next.config.mjs # Next.js configuration
βββ vitest.config.ts # Vitest test runner configuration
βββ eslint.config.mjs # ESLint flat config
βββ postcss.config.mjs # PostCSS + Tailwind v4
βββ README.md # This file
βββ SERVER.md # Setup and deployment guide
β
βββ src/
βββ middleware.ts # Auth middleware (Edge runtime)
β
βββ app/ # Next.js App Router
β βββ layout.tsx # Root layout (<html>, <body>, Providers, Toaster)
β βββ providers.tsx # ThemeProvider β LanguageProvider composition
β βββ globals.css # Tailwind v4 + dark/light CSS variables
β βββ page.tsx # β Main dashboard page (state orchestrator)
β βββ login/
β β βββ page.tsx # Password login page
β βββ api/
β βββ auth/route.ts # POST: login, DELETE: logout
β βββ connections/route.ts # POST: test & connect + schema extraction
β βββ query/route.ts # POST: full AI pipeline (streaming/standard)
β βββ schema/route.ts # POST: schema extraction only
β βββ suggestions/route.ts # POST: AI suggestion generation
β
βββ components/
β βββ charts/
β β βββ chart-card.tsx # Chart card (metadata + export + delete)
β β βββ dynamic-chart.tsx # 7-type chart renderer (Recharts + custom)
β βββ dashboard/
β β βββ header.tsx # Top bar (connection, theme, language, logout)
β β βββ command-input.tsx # Natural language query input + suggestion chips
β β βββ empty-state.tsx # Empty state screen + AI suggestions
β β βββ connection-modal.tsx # Database connection dialog (3 DB types)
β β βββ query-progress.tsx # 4-step pipeline progress indicator
β βββ ui/ # shadcn/Radix UI primitives
β βββ badge.tsx # Badge component (6 variants)
β βββ button.tsx # Button component (6 variants, 4 sizes)
β βββ dialog.tsx # Dialog component (Radix)
β βββ input.tsx # Input component
β βββ label.tsx # Label component (Radix)
β βββ tabs.tsx # Tabs component (Radix)
β
βββ lib/
β βββ utils.ts # cn() β clsx + tailwind-merge
β βββ ai/
β β βββ gemini-client.ts # Gemini AI singleton + generateWithTools()
β β βββ function-declarations.ts # 3 Function Declarations (query, chart, suggest)
β βββ db/
β β βββ index.ts # createDatabaseAdapter() factory
β β βββ postgres.ts # PostgresAdapter (pg.Pool)
β β βββ mysql.ts # MySQLAdapter (mysql2/promise)
β β βββ mongodb-client.ts # MongoDBAdapter (MongoClient)
β βββ i18n/
β β βββ index.ts # Barrel export
β β βββ translations.ts # Translations interface + EN/TR objects (80+ keys)
β β βββ language-context.tsx # LanguageProvider + useLanguage() hook
β βββ theme/
β β βββ index.ts # Barrel export
β β βββ theme-context.tsx # ThemeProvider + useTheme() hook
β βββ storage/
β βββ index.ts # Barrel export
β βββ connections.ts # Connection CRUD (localStorage)
β βββ chat-history.ts # Conversation history (max 20 messages)
β βββ dashboard.ts # Dashboard save/load
β
βββ services/
β βββ query-generator.ts # AI Step 1: Text β SQL/MongoDB query
β βββ chart-formatter.ts # AI Step 2: Data β ChartConfig
β βββ suggestion-generator.ts # AI: Schema β Smart query suggestions
β
βββ types/
β βββ api.ts # ApiResponse<T>, ConversationMessage
β βββ chart.ts # ChartType, ChartConfig, DashboardQueryResponse
β βββ database.ts # DatabaseAdapter interface, all DB types
β
βββ utils/
βββ query-sanitizer.ts # SQL/MongoDB query security sanitization
βββ validators.ts # Form validation + prompt sanitization
βββ export.ts # PNG/CSV/JSON export
βββ __tests__/
βββ query-sanitizer.test.ts # 38 security test cases
βββ validators.test.ts # 23 validation test cases
InsightNode uses Google Gemini's Function Calling feature. This guarantees the AI always returns structured JSON instead of free text.
gemini-2.0-flash
toolConfig: {
functionCallingConfig: {
mode: FunctionCallingConfigMode.ANY // Mandatory function call
}
}Service: src/services/query-generator.ts
User question + Database schema + Conversation history
β
βΌ
Gemini AI API
Tool: execute_database_query
β
βΌ
{ query_string, query_type, explanation }
Prompt structure:
- System role: "Expert {PostgreSQL/MySQL/MongoDB} database analyst"
- Full schema description (table names, column names/types/nullability)
- Last 10 conversation messages (multi-turn support)
- Language directive ("Generate explanations in Turkish")
- Detailed rules: GROUP BY, JOIN, alias, LIMIT, chart-friendly data shape
Function Declaration:
{
name: "execute_database_query",
parameters: {
query_string: STRING, // SQL or MongoDB JSON query
query_type: STRING, // "sql" | "aggregation"
explanation: STRING // Human-readable explanation
}
}Service: src/services/chart-formatter.ts
Column names + 3 sample rows + User question
β
βΌ
Gemini AI API
Tool: render_chart
β
βΌ
{ chart_type, title, x_axis_key, data_keys, colors, kpi_* }
Privacy: Only column names and first 3 rows are sent to Gemini. The full dataset is never transmitted to the AI.
Selection matrix (in prompt):
- Bar β Comparisons
- Line β Time trends
- Area β Cumulative data
- Pie β Proportional distributions
- Scatter β Correlations
- KPI β Single value results
- Table β Detailed, multi-column data
Service: src/services/suggestion-generator.ts
Database schema + Language preference
β
βΌ
Gemini AI API
Tool: suggest_queries
β
βΌ
6 schema-specific query suggestions
Fallback: If Gemini fails, 6 default generic questions are returned based on language.
InsightNode uses the Adapter Pattern to support three different databases through a single interface.
interface DatabaseAdapter {
connect(): Promise<void>;
disconnect(): Promise<void>;
getSchema(): Promise<DatabaseSchema>;
executeQuery(query: string): Promise<QueryResult>;
testConnection(): Promise<boolean>;
}function createDatabaseAdapter(connection: DatabaseConnection): DatabaseAdapter {
switch (connection.type) {
case "postgresql": return new PostgresAdapter(connection);
case "mysql": return new MySQLAdapter(connection);
case "mongodb": return new MongoDBAdapter(connection);
}
}| Adapter | Driver | Connection | Schema Source | Query Mechanism |
|---|---|---|---|---|
| PostgresAdapter | pg.Pool |
host/port/user/pass/db/ssl, max 5 connections, 30s idle timeout | information_schema.tables + columns (public schema) |
pool.query(sql) |
| MySQLAdapter | mysql2.createPool |
host/port/user/pass/db/ssl, limit 5, 10s timeout | information_schema.TABLES + COLUMNS |
pool.query<RowDataPacket[]>(sql) |
| MongoDBAdapter | MongoClient |
URI or field-based, 10s timeout | db.listCollections() + findOne() sample-based |
JSON parse β collection.find() or .aggregate() |
connect() β getSchema() β executeQuery() β disconnect()
Each API call follows this lifecycle. testConnection() follows a connect β trivial op β disconnect sequence.
Every AI-generated query goes through multi-layer security checks before being executed on the database.
AI Output (query_string)
β
βΌ
β Length check (max 10,000 characters)
β
βΌ
β‘ MySQL nested comment detection (/*!50000 ...*/ blocking)
β
βΌ
β’ Comment stripping (-- and /* */ removal)
β
βΌ
β£ Empty query check
β
βΌ
β€ Blocked keyword check (17 keywords):
DROP, DELETE, UPDATE, INSERT, TRUNCATE, ALTER,
CREATE, GRANT, REVOKE, EXEC, EXECUTE, CALL,
MERGE, REPLACE, RENAME, LOAD, SOURCE
β Word-boundary regex (\b) for false-positive protection
β
βΌ
β₯ Blocked pattern check:
INTO OUTFILE, INTO DUMPFILE, INTO LOCAL,
SET (without FROM)
β
βΌ
β¦ Start assertion: Must begin with SELECT or WITH
β
βΌ
β
Safe query β Send to database
Whitelist approach:
β
find, aggregate, countDocuments, estimatedDocumentCount, distinct
β deleteMany, insertOne, updateMany, drop, rename, etc.
Using \b word boundary regex ensures that updated_at column is not blocked as "UPDATE" and settings table is not detected as "SET".
38 dedicated test cases validate all attack vectors:
- All 17 blocked keywords
- Case variations (upper/lower/mixed)
- Hidden attempts inside comment lines
- Destructive operations within subqueries
INTO OUTFILE/INTO DUMPFILE/LOAD DATA- MySQL conditional comments (
/*!*/) - Edge cases (empty, whitespace, max length)
// Request
{ password: string }
// Success Response (200) β httpOnly cookie is set
{ success: true }
// Error Response (401)
{ success: false, error: "Invalid password." }// Response (200) β cookie is cleared
{ success: true }// Request
{
name: string,
type: "postgresql" | "mysql" | "mongodb",
host: string,
port: number,
user: string,
password: string,
database: string,
ssl: boolean,
connectionString?: string, // MongoDB URI mode
connectionMode?: "manual" | "uri" // MongoDB connection mode
}
// Success Response
{
success: true,
data: {
connected: true,
message: "Successfully connected to postgresql database \"mydb\".",
schema: {
tables: [
{
name: "users",
columns: [
{ name: "id", type: "integer", nullable: false },
{ name: "email", type: "varchar", nullable: false }
]
}
],
databaseType: "postgresql"
}
}
}// Request
{
prompt: string, // Natural language question
locale?: string, // "en" | "tr"
connection: ConnectionFormData & { id }, // Connection details
conversationHistory?: ConversationMessage[], // Previous messages
streaming?: boolean // Streaming mode
}
// Standard Response (streaming: false)
{
success: true,
data: {
chartConfig: {
chartType: "bar",
title: "Monthly Sales Totals",
xAxisKey: "month",
dataKeys: ["total_sales"],
colors: [{ key: "total_sales", color: "#6366f1" }],
data: [{ month: "January", total_sales: 15000 }, ...]
},
generatedQuery: "SELECT ... FROM ...",
queryType: "sql",
executionTimeMs: 45,
rowCount: 12
}
}
// Streaming Response (streaming: true)
// Content-Type: application/x-ndjson
{"step":"generating"}
{"step":"validating"}
{"step":"executing"}
{"step":"charting"}
{"step":"done","data":{...DashboardQueryResponse}}
// On error:
{"step":"error","error":"Error message"}// Request
{ schema: DatabaseSchema, locale?: string }
// Response
{
success: true,
data: {
suggestions: [
"Show total revenue by product category",
"What are the top 10 customers by order count?",
"Display monthly user registrations trend",
"Which cities have the highest sales?",
"Compare this year vs last year revenue",
"Show the distribution of order statuses"
]
}
}// Request: ConnectionFormData
// Response
{ success: true, data: DatabaseSchema } βββββββββββββββββββ
β .env.local β
β ADMIN_PASSWORD=? β
ββββββββββ¬βββββββββ
β
ββββββββββββββ΄βββββββββββββ
β β
ADMIN_PASSWORD ADMIN_PASSWORD
is set is NOT set
β β
βΌ βΌ
βββββββββββββββββ ββββββββββββββββ
β Middleware β β Auth disabledβ
β cookie check β β open access β
βββββββββ¬ββββββββ β for everyone β
β ββββββββββββββββ
no cookie β has cookie
β β
βΌ βΌ
ββββββββββββ βββββββββββββ
β Redirect β β Show β
β to /login β β content β
β β β (valid) β
βββββββ¬ββββββ βββββββββββββ
β
βΌ
POST /api/auth
{ password }
β
password === ADMIN_PASSWORD?
ββ Yes β Generate 32-byte hex token
β Set httpOnly cookie (7 days)
β β Redirect to Dashboard
ββ No β 401 "Invalid password"
Security notes:
- Token generated cryptographically secure via
crypto.getRandomValues(new Uint8Array(32)) - Cookie:
httpOnly(no JS access),secure(in production),sameSite: "lax" - Tokens stored in server memory (
globalThis.__insightnode_tokensSet) - Simple protection layer β not production-grade session management
// Type-safe translations
interface Translations {
header: { brand, subtitle, connected, noConnection, ... };
commandInput: { askYourData, placeholders: string[], ... };
emptyState: { readyTitle, suggestedQuestions, ... };
connectionModal: { title, host, port, ... };
chartCard: { showQuery, exportPng, exportCsv, delete, ... };
progress: { generating, validating, executing, charting };
toasts: { connectedTo, chartGenerated, exported, ... };
footer: { brand, poweredBy };
ai: { respondIn };
// ... 80+ total keys
}const { locale, t, toggleLocale } = useLanguage();
// In components:
<h1>{t.header.brand}</h1>
<p>{t.emptyState.readyTitle}</p>
// In AI prompts:
const instruction = t.ai.respondIn; // "Generate explanations in Turkish"- πΊπΈ English (default)
- πΉπ· Turkish
Language switching: One-click via the globe icon in the header.
| Mode | Description |
|---|---|
dark |
Default. #09090b background, #fafafa text |
light |
#fafafa background, #09090b text |
system |
Follows OS preference (prefers-color-scheme media query) |
/* Dark (default β @theme inline) */
--color-background: #09090b;
--color-foreground: #fafafa;
--color-primary: #6366f1;
--color-card: #0a0a0f;
--color-border: #27272a;
/* Light (.light class override) */
--color-background: #fafafa;
--color-foreground: #09090b;
--color-card: #ffffff;
--color-border: #e4e4e7;localStorage key: insightnode_theme. Theme is preserved on page refresh.
| Effect | Dark | Light |
|---|---|---|
| Glass Card | rgba(10,10,15,0.6) + blur |
rgba(255,255,255,0.7) + subtle shadow |
| Glow Pulse | Blue-purple glow | Softer glow |
| Shimmer | rgba(99,102,241,0.08) |
rgba(99,102,241,0.04) |
| Scrollbar | #27272a |
#d4d4d8 |
All modules are SSR-safe (typeof window === "undefined" check).
interface SavedConnection extends ConnectionFormData {
id: string; // crypto.randomUUID()
savedAt: string; // ISO timestamp
}- Passwords obfuscated with
btoa(encodeURIComponent(password)) - Upsert by name (updates if connection with same name exists)
- Active connection stored separately:
insightnode_active_connection
interface ChatMessage {
role: "user" | "assistant";
content: string;
timestamp: string;
}- FIFO β max 20 messages
- Last 10 messages sent to API as
conversationHistory - Automatically cleared on new connection
interface SavedDashboard {
id: string;
name: string;
charts: DashboardQueryResponse[];
layout: DashboardLayoutItem[];
createdAt: string;
updatedAt: string;
}Storage layer is ready; UI integration is planned for a future release.
All charts are rendered with Recharts in src/components/charts/dynamic-chart.tsx.
| Type | Component | Features |
|---|---|---|
| Bar | <BarChart> + <Bar> |
Rounded top corners, max 50px width, angled labels for >5 data points |
| Line | <LineChart> + <Line> |
Monotone interpolation, 2px stroke, r=3 dots, active dot r=5 |
| Area | <AreaChart> + <Area> |
Monotone, 15% fill opacity, 2px stroke |
| Pie | <PieChart> + <Pie> |
Donut style (inner radius 60, outer 130), name: XX% labels, 3Β° padding |
| Scatter | <ScatterChart> + <Scatter> |
Both axes numeric, dashed cursor |
| KPI | Custom KPIChart |
Large number + prefix/suffix, ββ change indicator (green/red), mini sparkline |
| Table | Custom TableChart |
Sortable headers (asc/desc), 10 rows/page, pagination controls |
- Tooltip: Dark background (
rgba(10,10,15,0.95)), border#27272a, 8px radius - Grid: Dashed lines
#1a1a2e - Axis:
#a1a1aacolor, 11px font - Container:
<ResponsiveContainer width="100%" height={360}> - Color palette: 8 default colors (starting with
#6366f1indigo)
The src/utils/export.ts module supports three formats:
| Format | Function | Details |
|---|---|---|
| PNG | exportChartAsPNG(elementId, title) |
html2canvas at 2x scale, dark background. SVG serialization fallback. |
| CSV | exportDataAsCSV(data, filename) |
BOM (\uFEFF) with UTF-8 β Excel compatible. Values with commas/quotes/newlines are properly escaped. |
| JSON | exportDataAsJSON(data, filename) |
2-space indented pretty-print. application/json MIME type. |
Trigger: From each ChartCard's export dropdown menu (Download icon).
Download mechanism: downloadBlob() β Object URL β Programmatic <a> click β URL revoke.
// vitest.config.ts
{
test: {
globals: true, // describe, it, expect are global
environment: "node", // No DOM required
},
resolve: {
alias: { "@": "./src" } // Path alias support
}
}| File | Test Count | Coverage |
|---|---|---|
query-sanitizer.test.ts |
38+ | SQL keyword blocking, comment stripping, subquery attacks, INTO OUTFILE, LOAD DATA, MySQL comments, edge cases, false-positive protection, MongoDB whitelist |
validators.test.ts |
23+ | Form validation (PG, MongoDB URI), prompt sanitization, port range, default ports |
| Total | 61 | Security + validation layers |
npm test # Single run (vitest run)
npm run test:watch # Watch mode (vitest)Connection successful
β
βΌ
fetchSuggestions(schema)
β
βΌ
POST /api/suggestions { schema, locale }
β
βΌ
generateSuggestions() β Gemini AI
Tool: suggest_queries
"Suggest 6 different questions: aggregation, trend,
comparison, distribution, ranking β use real
table/column names"
β
βΌ
βββββββββββββββββββββββββββββββββ
β Suggestions shown in 2 spots: β
β β
β 1. EmptyState β
β β Wrapped pill buttons β
β β Click β Run query β
β β
β 2. Below CommandInput β
β β Horizontal scroll chips β
β β Click β Run query β
βββββββββββββββββββββββββββββββββ
Fallback: If Gemini fails, 6 default generic questions are returned based on locale.
The AI pipeline consists of 4 stages and can take 3β10 seconds total. With a standard JSON response, the user sees a "loading" spinner for the entire duration. With streaming, each step is shown instantly.
Content-Type: application/x-ndjson
Cache-Control: no-cache
Transfer-Encoding: chunked
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
const send = (data: unknown) => {
controller.enqueue(encoder.encode(JSON.stringify(data) + "\n"));
};
send({ step: "generating" });
const query = await generateQuery(...);
send({ step: "validating" });
sanitizeSQLQuery(query.queryString);
send({ step: "executing" });
const result = await adapter.executeQuery(...);
send({ step: "charting" });
const chart = await formatChart(...);
send({ step: "done", data: response });
controller.close();
}
});const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || ""; // Keep incomplete line in buffer
for (const line of lines) {
const chunk = JSON.parse(line);
if (chunk.step) setQueryStep(chunk.step);
if (chunk.data) setFinalResult(chunk.data);
}
}4 steps shown with animation:
[β Generating] βββ [β Validating] βββ [β Executing] βββ [β Charting]
completed active (spinner) waiting waiting
Each step: Loader2 spinner β Check animation (Framer Motion).
Node.js 18.17+ (20.x recommended)
npm 9+
Google Gemini API Key
git clone https://github.com/bcsakalar/insightnode.git
cd insightnode
npm installCreate a .env.local file in the project root:
# [REQUIRED] Google Gemini API key
GEMINI_API_KEY=your_gemini_api_key_here
# [OPTIONAL] Admin password β if not set, auth is disabled
ADMIN_PASSWORD=your_password_here- Go to Google AI Studio
- Click "Create API Key"
- Paste the generated key into your
.env.localfile
npm run dev
# Open http://localhost:3000
# Starts with Turbopack β fast HMR (Hot Module Replacement)npm run build
npm start
# Default port: 3000npm test # Single run
npm run test:watch # Watch mode| Variable | Required | Default | Description |
|---|---|---|---|
GEMINI_API_KEY |
β Yes | β | Google Gemini API key from AI Studio |
ADMIN_PASSWORD |
β No | β | Password for simple auth; if not set, auth is disabled |
NODE_ENV |
β No | development | Affects cookie security (httpOnly: true only in production) |
PORT |
β No | 3000 | Server port for npm start |
This project is licensed under the MIT License.
InsightNode β Talk to your database with AI. β‘