swadhinbiswas

[email protected]
2026-07-25 21:53:06 +0600 committed NaN years ago
1697b71
commit 1697b71
11 files changed
+758 -48
▼docs_site/src/content/docs/api-reference/auth.md
+105-0
1
+---
2
+title: Authentication
3
+description: JWT, OAuth2, and API key authentication in JustAPI — Rust-native and Python-compatible.
4
+keywords: [JustAPI, auth, JWT, OAuth2, API key, authentication, security]
5
+---
6
+
7
+## JwtAuth (Rust-native)
8
+
9
+Fast, Rust-native JWT encoding/decoding:
10
+
11
+```python
12
+from justapi import JustAPIApp, JwtAuth, Security
13
+
14
+app = JustAPIApp()
15
+
16
+jwt = JwtAuth(secret="your-secret-key", algorithm="HS256")
17
+
18
+@app.get("/protected")
19
+def protected(token: dict = Security(jwt)):
20
+ return {"user": token["sub"]}
21
+```
22
+
23
+### JwtAuth Options
24
+
25
+| Parameter | Type | Default | Description |
26
+|-----------|------|---------|-------------|
27
+| `secret` | `str` | — | Signing secret |
28
+| `algorithm` | `str` | `"HS256"` | JWT algorithm |
29
+| `auto_error` | `bool` | `True` | Raise 401 on invalid token |
30
+
31
+### Supported Algorithms
32
+
33
+HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ED25519
34
+
35
+### Encode/Decode
36
+
37
+```python
38
+# Encode
39
+token = jwt.encode({"sub": "alice", "role": "admin"})
40
+
41
+# Decode
42
+claims = jwt.decode(token, verify_exp=True, verify_iat=True)
43
+```
44
+
45
+### Decode Options
46
+
47
+| Option | Type | Description |
48
+|--------|------|-------------|
49
+| `verify_exp` | `bool` | Verify expiration claim |
50
+| `verify_iat` | `bool` | Verify issued-at claim |
51
+| `verify_nbf` | `bool` | Verify not-before claim |
52
+| `verify_aud` | `bool` | Verify audience claim |
53
+| `verify_iss` | `bool` | Verify issuer claim |
54
+
55
+## OAuth2PasswordBearer
56
+
57
+FastAPI-compatible bearer token extractor:
58
+
59
+```python
60
+from justapi import OAuth2PasswordBearer
61
+
62
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
63
+
64
+@app.get("/users/me")
65
+def read_me(token: str = Security(oauth2_scheme)):
66
+ return {"token": token}
67
+```
68
+
69
+## OAuth2PasswordRequestForm
70
+
71
+Form dependency for token endpoints:
72
+
73
+```python
74
+from justapi import OAuth2PasswordRequestForm
75
+
76
+@app.post("/token")
77
+def login(form: OAuth2PasswordRequestForm):
78
+ # form.username, form.password, form.scope
79
+ return {"access_token": form.username, "token_type": "bearer"}
80
+```
81
+
82
+### Strict Variant
83
+
84
+```python
85
+from justapi import OAuth2PasswordRequestFormStrict
86
+
87
+# Requires grant_type=password in the form
88
+@app.post("/token")
89
+def login(form: OAuth2PasswordRequestFormStrict):
90
+ ...
91
+```
92
+
93
+## Set JWT Auth (Convenience)
94
+
95
+```python
96
+app.set_jwt_auth(secret="my-secret", algorithm="HS256")
97
+```
98
+
99
+This adds Rust-native JWT middleware to all routes.
100
+
101
+## See Also
102
+
103
+- [Security — First Steps](/tutorials/security/first-steps/) — basic auth setup
104
+- [OAuth2 + JWT](/tutorials/security/oauth2-jwt/) — production JWT
105
+- [Advanced Security](/advanced/advanced-security/) — scopes, API keys
0
106
▼docs_site/src/content/docs/api-reference/controller.md
+66-0
1
+---
2
+title: Controller
3
+description: Class-based route controllers in JustAPI — organize related routes in a single class.
4
+keywords: [JustAPI, controller, class-based routes, route decorators]
5
+---
6
+
7
+## Basic Controller
8
+
9
+Use `@controller` and `@route_get/post/etc` to group related routes in a class:
10
+
11
+```python
12
+from justapi import JustAPIApp, Controller, controller, route_get, route_post
13
+
14
+app = JustAPIApp()
15
+
16
+@controller("/users")
17
+class UserController:
18
+ @route_get("/")
19
+ def list_users(self):
20
+ return []
21
+
22
+ @route_get("/{user_id}")
23
+ def get_user(self, user_id: int):
24
+ return {"user_id": user_id}
25
+
26
+ @route_post("/")
27
+ def create_user(self, body: dict):
28
+ return body
29
+
30
+app.include_controller(UserController)
31
+```
32
+
33
+All routes are prefixed with `/users`.
34
+
35
+## Route Decorators
36
+
37
+| Decorator | HTTP Method |
38
+|-----------|-------------|
39
+| `@route_get(path)` | GET |
40
+| `@route_post(path)` | POST |
41
+| `@route_put(path)` | PUT |
42
+| `@route_patch(path)` | PATCH |
43
+| `@route_delete(path)` | DELETE |
44
+| `@route_query(path)` | QUERY (RFC 10008) |
45
+| `@route_sse(path)` | GET (SSE stream) |
46
+| `@route_websocket(path)` | WebSocket |
47
+
48
+## Controller with Dependencies
49
+
50
+```python
51
+from justapi import Depends, Security
52
+
53
+def verify_auth(token: str = Security(oauth2_scheme)):
54
+ return {"user_id": 1}
55
+
56
+@controller("/admin", dependencies=[Depends(verify_auth)])
57
+class AdminController:
58
+ @route_get("/dashboard")
59
+ def dashboard(self):
60
+ return {"message": "admin only"}
61
+```
62
+
63
+## See Also
64
+
65
+- [Routing & Sub-routers](/tutorials/routing-subrouters/) — APIRouter usage
66
+- [APIRouter](/api-reference/apirouter/) — router reference
0
67
▼docs_site/src/content/docs/api-reference/dag.md
+57-0
1
+---
2
+title: Dag and DagNode
3
+description: Execute tasks in a directed acyclic graph (DAG) with dependency resolution in JustAPI.
4
+keywords: [JustAPI, DAG, directed acyclic graph, task orchestration, dependencies]
5
+---
6
+
7
+## Basic Usage
8
+
9
+Define tasks as nodes and let JustAPI execute them in dependency order:
10
+
11
+```python
12
+from justapi import Dag, DagNode
13
+
14
+# Define tasks
15
+def fetch_data():
16
+ return {"raw": [1, 2, 3]}
17
+
18
+def transform(data):
19
+ return {"transformed": [x * 2 for x in data["raw"]]}
20
+
21
+def save(data):
22
+ print(f"Saving: {data}")
23
+ return {"status": "saved"}
24
+
25
+# Build DAG
26
+dag = Dag([
27
+ DagNode("fetch", fetch_data),
28
+ DagNode("transform", transform, dependencies=["fetch"]),
29
+ DagNode("save", save, dependencies=["transform"]),
30
+])
31
+
32
+# Execute
33
+result = dag.execute({"input": "test"})
34
+# Runs: fetch → transform → save
35
+```
36
+
37
+## DagNode
38
+
39
+| Parameter | Type | Description |
40
+|-----------|------|-------------|
41
+| `name` | `str` | Unique node identifier |
42
+| `handler` | `Callable` | Function to execute |
43
+| `dependencies` | `list[str]` | Names of nodes this depends on |
44
+
45
+## Dag.execute
46
+
47
+```python
48
+result = dag.execute(inputs={})
49
+# Returns: asyncio.Future with final state dict
50
+```
51
+
52
+The `inputs` dict is the initial state. Each node's return value is merged into the state under its `name` key. Dependent nodes receive the accumulated state.
53
+
54
+## See Also
55
+
56
+- [Background Tasks](/tutorials/background-tasks/) — post-response tasks
57
+- [Resilience Patterns](/advanced/resilience-patterns/) — error handling in DAGs
0
58
▼docs_site/src/content/docs/api-reference/mailer.md
+86-0
1
+---
2
+title: Mailer
3
+description: Send emails from JustAPI applications — sync, async, and template-based.
4
+keywords: [JustAPI, mailer, email, SMTP, send email, templates]
5
+---
6
+
7
+`Mailer` sends emails over SMTP. It supports synchronous and asynchronous sending, plus Jinja2 templates.
8
+
9
+## Setup
10
+
11
+```python
12
+from justapi import JustAPIApp, Mailer
13
+
14
+app = JustAPIApp()
15
+
16
+mailer = Mailer(
17
+ host="smtp.example.com",
18
+ port=587,
19
+ username="[email protected]",
20
+ password="secret",
21
+ use_tls=True,
22
+ default_from="[email protected]",
23
+ default_from_name="My App",
24
+)
25
+```
26
+
27
+## Send Email
28
+
29
+```python
30
+@app.post("/send")
31
+def send_email():
32
+ mailer.send(
33
34
+ subject="Hello",
35
+ body="This is the plain text body",
36
+ html="<h1>Hello</h1><p>This is the HTML body</p>",
37
+ )
38
+ return {"status": "sent"}
39
+```
40
+
41
+## Async Send
42
+
43
+```python
44
+@app.post("/send-async")
45
+async def send_email_async(background_tasks: BackgroundTasks):
46
+ background_tasks.add_task(
47
+ mailer.send_async,
48
49
+ subject="Hello",
50
+ body="Sent asynchronously",
51
+ )
52
+ return {"status": "queued"}
53
+```
54
+
55
+## Send with Template
56
+
57
+```python
58
+mailer = Mailer(
59
+ host="smtp.example.com",
60
+ port=587,
61
+ templates="templates/email",
62
+)
63
+
64
+@app.post("/send-template")
65
+def send_welcome():
66
+ mailer.send_template(
67
+ template_name="welcome.html",
68
69
+ subject="Welcome!",
70
+ context={"name": "Alice"},
71
+ )
72
+ return {"status": "sent"}
73
+```
74
+
75
+## Parameters
76
+
77
+| Parameter | Type | Default | Description |
78
+|-----------|------|---------|-------------|
79
+| `host` | `str` | — | SMTP server hostname |
80
+| `port` | `int` | `587` | SMTP port |
81
+| `username` | `str` | `None` | Auth username |
82
+| `password` | `str` | `None` | Auth password |
83
+| `use_tls` | `bool` | `True` | Enable TLS |
84
+| `default_from` | `str` | `None` | Default sender email |
85
+| `default_from_name` | `str` | `None` | Default sender name |
86
+| `templates` | `str` | `None` | Directory for Jinja2 email templates |
0
87
▼docs_site/src/content/docs/api-reference/rate-limiter.md
+69-0
1
+---
2
+title: RateLimiter
3
+description: Redis-backed distributed rate limiting with GCRA algorithm in JustAPI.
4
+keywords: [JustAPI, rate limiter, GCRA, Redis, distributed rate limiting]
5
+---
6
+
7
+## Create Rate Limiter
8
+
9
+```python
10
+from justapi import RateLimiter
11
+
12
+# Async — creates Redis connection
13
+limiter = await RateLimiter.new_redis("redis://localhost:6379")
14
+```
15
+
16
+## Check Rate Limit
17
+
18
+```python
19
+result = await limiter.check_limit(
20
+ key="user:123", # unique key per user/client
21
+ capacity=100, # max requests in window
22
+ replenish_rate=10, # tokens added per second
23
+ tokens=1, # tokens to consume
24
+)
25
+
26
+if result.allowed:
27
+ # Process request
28
+ return {"status": "ok"}
29
+else:
30
+ # Rate limited
31
+ return JSONResponse(
32
+ status_code=429,
33
+ content={"error": "Rate limit exceeded"},
34
+ headers={"Retry-After": str(result.retry_after_ms // 1000)},
35
+ )
36
+```
37
+
38
+## Parameters
39
+
40
+| Parameter | Type | Description |
41
+|-----------|------|-------------|
42
+| `key` | `str` | Unique identifier for the rate limit bucket |
43
+| `capacity` | `int` | Maximum burst size |
44
+| `replenish_rate` | `float` | Tokens added per second |
45
+| `tokens` | `int` | Tokens to consume (default: 1) |
46
+
47
+## RateLimitResult
48
+
49
+| Property | Type | Description |
50
+|----------|------|-------------|
51
+| `allowed` | `bool` | Whether the request is allowed |
52
+| `retry_after_ms` | `int` | Milliseconds until next token is available |
53
+
54
+## Built-in Middleware
55
+
56
+JustAPI also provides a built-in rate limiter middleware:
57
+
58
+```python
59
+app.add_middleware(
60
+ RateLimiter,
61
+ capacity=100,
62
+ replenish_rate=10,
63
+)
64
+```
65
+
66
+## See Also
67
+
68
+- [Resilience Patterns](/advanced/resilience-patterns/) — circuit breakers, retry
69
+- [Security](/tutorials/security/first-steps/) — authentication
0
70
▼docs_site/src/content/docs/api-reference/session.md
+50-48
1
1
---
2
-title: Session API (Agent System)
3
-description: "API reference for session management in JustAPI, the FastAPI alternative — Rust-backed agent session state management."
4
-keywords: [session, fastapi alternative, justapi, session management, agent system, state management]
2
+title: Session
3
+description: Manage per-user session state in JustAPI — create, read, update, delete sessions.
4
+keywords: [JustAPI, session, state, agent, user session]
5
5
---
6
6
7
-## `Session` Object
8
-
9
-Sessions provide durable per-client state storage backed by the Rust runtime. Sessions are identified by the `justapi_session` cookie or `?session=` query parameter.
7
+## Enable Sessions
10
8
11
9
```python
12
-from justapi import Session
13
-```
14
-
15
-### Properties & Methods
16
-
17
-| Method | Returns | Description |
18
-|---|---|---|
19
-| `session.id` | `str` | Unique session identifier |
20
-| `session.get()` | `dict` | Get all session data |
21
-| `session.update(**kwargs)` | `None` | Update session data with key-value pairs |
22
-
23
-### Usage
24
-
25
-```python
26
-from justapi import JustAPIApp, Session
10
+from justapi import JustAPIApp
27
11
28
12
app = JustAPIApp()
29
-app.enable_sessions()
30
-
31
-
32
-@app.get("/agent/state")
33
-def agent_state(request, session: Session):
34
- prior = session.get().get("visits", 0)
35
- session.update(visits=prior + 1)
36
- return {
37
- "session_id": session.id,
38
- "visits": prior + 1,
39
- "data": session.get(),
40
- }
13
+app.enable_sessions(cookie_name="session_id")
41
14
```
42
15
43
-## Enabling Sessions
16
+## Session Methods
17
+
18
+| Method | Description |
19
+|--------|-------------|
20
+| `app.create_session(data, id=None)` | Create a new session |
21
+| `app.get_session(id)` | Get session data by ID |
22
+| `app.set_session(id, data)` | Replace session data |
23
+| `app.update_session(id, **fields)` | Shallow merge fields |
24
+| `app.delete_session(id)` | Delete a session |
25
+
26
+## Example
44
27
45
28
```python
46
-app.enable_sessions()
29
+@app.post("/sessions")
30
+def create_session():
31
+ session_id = app.create_session({"user_id": 1, "role": "admin"})
32
+ return {"session_id": session_id}
33
+
34
+@app.get("/sessions/{session_id}")
35
+def get_session(session_id: str):
36
+ data = app.get_session(session_id)
37
+ if data is None:
38
+ raise HTTPException(status_code=404, detail="Session not found")
39
+ return data
40
+
41
+@app.patch("/sessions/{session_id}")
42
+def update_session(session_id: str, fields: dict):
43
+ app.update_session(session_id, **fields)
44
+ return {"status": "updated"}
45
+
46
+@app.delete("/sessions/{session_id}")
47
+def delete_session(session_id: str):
48
+ app.delete_session(session_id)
49
+ return {"status": "deleted"}
47
50
```
48
51
49
-This activates the Rust-backed session store. Sessions are:
50
-- Created on first use (new session ID generated)
51
-- Persisted across requests via cookie or query param
52
-- Available in any handler by declaring `session: Session`
52
+## Agent Sessions
53
53
54
-## System Routes
54
+Sessions are the foundation for the agent system. The `Session` class wraps session state with a typed interface:
55
55
56
56
```python
57
-app.enable_system_routes()
58
-```
57
+from justapi import Session
59
58
60
-Enables:
61
-- `GET /_system/tools` — List registered MCP tools
62
-- `POST /_system/tools/call` — Call an MCP tool
59
+@app.get("/agent")
60
+def agent_endpoint(request):
61
+ session = Session(request)
62
+ session.set("context", {"topic": "weather"})
63
+ context = session.get("context")
64
+ return {"context": context}
65
+```
63
66
64
67
## See Also
65
68
66
-- [Agent System Guide](/advanced/agent-system/) — Deep dive into agent-native features
67
-- [Dependency Injection API](/api-reference/dependency-injection/) — Session in DI
68
-- [JustAPIApp](/api-reference/justapiapp/) — enable_sessions() and enable_system_routes()
69
+- [Agent System](/advanced/agent-system/) — MCP tools and agent sessions
70
+- [Tool Registration](/advanced/agent-system/) — registering tools
69
71
▼docs_site/src/content/docs/api-reference/system-routes.md
+75-0
1
+---
2
+title: System Routes
3
+description: Built-in introspection routes in JustAPI — help, tools, OpenAPI, health checks.
4
+keywords: [JustAPI, system routes, health check, help, introspection, tools]
5
+---
6
+
7
+## Built-in Routes
8
+
9
+JustAPI registers these routes automatically:
10
+
11
+| Route | Method | Auth | Description |
12
+|-------|--------|------|-------------|
13
+| `/health` | GET | No | Health check (always returns 200) |
14
+| `/live` | GET | No | Liveness probe |
15
+| `/ready` | GET | No | Readiness probe (checks registered health checks) |
16
+| `/metrics` | GET | Yes | Prometheus metrics |
17
+| `/openapi.json` | GET | Yes | OpenAPI 3.1 schema |
18
+| `/docs` | GET | Yes | Swagger UI |
19
+| `/redoc` | GET | Yes | ReDoc |
20
+| `/scalar` | GET | Yes | Scalar API Reference |
21
+
22
+## Enable System Routes
23
+
24
+```python
25
+app = JustAPIApp(admin_token="my-secret-token")
26
+app.enable_system_routes()
27
+```
28
+
29
+This mounts additional introspection routes:
30
+
31
+| Route | Description |
32
+|-------|-------------|
33
+| `/_system/help` | Rich route descriptors (AI-friendly) |
34
+| `/_system/help/{name}` | Detailed help for one route |
35
+| `/_system/tools` | List registered MCP tools |
36
+| `/_system/tools/call` | Invoke a tool by name |
37
+
38
+## Register Health Checks
39
+
40
+```python
41
+def check_db():
42
+ db = app.db
43
+ if db.health():
44
+ return True
45
+ raise Exception("Database unreachable")
46
+
47
+def check_redis():
48
+ # Check Redis connection
49
+ return True
50
+
51
+app.register_health_check("database", check_db)
52
+app.register_health_check("redis", check_redis)
53
+```
54
+
55
+`GET /ready` returns 200 only if all health checks pass.
56
+
57
+## Admin Token Protection
58
+
59
+Routes like `/metrics`, `/openapi.json`, `/docs` are protected by `admin_token`:
60
+
61
+```python
62
+app = JustAPIApp(admin_token="my-secret-token")
63
+```
64
+
65
+Include the token in requests:
66
+
67
+```bash
68
+curl -H "Authorization: Bearer my-secret-token" http://localhost:8000/metrics
69
+```
70
+
71
+## See Also
72
+
73
+- [Health Checks](/observability/health-checks/) — monitoring
74
+- [Metrics & Monitoring](/observability/metrics-monitoring/) — Prometheus
75
+- [Configuration Reference](/reference/configuration/) — all options
0
76
▼docs_site/src/content/docs/reference/environment-variables.md
+43-0
1
+---
2
+title: Environment Variables
3
+description: Environment variables that control JustAPI behavior — profiling, tracing, background tasks, testing.
4
+keywords: [JustAPI, environment variables, configuration, profiling, tracing]
5
+---
6
+
7
+JustAPI reads these environment variables at startup:
8
+
9
+## Runtime
10
+
11
+| Variable | Default | Description |
12
+|----------|---------|-------------|
13
+| `JUSTAPI_BG_MAX_QUEUE` | `100000` | Maximum number of queued background tasks |
14
+| `JUSTAPI_ENABLE_TRACE` | `false` | Inject OpenTelemetry trace context into Python contextvars |
15
+| `JUSTAPI_PROFILE` | `false` | Enable GIL-path profiler for performance analysis |
16
+
17
+## Testing
18
+
19
+| Variable | Default | Description |
20
+|----------|---------|-------------|
21
+| `SNAPSHOT_UPDATE` | `false` | Update snapshot files when set to `1`, `true`, or `yes` |
22
+
23
+## Examples
24
+
25
+```bash
26
+# Enable tracing in production
27
+JUSTAPI_ENABLE_TRACE=true python main.py
28
+
29
+# Increase background task queue
30
+JUSTAPI_BG_MAX_QUEUE=500000 python main.py
31
+
32
+# Profile GIL contention
33
+JUSTAPI_PROFILE=true python main.py
34
+
35
+# Update test snapshots
36
+SNAPSHOT_UPDATE=1 pytest tests/
37
+```
38
+
39
+## See Also
40
+
41
+- [Configuration Reference](/reference/configuration/) — all config options
42
+- [OpenTelemetry](/observability/opentelemetry/) — tracing setup
43
+- [Background Tasks](/tutorials/background-tasks/) — task execution
0
44
▼docs_site/src/content/docs/tutorials/frontend.md
+63-0
1
+---
2
+title: Frontend
3
+description: Serve a static SPA frontend from JustAPI — React, Vue, Next.js, or any static build.
4
+keywords: [JustAPI, frontend, SPA, static files, React, Vue, Next.js]
5
+---
6
+
7
+## Serve a Frontend
8
+
9
+Mount a static directory as an SPA:
10
+
11
+```python
12
+from justapi import JustAPIApp
13
+
14
+app = JustAPIApp()
15
+
16
+@app.frontend("/", directory="dist")
17
+def serve_frontend():
18
+ pass
19
+```
20
+
21
+This serves `dist/` at `/` and handles client-side routing (any path not matching an API route returns `index.html`).
22
+
23
+## With API Routes
24
+
25
+```python
26
+app = JustAPIApp()
27
+
28
+@app.get("/api/hello")
29
+def hello():
30
+ return {"message": "Hello from API"}
31
+
32
+# Must come AFTER API routes
33
+@app.frontend("/", directory="dist")
34
+def serve_frontend():
35
+ pass
36
+```
37
+
38
+:::note
39
+Place `frontend()` after all API routes so API paths take priority.
40
+:::
41
+
42
+## Parameters
43
+
44
+| Parameter | Type | Default | Description |
45
+|-----------|------|---------|-------------|
46
+| `path` | `str` | — | Mount path |
47
+| `directory` | `str` | — | Static file directory |
48
+| `html` | `str` | `None` | Custom index HTML (overrides file) |
49
+| `check_dir` | `bool` | `True` | Raise error if directory doesn't exist |
50
+
51
+## Custom HTML
52
+
53
+```python
54
+@app.frontend("/", html="<h1>My App</h1>")
55
+def serve():
56
+ pass
57
+```
58
+
59
+## See Also
60
+
61
+- [Static Files](/tutorials/static-files/) — serving CSS/JS/images
62
+- [Sub Applications — Mounts](/advanced/sub-applications/) — mounting sub-apps
63
+- [Behind a Proxy](/advanced/behind-a-proxy/) — reverse proxy config
0
64
▼docs_site/src/content/docs/tutorials/stream-json-lines.md
+53-0
1
+---
2
+title: Stream JSON Lines
3
+description: Stream validated JSON responses line-by-line in JustAPI with stream_json.
4
+keywords: [JustAPI, stream JSON, SSE, streaming, NDJSON, validated streaming]
5
+---
6
+
7
+## Basic Streaming
8
+
9
+Use `stream_json` to send validated JSON lines:
10
+
11
+```python
12
+from justapi import JustAPIApp, Schema
13
+from typing import AsyncGenerator
14
+
15
+class Token(Schema):
16
+ text: str
17
+ index: int
18
+
19
+app = JustAPIApp()
20
+
21
+@app.stream_json("/stream", schema=Token, mode="ndjson")
22
+async def stream_tokens():
23
+ for i, word in enumerate(["Hello", "world", "from", "JustAPI"]):
24
+ yield Token(text=word, index=i)
25
+```
26
+
27
+Each line is validated against the schema before sending.
28
+
29
+## Mode
30
+
31
+| Mode | Format | Content-Type |
32
+|------|--------|-------------|
33
+| `"ndjson"` | One JSON object per line | `application/x-ndjson` |
34
+| `"sse"` | Server-Sent Events format | `text/event-stream` |
35
+
36
+## With Dependencies
37
+
38
+```python
39
+@app.stream_json(
40
+ "/stream",
41
+ schema=Token,
42
+ dependencies=[Depends(get_current_user)],
43
+)
44
+async def stream_tokens(user: dict):
45
+ for i, word in enumerate(["Hello", "world"]):
46
+ yield Token(text=word, index=i)
47
+```
48
+
49
+## See Also
50
+
51
+- [Streaming Output](/advanced/streaming-output/) — advanced streaming
52
+- [Server-Sent Events](/tutorials/websockets-sse/) — SSE basics
53
+- [Validated Streaming](/advanced/streaming-output/) — schema validation
0
54
▼docs_site/src/content/docs/tutorials/testing-utilities.md
+91-0
1
+---
2
+title: Testing Utilities
3
+description: Advanced testing tools — AsyncTestClient, ManagedDb, Snapshot testing, and assertion helpers.
4
+keywords: [JustAPI, testing, AsyncTestClient, ManagedDb, Snapshot, assertions]
5
+---
6
+
7
+## AsyncTestClient
8
+
9
+For async tests with pytest-asyncio:
10
+
11
+```python
12
+import pytest
13
+from justapi.testing import AsyncTestClient
14
+from myapp import app
15
+
16
+@pytest.fixture
17
+async def client():
18
+ async with AsyncTestClient(app) as c:
19
+ yield c
20
+
21
+@pytest.mark.anyio
22
+async def test_hello(client):
23
+ response = await client.get("/hello")
24
+ assert response["status"] == 200
25
+```
26
+
27
+## ManagedDb
28
+
29
+Test database with automatic cleanup:
30
+
31
+```python
32
+from justapi.testing import ManagedDb
33
+
34
+db = ManagedDb("sqlite:///test.db", migrations_dir="migrations/")
35
+
36
+async def setup():
37
+ await db.run_migrations()
38
+
39
+async def teardown():
40
+ await db.run_sql("DELETE FROM users")
41
+```
42
+
43
+## db_client
44
+
45
+Create a test client with a managed database:
46
+
47
+```python
48
+from justapi.testing import db_client
49
+
50
+async def test_with_db():
51
+ async with db_client(app, "sqlite:///test.db") as client:
52
+ response = await client.get("/users")
53
+ assert response["status"] == 200
54
+```
55
+
56
+## Snapshot Testing
57
+
58
+Assert responses match saved snapshots:
59
+
60
+```python
61
+from justapi.testing import Snapshot
62
+
63
+snapshot = Snapshot()
64
+
65
+def test_api_response():
66
+ response = client.get("/items")
67
+ snapshot.assert_response(response, "items_list")
68
+ # First run: saves snapshot
69
+ # Subsequent runs: compares against saved snapshot
70
+```
71
+
72
+Update snapshots by setting `SNAPSHOT_UPDATE=1`.
73
+
74
+## Assertion Helpers
75
+
76
+```python
77
+from justapi.testing import assert_ok, assert_status, assert_json, assert_header
78
+
79
+def test_helpers():
80
+ response = client.get("/hello")
81
+
82
+ assert_ok(response) # status == 200
83
+ assert_status(response, 201) # status == 201
84
+ assert_json(response, {"message": "Hello"}) # JSON body matches
85
+ assert_header(response, "X-Custom", "value") # header exists with value
86
+```
87
+
88
+## See Also
89
+
90
+- [Testing](/tutorials/testing/) — basic testing with JustAPITestClient
91
+- [Testing a Database](/how-to/testing-database/) — database testing patterns
0
92