p2i: add Database Integration tutorial — all engines, query API, pool config, migrations, saturation handling

swadhinbiswas committed 3 months ago
67e97ec
1 parent c67d8ba
commit 67e97ec
1 file changed
+170 -0
▼docs_site/src/content/docs/tutorials/database-integration.md
+170-0
1
+---
2
+title: Database Integration
3
+description: Connect to SQL and NoSQL databases with JustAPI's Rust-native connection pool.
4
+---
5
+
6
+JustAPI provides a built-in database connection pool manager (`app.db`) that executes queries in Rust via `sqlx`, keeping the GIL released for maximum throughput.
7
+
8
+## Quick Start: SQLite
9
+
10
+```python
11
+from justapi import JustAPIApp
12
+
13
+app = JustAPIApp()
14
+
15
+app.set_database(
16
+ "sqlite:///app.db",
17
+ init_sql="""
18
+ CREATE TABLE IF NOT EXISTS items (
19
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
20
+ name TEXT NOT NULL,
21
+ quantity INTEGER DEFAULT 0
22
+ )
23
+ """,
24
+)
25
+
26
+
27
+@app.get("/items")
28
+def list_items(request):
29
+ return app.db.query("SELECT * FROM items ORDER BY id")
30
+
31
+
32
+@app.post("/items")
33
+def add_item(request):
34
+ data = request.json()
35
+ app.db.execute(
36
+ "INSERT INTO items (name, quantity) VALUES ($1, $2)",
37
+ [data["name"], data.get("quantity", 0)],
38
+ )
39
+ return {"message": "Item created"}
40
+```
41
+
42
+## Supported Databases
43
+
44
+| Engine | URL Scheme | Driver | Use Case |
45
+|---|---|---|---|
46
+| **PostgreSQL** | `postgres://user:pass@host/db` | Rust `sqlx-postgres` | Production OLTP |
47
+| **MySQL** | `mysql://user:pass@host/db` | Rust `sqlx-mysql` | Scalable relational |
48
+| **SQLite** | `sqlite:///path/to/db.db` | Rust `sqlx-sqlite` | Embedded, dev, single-server |
49
+| **DuckDB** | `duckdb:///path/to/db.duckdb` | Python `duckdb` | Analytical OLAP |
50
+| **ClickHouse** | `clickhouse://host:9000/db` | Python `clickhouse-driver` | Column-store analytics |
51
+| **MongoDB** | `mongodb://host:27017/db` | Python `pymongo` | Document store |
52
+| **Redis** | `redis://host:6379/0` | Python `redis` | Caching, pub/sub |
53
+
54
+## PostgreSQL Example
55
+
56
+```python
57
+app.set_database(
58
+ "postgres://postgres:password@localhost:5432/mydb",
59
+ init_sql="""
60
+ CREATE TABLE IF NOT EXISTS users (
61
+ id SERIAL PRIMARY KEY,
62
+ name TEXT NOT NULL,
63
+ email TEXT UNIQUE NOT NULL
64
+ )
65
+ """,
66
+)
67
+
68
+
69
+@app.post("/users")
70
+def create_user(request):
71
+ data = request.json()
72
+ result = app.db.query(
73
+ "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id",
74
+ [data["name"], data["email"]],
75
+ )
76
+ return {"user_id": result[0]["id"]}
77
+```
78
+
79
+## Query API
80
+
81
+The `app.db` object provides two methods:
82
+
83
+| Method | Description | Returns |
84
+|---|---|---|
85
+| `app.db.query(sql, params)` | Execute a SELECT query | `list[dict]` — rows as dicts |
86
+| `app.db.execute(sql, params)` | Execute INSERT/UPDATE/DELETE | `int` — affected rows |
87
+
88
+## Connection Pool Configuration
89
+
90
+```python
91
+app.set_database(
92
+ "postgres://localhost/mydb",
93
+ max_connections=20, # Pool size (default: 10)
94
+ request_acquire_timeout=3.0, # Seconds before 503 (default: 3.0)
95
+ init_sql="CREATE TABLE IF NOT EXISTS ...",
96
+)
97
+```
98
+
99
+### SQLite-Specific Options
100
+
101
+```python
102
+app.set_database(
103
+ "sqlite:///app.db",
104
+ wal=True, # Enable WAL mode for concurrent access
105
+ pragmas=["journal_mode=WAL", "synchronous=NORMAL"],
106
+)
107
+```
108
+
109
+By default, JustAPI enables `busy_timeout=5000`, WAL journal mode, and `synchronous=NORMAL` for all SQLite connections to support concurrent reads and writes.
110
+
111
+## Pool Saturation Handling
112
+
113
+When the connection pool is exhausted, requests get **503 Service Unavailable** instead of waiting indefinitely:
114
+
115
+```python
116
+# Default: request_acquire_timeout=3.0 seconds
117
+# If no connection is available within 3s, returns 503
118
+app.set_database("sqlite:///app.db", request_acquire_timeout=1.0)
119
+```
120
+
121
+## Migration System
122
+
123
+JustAPI includes a simple migration runner:
124
+
125
+```bash
126
+justapi db migrate # Apply pending migrations
127
+justapi db rollback # Rollback last migration
128
+justapi db list # List migration status
129
+```
130
+
131
+Migrations are SQL files in the `migrations/` directory:
132
+
133
+```sql
134
+-- migrations/0002_add_email.sql
135
+ALTER TABLE users ADD COLUMN email TEXT;
136
+```
137
+
138
+## Using Native Drivers (Python)
139
+
140
+For databases not supported by `sqlx`, use standard Python drivers:
141
+
142
+```python
143
+import duckdb
144
+
145
+
146
+app = JustAPIApp()
147
+conn = duckdb.connect("analytics.duckdb")
148
+
149
+
150
+@app.get("/analytics/sales")
151
+def sales_summary(request):
152
+ result = conn.execute(
153
+ "SELECT region, SUM(amount) as total FROM sales GROUP BY region"
154
+ ).fetchall()
155
+ return {"summary": [{"region": r[0], "total": r[1]} for r in result]}
156
+```
157
+
158
+## How It Works Internally
159
+
160
+1. `app.set_database()` creates a Rust `sqlx` connection pool (`AnyPool`)
161
+2. Queries are executed in Rust via `py.detach()` — zero GIL overhead
162
+3. Results are returned as Python dicts via zero-copy serialization
163
+4. Transactions are auto-managed: commit on 2xx, rollback on error
164
+5. Pool saturation returns 503 with `Retry-After: 1` header
165
+
166
+## Next Steps
167
+
168
+- [Background Tasks](/tutorials/background-tasks/) — Async processing after DB writes
169
+- [Dependency Injection](/tutorials/dependency-injection/) — DB session as a dependency
170
+- [Performance Tuning](/advanced/performance-tuning/) — Optimize database queries
0
171