Local API Server
Local API Server
Section titled “Local API Server”Run your Jac walkers as a production-ready HTTP API server.
Prerequisites
- Completed: Your First App
- Time: ~15 minutes
Overview
Section titled “Overview”The jac start command turns your walkers into REST API endpoints automatically:
graph LR Client["Client<br/>(Browser, Mobile)"] -- "HTTP" --> Server["jac start<br/>Server"] Server -- "JSON" --> ClientQuick Start
Section titled “Quick Start”1. Create Your Walker
Section titled “1. Create Your Walker”# app.jacnode Task { has id: int; has title: str; has done: bool = False;}
walker:pub get_tasks { can fetch with Root entry { tasks = [-->](?:Task); report [{"id": t.id, "title": t.title, "done": t.done} for t in tasks]; }}
walker:pub add_task { has title: str;
can create with Root entry { import random; task = Task(id=random.randint(1, 10000), title=self.title); root ++> task; report {"id": task.id, "title": task.title, "done": task.done}; }}Note: The
:pubmodifier makes walkers publicly accessible without authentication. Without it, API endpoints require authentication tokens.
2. Start the Server
Section titled “2. Start the Server”jac start app.jacOutput:
INFO: Started server process [12345]INFO: Waiting for application startup.INFO: Application startup complete.INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)3. Call the API
Section titled “3. Call the API”# Get all taskscurl http://localhost:8000/get_tasks
# Add a taskcurl -X POST http://localhost:8000/add_task \ -H "Content-Type: application/json" \ -d '{"title": "Buy groceries"}'Server Configuration
Section titled “Server Configuration”# Custom portjac start app.jac --port 3000Development Mode (HMR)
Section titled “Development Mode (HMR)”Hot Module Replacement for development:
jac start app.jac --devChanges to your .jac files will automatically reload.
API-Only Mode
Section titled “API-Only Mode”Skip client bundling and only serve the API:
jac start app.jac --dev --no-clientAPI Endpoints
Section titled “API Endpoints”Automatic Endpoint Generation
Section titled “Automatic Endpoint Generation”Each public walker becomes an endpoint:
| Walker | HTTP Method | Endpoint |
|---|---|---|
walker:pub get_users { } | POST | /get_users |
walker:pub create_user { } | POST | /create_user |
walker:pub delete_user { } | POST | /delete_user |
Request Format
Section titled “Request Format”Walker parameters become request body:
walker:pub search_users { has query: str; has limit: int = 10; has page: int = 1;}curl -X POST http://localhost:8000/search_users \ -H "Content-Type: application/json" \ -d '{"query": "john", "limit": 20, "page": 1}'Response Format
Section titled “Response Format”Walker report values become the response:
walker:pub get_user { has user_id: str;
can fetch with Root entry { for user in [-->](?:User) { if user.id == self.user_id { report { "id": user.id, "name": user.name, "email": user.email }; return; } } report {"error": "User not found"}; }}Response:
{ "id": "123", "name": "Alice", "email": "alice@example.com"}Interactive Documentation
Section titled “Interactive Documentation”jac start automatically generates Swagger/OpenAPI docs:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - OpenAPI JSON:
http://localhost:8000/openapi.json
Database Persistence
Section titled “Database Persistence”By default, Jac uses SQLite for persistence (you’ll see “Using SQLite for persistence” when starting).
Custom Persistence
Section titled “Custom Persistence”import json;
walker save_state { can save with Root entry { data = { "users": [u.__dict__ for u in [-->](?:User)], "posts": [p.__dict__ for p in [-->](?:Post)] };
with open("state.json", "w") as f { json.dump(data, f); }
report {"saved": True}; }}
walker load_state { can load with Root entry { try { with open("state.json", "r") as f { data = json.load(f); }
for u in data["users"] { root ++> User(**u); }
report {"loaded": True}; } except FileNotFoundError { report {"loaded": False, "reason": "No saved state"}; } }}Environment Variables
Section titled “Environment Variables”Accessing Environment in Code
Section titled “Accessing Environment in Code”import os;
walker get_config { can fetch with Root entry { report { "database_url": os.getenv("DATABASE_URL", "sqlite:///default.db"), "api_key": os.getenv("API_KEY"), "debug": os.getenv("DEBUG", "false") == "true" }; }}Middleware and Hooks
Section titled “Middleware and Hooks”Request Logging
Section titled “Request Logging”walker _before_request { has request: dict;
can log with Root entry { print(f"Request: {self.request['method']} {self.request['path']}"); }}Authentication Middleware
Section titled “Authentication Middleware”walker _authenticate { has headers: dict;
can check with Root entry { token = self.headers.get("Authorization", "");
if not token.startswith("Bearer ") { report {"error": "Unauthorized", "status": 401}; return; }
# Validate token... report {"authenticated": True}; }}Health Checks
Section titled “Health Checks”Liveness Probe
Section titled “Liveness Probe”walker:pub health { can check with Root entry { report {"status": "healthy"}; }}curl http://localhost:8000/health# {"status": "healthy"}Readiness Probe
Section titled “Readiness Probe”walker:pub ready { can check with Root entry { # Check dependencies db_ok = check_database(); cache_ok = check_cache();
if db_ok and cache_ok { report {"status": "ready"}; } else { report {"status": "not_ready", "db": db_ok, "cache": cache_ok}; } }}CLI Options Reference
Section titled “CLI Options Reference”| Option | Description | Default |
|---|---|---|
--port, -p | Server port | 8000 |
--dev, -d | Enable Hot Module Replacement | false |
--no-client, -n | Skip client bundling (API only) | false |
--faux, -f | Print API docs only (no server) | false |
--scale | Deploy to Kubernetes (requires jac-scale) | false |
Example: Full API
Section titled “Example: Full API”# api.jacimport from datetime { datetime }import uuid;
node User { has id: str; has name: str; has email: str; has created_at: str;}
# List all userswalker:pub list_users { can fetch with Root entry { users = [-->](?:User); report [{ "id": u.id, "name": u.name, "email": u.email } for u in users]; }}
# Get single userwalker:pub get_user { has user_id: str;
can fetch with Root entry { for u in [-->](?:User) { if u.id == self.user_id { report { "id": u.id, "name": u.name, "email": u.email, "created_at": u.created_at }; return; } } report {"error": "Not found"}; }}
# Create userwalker:pub create_user { has name: str; has email: str;
can create with Root entry { user = User( id=str(uuid.uuid4()), name=self.name, email=self.email, created_at=datetime.now().isoformat() ); root ++> user; report {"id": user.id, "name": user.name, "email": user.email}; }}
# Update userwalker:pub update_user { has user_id: str; has name: str = ""; has email: str = "";
can update with Root entry { for u in [-->](?:User) { if u.id == self.user_id { if self.name { u.name = self.name; } if self.email { u.email = self.email; } report {"id": u.id, "name": u.name, "email": u.email}; return; } } report {"error": "Not found"}; }}
# Delete userwalker:pub delete_user { has user_id: str;
can remove with Root entry { for u in [-->](?:User) { if u.id == self.user_id { del u; report {"deleted": True}; return; } } report {"error": "Not found"}; }}
# Health checkwalker:pub health { can check with Root entry { report {"status": "ok", "timestamp": datetime.now().isoformat()}; }}Run it:
jac start api.jac --port 8000 --devTest it:
# Create usercurl -X POST http://localhost:8000/create_user \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "alice@example.com"}'
# List userscurl http://localhost:8000/list_users
# Health checkcurl http://localhost:8000/healthNext Steps
Section titled “Next Steps”- Kubernetes Deployment - Scale with jac-scale
- Authentication - Add user login