# Moose / App Api Frameworks Documentation – Python ## Included Files 1. moose/app-api-frameworks/express.mdx 2. moose/app-api-frameworks/fastapi.mdx 3. moose/app-api-frameworks/typescript-fastify.mdx 4. moose/app-api-frameworks/typescript-koa.mdx 5. moose/app-api-frameworks/typescript-raw-nodejs.mdx ## Express with MooseStack Source: moose/app-api-frameworks/express.mdx Use Express framework with MooseStack # Express with MooseStack Mount Express applications within your MooseStack project using the `WebApp` class. Express is the most popular Node.js web framework with a rich ecosystem of middleware. - Already run Express elsewhere? Keep it outside your MooseStack project and query data with the MooseStack client. The [Querying Data guide](/moose/olap/read-data) shows how to use the SDK. - Want to mount Express in your MooseStack project? Follow the steps below with `WebApp` for unified deployment and access to MooseStack utilities. ## Basic Example ```ts filename="app/apis/expressApp.ts" copy const app = express(); app.use(express.json()); app.use(expressMiddleware()); // Required for Express app.get("/health", (req, res) => { res.json({ status: "ok" }); }); app.get("/data", async (req, res) => { const moose = getMooseUtils(req); if (!moose) { return res.status(500).json({ error: "Moose utilities not available" }); } const { client, sql } = moose; const limit = parseInt(req.query.limit as string) || 10; try { const query = sql` SELECT ${MyTable.columns.id}, ${MyTable.columns.name}, ${MyTable.columns.createdAt} FROM ${MyTable} ORDER BY ${MyTable.columns.createdAt} DESC LIMIT ${limit} `; const result = await client.query.execute(query); const data = await result.json(); res.json(data); } catch (error) { res.status(500).json({ error: String(error) }); } }); ); ``` **Access your API:** - `GET http://localhost:4000/express/health` - `GET http://localhost:4000/express/data?limit=20` Express applications must use `expressMiddleware()` to access Moose utilities: ```ts app.use(expressMiddleware()); ``` This middleware injects MooseStack utilities into the request object. ## Complete Example with Features ```ts filename="app/apis/advancedExpressApp.ts" copy const app = express(); // Middleware setup app.use(express.json()); app.use(expressMiddleware()); // Required! // Custom logging middleware app.use((req: Request, res: Response, next: NextFunction) => { console.log(`${req.method} ${req.path}`); next(); }); // Error handling middleware const asyncHandler = (fn: Function) => (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); }; // Health check endpoint app.get("/health", (req, res) => { res.json({ status: "ok", timestamp: new Date().toISOString() }); }); // GET endpoint with query parameters app.get("/users/:userId/events", asyncHandler(async (req: Request, res: Response) => { const moose = getMooseUtils(req); if (!moose) { return res.status(500).json({ error: "Moose utilities not available" }); } const { client, sql } = moose; const { userId } = req.params; const limit = parseInt(req.query.limit as string) || 10; const eventType = req.query.eventType as string; const cols = UserEvents.columns; const query = sql` SELECT ${cols.id}, ${cols.event_type}, ${cols.timestamp} FROM ${UserEvents} WHERE ${cols.user_id} = ${userId} ${eventType ? sql`AND ${cols.event_type} = ${eventType}` : sql``} ORDER BY ${cols.timestamp} DESC LIMIT ${limit} `; const result = await client.query.execute(query); const events = await result.json(); res.json({ userId, count: events.length, events }); })); // POST endpoint app.post("/users/:userId/profile", asyncHandler(async (req: Request, res: Response) => { const moose = getMooseUtils(req); if (!moose) { return res.status(500).json({ error: "Moose utilities not available" }); } const { userId } = req.params; const { name, email } = req.body; // Validation if (!name || !email) { return res.status(400).json({ error: "Name and email are required" }); } // Handle POST logic here res.json({ success: true, userId, profile: { name, email } }); })); // Protected endpoint with JWT app.get("/protected", asyncHandler(async (req: Request, res: Response) => { const moose = getMooseUtils(req); if (!moose?.jwt) { return res.status(401).json({ error: "Unauthorized" }); } const userId = moose.jwt.sub; res.json({ message: "Authenticated", userId, claims: moose.jwt }); })); // Error handling app.use((err: Error, req: Request, res: Response, next: NextFunction) => { console.error(err); res.status(500).json({ error: "Internal Server Error", message: err.message }); }); // Register as WebApp ); ``` ## WebApp Configuration ```ts new WebApp(name, app, config) ``` **Parameters:** - `name` (string): Unique identifier for your WebApp - `app`: Your Express application instance - `config` (WebAppConfig): Configuration object **WebAppConfig:** ```ts interface WebAppConfig { mountPath: string; // Required: URL path (e.g., "/api/v1") metadata?: { description?: string }; // Optional: Documentation metadata injectMooseUtils?: boolean; // Optional: Inject utilities (default: true) } ``` ## Accessing Moose Utilities ```ts app.get("/data", async (req, res) => { const moose = getMooseUtils(req); if (!moose) { return res.status(500).json({ error: "Utilities not available" }); } const { client, sql, jwt } = moose; // Use client and sql for database queries }); ``` **Available utilities:** - `client`: MooseClient for database queries - `sql`: Template tag for safe SQL queries - `jwt`: Parsed JWT payload (when authentication is configured) ## Middleware Integration Express middleware works seamlessly with MooseStack: ```ts const app = express(); // Security app.use(helmet()); // Compression app.use(compression()); // Rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs }); app.use(limiter); // Body parsing app.use(express.json()); // Moose utilities (must be after body parsing) app.use(expressMiddleware()); ``` ## Router Pattern Organize routes using Express Router: ```ts filename="app/apis/routers/usersRouter.ts" ); ``` ```ts filename="app/apis/mainApp.ts" const app = express(); app.use(express.json()); app.use(expressMiddleware()); // Mount routers app.use("/users", usersRouter); ); ``` ## Authentication with JWT ```ts app.get("/protected", async (req, res) => { const moose = getMooseUtils(req); if (!moose?.jwt) { return res.status(401).json({ error: "Unauthorized" }); } const userId = moose.jwt.sub; const userRole = moose.jwt.role; // Check permissions if (userRole !== "admin") { return res.status(403).json({ error: "Forbidden" }); } res.json({ message: "Authenticated", userId }); }); ``` See [Authentication documentation](/moose/apis/auth) for JWT configuration. ## Best Practices 1. **Always use expressMiddleware()**: Required for accessing Moose utilities 2. **Check for moose utilities**: Always verify `getMooseUtils(req)` returns a value 3. **Use async error handling**: Wrap async routes with error handler 4. **Organize with routers**: Split large applications into multiple routers 5. **Apply middleware in order**: Body parsing before expressMiddleware 6. **Use TypeScript types**: Import Request, Response types from Express 7. **Handle errors globally**: Use Express error handling middleware ## Troubleshooting ### "Moose utilities not available" **Solution:** Ensure `expressMiddleware()` is added after body parsing: ```ts app.use(express.json()); app.use(expressMiddleware()); // Must come after body parsers ``` ### TypeScript errors with getMooseUtils **Solution:** The utilities may be undefined, always check: ```ts const moose = getMooseUtils(req); if (!moose) { return res.status(500).json({ error: "Utilities not available" }); } // Now moose.client, moose.sql are safely accessible ``` ### Mount path conflicts **Solution:** Ensure your mount path doesn't conflict with reserved paths: - Avoid: `/api`, `/admin`, `/consumption`, `/health`, `/ingest`, `/mcp` - Use: `/myapi`, `/v1`, `/custom` --- ## FastAPI with MooseStack Source: moose/app-api-frameworks/fastapi.mdx Use FastAPI framework with MooseStack # FastAPI with MooseStack Mount FastAPI applications within your MooseStack project using the `WebApp` class. FastAPI is a modern, fast Python framework with automatic API documentation and async support. - Already operating FastAPI outside MooseStack? Keep it separate and call MooseStack data with the client SDK. The [Querying Data guide](/moose/olap/read-data) includes Python examples. - Want to mount FastAPI in your MooseStack project? Use the `WebApp` flow below for unified deployment and access to MooseStack utilities. ## Basic Example ```python filename="app/apis/fastapi_app.py" copy from fastapi import FastAPI, Request, HTTPException from moose_lib.dmv2 import WebApp, WebAppConfig, WebAppMetadata from moose_lib.dmv2.web_app_helpers import get_moose_utils from app.tables.my_table import MyTable app = FastAPI() @app.get("/health") async def health(): return {"status": "ok"} @app.get("/data") async def get_data(request: Request, limit: int = 10): moose = get_moose_utils(request) if not moose: raise HTTPException( status_code=500, detail="Moose utilities not available" ) try: query = f""" SELECT {MyTable.columns.id}, {MyTable.columns.name}, {MyTable.columns.created_at} FROM {MyTable} ORDER BY {MyTable.columns.created_at} DESC LIMIT {{limit}} """ result = moose.client.query.execute_raw(query, { "limit": limit }) return {"success": True, "data": result} except Exception as error: raise HTTPException(status_code=500, detail=str(error)) # Register as WebApp fastapi_app = WebApp( "fastApiApp", app, WebAppConfig( mount_path="/fastapi", metadata=WebAppMetadata(description="FastAPI application") ) ) ``` **Access your API:** - `GET http://localhost:4000/fastapi/health` - `GET http://localhost:4000/fastapi/data?limit=20` ## Complete Example with Features ```python filename="app/apis/advanced_fastapi_app.py" copy from fastapi import FastAPI, Request, HTTPException, Depends, BackgroundTasks from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware from moose_lib.dmv2 import WebApp, WebAppConfig, WebAppMetadata from moose_lib.dmv2.web_app_helpers import get_moose_utils, ApiUtil from app.tables.user_events import UserEvents from app.tables.user_profile import UserProfile from pydantic import BaseModel, Field from datetime import datetime from typing import Optional app = FastAPI( title="Advanced API", description="Advanced FastAPI application with MooseStack", version="1.0.0" ) # CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Custom middleware @app.middleware("http") async def log_requests(request: Request, call_next): start_time = datetime.now() response = await call_next(request) duration = (datetime.now() - start_time).total_seconds() print(f"{request.method} {request.url.path} - {duration:.3f}s") return response # Request/Response models class EventQuery(BaseModel): limit: int = Field(10, ge=1, le=100) event_type: Optional[str] = None class EventData(BaseModel): event_type: str = Field(..., min_length=1) data: dict class EventResponse(BaseModel): id: str event_type: str timestamp: datetime # Health check @app.get("/health") async def health(): return { "status": "ok", "timestamp": datetime.now().isoformat() } # GET with path and query parameters @app.get("/users/{user_id}/events", response_model=dict) async def get_user_events( request: Request, user_id: str, limit: int = 10, event_type: Optional[str] = None ): moose = get_moose_utils(request) if not moose: raise HTTPException(status_code=500, detail="Moose utilities not available") query = """ SELECT id, event_type, timestamp FROM {table} WHERE user_id = {user_id} {event_filter} ORDER BY timestamp DESC LIMIT {limit} """ event_filter = "AND event_type = {event_type}" if event_type else "" params = { "table": UserEvents, "user_id": user_id, "limit": limit } if event_type: params["event_type"] = event_type try: result = moose.client.query.execute( query.format(event_filter=event_filter), params ) return { "user_id": user_id, "count": len(result), "events": result } except Exception as error: raise HTTPException(status_code=500, detail=str(error)) # POST with validated body @app.post("/users/{user_id}/events", status_code=201) async def create_event( request: Request, user_id: str, body: EventData, background_tasks: BackgroundTasks ): moose = get_moose_utils(request) if not moose: raise HTTPException(status_code=500, detail="Moose utilities not available") # Background task def log_event_creation(user_id: str, event_type: str): print(f"Event created: {user_id} - {event_type}") background_tasks.add_task(log_event_creation, user_id, body.event_type) return { "success": True, "user_id": user_id, "event_type": body.event_type, "data": body.data } # Protected endpoint with dependency injection async def require_auth(request: Request) -> ApiUtil: moose = get_moose_utils(request) if not moose or not moose.jwt: raise HTTPException(status_code=401, detail="Unauthorized") return moose @app.get("/protected") async def protected(moose: ApiUtil = Depends(require_auth)): return { "message": "Authenticated", "user": moose.jwt.get("sub"), "claims": moose.jwt } # Admin endpoint with role check async def require_admin(moose: ApiUtil = Depends(require_auth)) -> ApiUtil: role = moose.jwt.get("role") if role != "admin": raise HTTPException(status_code=403, detail="Forbidden") return moose @app.get("/admin/stats") async def admin_stats(moose: ApiUtil = Depends(require_admin)): return {"message": "Admin access granted"} # WebSocket support from fastapi import WebSocket @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() try: while True: data = await websocket.receive_text() await websocket.send_text(f"Echo: {data}") except Exception: await websocket.close() # Global exception handler @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): return JSONResponse( status_code=500, content={"error": "Internal Server Error", "message": str(exc)} ) # Register as WebApp fastapi_app = WebApp( "advancedFastApi", app, WebAppConfig( mount_path="/api/v1", metadata=WebAppMetadata( description="Advanced FastAPI application with middleware and dependencies" ) ) ) ``` ## WebApp Configuration ```python WebApp(name, app, config) ``` **Parameters:** - `name` (str): Unique identifier for your WebApp - `app` (FastAPI): Your FastAPI application instance - `config` (WebAppConfig): Configuration object **WebAppConfig:** ```python @dataclass class WebAppConfig: mount_path: str # Required: URL path metadata: Optional[WebAppMetadata] = None # Optional: Documentation inject_moose_utils: bool = True # Optional: Inject utilities @dataclass class WebAppMetadata: description: Optional[str] = None ``` ## Accessing Moose Utilities ### Direct Access ```python from moose_lib.dmv2.web_app_helpers import get_moose_utils @app.get("/data") async def get_data(request: Request): moose = get_moose_utils(request) if not moose: raise HTTPException(status_code=500, detail="Utilities not available") client = moose.client jwt = moose.jwt ``` ### Dependency Injection Use FastAPI's dependency injection for cleaner code: ```python from moose_lib.dmv2.web_app_helpers import get_moose_dependency, ApiUtil from fastapi import Depends @app.get("/data") async def get_data(moose: ApiUtil = Depends(get_moose_dependency())): # moose is automatically injected and guaranteed to exist result = moose.client.query.execute(...) return result ``` ## Middleware FastAPI middleware works seamlessly: ```python from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware from starlette.middleware.sessions import SessionMiddleware app = FastAPI() # CORS app.add_middleware( CORSMiddleware, allow_origins=["https://example.com"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Compression app.add_middleware(GZipMiddleware, minimum_size=1000) # Sessions app.add_middleware(SessionMiddleware, secret_key="your-secret-key") # Custom middleware @app.middleware("http") async def add_custom_header(request: Request, call_next): response = await call_next(request) response.headers["X-Custom-Header"] = "Value" return response ``` ## Dependency Patterns ### Reusable Dependencies ```python from fastapi import Depends, HTTPException from typing import Optional # Auth dependency async def get_current_user(moose: ApiUtil = Depends(get_moose_dependency())) -> dict: if not moose.jwt: raise HTTPException(status_code=401, detail="Not authenticated") user_id = moose.jwt.get("sub") # Fetch user from database return {"id": user_id} # Admin dependency async def require_admin_user(user: dict = Depends(get_current_user)) -> dict: if user.get("role") != "admin": raise HTTPException(status_code=403, detail="Not authorized") return user # Use dependencies @app.get("/user/profile") async def get_profile(user: dict = Depends(get_current_user)): return user @app.get("/admin/dashboard") async def admin_dashboard(user: dict = Depends(require_admin_user)): return {"message": f"Welcome admin {user['id']}"} ``` ### Dependency Classes ```python from fastapi import Depends from app.tables.user_table import UserTable class Pagination: def __init__(self, page: int = 1, size: int = 10): self.page = page self.size = size self.skip = (page - 1) * size @app.get("/users") async def list_users( pagination: Pagination = Depends(), moose: ApiUtil = Depends(get_moose_dependency()) ): # Using parameterized query for user input values query = f""" SELECT {UserTable.columns.id}, {UserTable.columns.name}, {UserTable.columns.email} FROM {UserTable} WHERE {UserTable.columns.status} = 'active' LIMIT {{size:UInt32}} OFFSET {{skip:UInt32}} """ result = moose.client.query.execute_raw(query, { "size": pagination.size, "skip": pagination.skip }) return result ``` ## Request Validation FastAPI uses Pydantic for powerful validation: ```python from pydantic import BaseModel, Field, validator from datetime import datetime from typing import Optional, Literal class UserEventCreate(BaseModel): event_type: Literal["click", "view", "purchase"] timestamp: datetime = Field(default_factory=datetime.now) properties: dict = Field(default_factory=dict) value: Optional[float] = Field(None, ge=0, le=1000000) @validator('properties') def validate_properties(cls, v): if len(v) > 50: raise ValueError('Too many properties') return v @app.post("/events") async def create_event( event: UserEventCreate, moose: ApiUtil = Depends(get_moose_dependency()) ): # event is fully validated return {"success": True, "event": event} ``` ## Background Tasks ```python from fastapi import BackgroundTasks def send_notification(user_id: str, message: str): # Expensive operation print(f"Sending notification to {user_id}: {message}") @app.post("/notify/{user_id}") async def notify_user( user_id: str, message: str, background_tasks: BackgroundTasks ): # Add task to run after response is sent background_tasks.add_task(send_notification, user_id, message) return {"message": "Notification queued"} ``` ## Authentication with JWT ```python # Manual check @app.get("/protected") async def protected(request: Request): moose = get_moose_utils(request) if not moose or not moose.jwt: raise HTTPException(status_code=401, detail="Unauthorized") user_id = moose.jwt.get("sub") return {"message": "Authenticated", "user_id": user_id} # Dependency pattern (recommended) async def require_auth(request: Request) -> ApiUtil: moose = get_moose_utils(request) if not moose or not moose.jwt: raise HTTPException(status_code=401, detail="Unauthorized") return moose @app.get("/protected") async def protected(moose: ApiUtil = Depends(require_auth)): return {"message": "Authenticated", "user": moose.jwt.get("sub")} ``` See [Authentication documentation](/moose/apis/auth) for JWT configuration. ## Best Practices 1. **Use dependency injection**: Leverage `Depends()` for cleaner code 2. **Validate with Pydantic**: Use `BaseModel` and `Field()` for validation 3. **Use response models**: Specify `response_model` for automatic validation 4. **Handle async properly**: Use `async def` for I/O operations 5. **Add type hints**: FastAPI uses types for validation and documentation 6. **Use background tasks**: For operations that don't need to complete before response 7. **Document with docstrings**: FastAPI includes docstrings in OpenAPI docs 8. **Organize with routers**: Split large applications into APIRouter instances ## Router Organization ```python filename="app/apis/routers/users.py" from fastapi import APIRouter, Depends from moose_lib.dmv2.web_app_helpers import get_moose_dependency, ApiUtil router = APIRouter(prefix="/users", tags=["users"]) @router.get("/{user_id}") async def get_user( user_id: str, moose: ApiUtil = Depends(get_moose_dependency()) ): return {"user_id": user_id} ``` ```python filename="app/apis/main_app.py" from fastapi import FastAPI from moose_lib.dmv2 import WebApp, WebAppConfig from .routers import users app = FastAPI() # Include routers app.include_router(users.router) webapp = WebApp("mainApp", app, WebAppConfig(mount_path="/api")) ``` ## Troubleshooting ### "Moose utilities not available" **Solution:** Verify `inject_moose_utils` is enabled (default): ```python WebAppConfig(mount_path="/myapi", inject_moose_utils=True) ``` ### Routes not responding **Solution:** Pass the FastAPI app instance, not a router: ```python # Correct app = FastAPI() webapp = WebApp("name", app, config) # Incorrect router = APIRouter() webapp = WebApp("name", router, config) # Won't work ``` ### Dependency injection errors **Solution:** Ensure dependencies return correct types: ```python # Correct async def get_moose(request: Request) -> ApiUtil: moose = get_moose_utils(request) if not moose: raise HTTPException(500, "Not available") return moose # Return ApiUtil # Usage @app.get("/") async def handler(moose: ApiUtil = Depends(get_moose)): # moose is ApiUtil type pass ``` --- ## Fastify with MooseStack Source: moose/app-api-frameworks/typescript-fastify.mdx Use Fastify framework with MooseStack # Fastify with MooseStack Mount Fastify applications within your MooseStack project using the `WebApp` class. Fastify is a fast and low overhead web framework with powerful schema-based validation. - Already running Fastify elsewhere? Keep it outside your MooseStack project and query data with the MooseStack client. The [Querying Data guide](/moose/olap/read-data) walks through the SDK. - Want to mount Fastify in your MooseStack project? Follow the setup below with `WebApp` for unified deployment and access to MooseStack utilities. ## Basic Example ```ts filename="app/apis/fastifyApp.ts" copy const app = Fastify({ logger: true }); app.get("/health", async (request, reply) => { return { status: "ok" }; }); app.get("/data", async (request, reply) => { const moose = getMooseUtils(request.raw); if (!moose) { reply.code(500); return { error: "Moose utilities not available" }; } const { client, sql } = moose; const limit = parseInt((request.query as any).limit || "10"); try { const query = sql` SELECT ${MyTable.columns.id}, ${MyTable.columns.name}, ${MyTable.columns.createdAt} FROM ${MyTable} ORDER BY ${MyTable.columns.createdAt} DESC LIMIT ${limit} `; const result = await client.query.execute(query); return await result.json(); } catch (error) { reply.code(500); return { error: String(error) }; } }); // Must call ready() before passing to WebApp await app.ready(); ); ``` **Access your API:** - `GET http://localhost:4000/fastify/health` - `GET http://localhost:4000/fastify/data?limit=20` Fastify apps must call `.ready()` before passing to WebApp. ## Complete Example with Schema Validation ```ts filename="app/apis/advancedFastifyApp.ts" copy const app = Fastify({ logger: true, ajv: { customOptions: { removeAdditional: "all", coerceTypes: true } } }); // Schema definitions const getUserEventsSchema = { querystring: { type: "object", properties: { limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, eventType: { type: "string" } } }, params: { type: "object", required: ["userId"], properties: { userId: { type: "string", pattern: "^[a-zA-Z0-9-]+$" } } }, response: { 200: { type: "object", properties: { userId: { type: "string" }, count: { type: "integer" }, events: { type: "array" } } } } }; // GET with schema validation app.get<{ Params: { userId: string }; Querystring: { limit?: number; eventType?: string }; }>("/users/:userId/events", { schema: getUserEventsSchema }, async (request, reply) => { const moose = getMooseUtils(request.raw); if (!moose) { reply.code(500); return { error: "Moose utilities not available" }; } const { client, sql } = moose; const { userId } = request.params; const { limit = 10, eventType } = request.query; const cols = UserEvents.columns; const query = sql` SELECT ${cols.id}, ${cols.event_type}, ${cols.timestamp} FROM ${UserEvents} WHERE ${cols.user_id} = ${userId} ${eventType ? sql`AND ${cols.event_type} = ${eventType}` : sql``} ORDER BY ${cols.timestamp} DESC LIMIT ${limit} `; const result = await client.query.execute(query); const events = await result.json(); return { userId, count: events.length, events }; }); // POST with schema validation const createEventSchema = { body: { type: "object", required: ["eventType", "data"], properties: { eventType: { type: "string", minLength: 1 }, data: { type: "object" } } } }; app.post<{ Params: { userId: string }; Body: { eventType: string; data: object }; }>("/users/:userId/events", { schema: createEventSchema }, async (request, reply) => { const { userId } = request.params; const { eventType, data } = request.body; // Handle POST logic return { success: true, userId, eventType, data }; }); // Protected route with JWT app.get("/protected", async (request, reply) => { const moose = getMooseUtils(request.raw); if (!moose?.jwt) { reply.code(401); return { error: "Unauthorized" }; } return { message: "Authenticated", userId: moose.jwt.sub }; }); // Error handler app.setErrorHandler((error, request, reply) => { request.log.error(error); reply.code(500).send({ error: "Internal Server Error", message: error.message }); }); await app.ready(); ); ``` ## Accessing Moose Utilities Use `request.raw` to access the underlying Node.js request: ```ts const moose = getMooseUtils(request.raw); if (!moose) { reply.code(500); return { error: "Utilities not available" }; } const { client, sql, jwt } = moose; ``` ## Plugins and Decorators Fastify plugins work seamlessly: ```ts const app = Fastify({ logger: true }); // CORS await app.register(cors, { origin: true }); // Security headers await app.register(helmet); // Rate limiting await app.register(rateLimit, { max: 100, timeWindow: "15 minutes" }); // Custom decorator app.decorate("utility", { formatResponse: (data: any) => ({ success: true, timestamp: new Date().toISOString(), data }) }); app.get("/data", async (request, reply) => { const moose = getMooseUtils(request.raw); if (!moose) { reply.code(500); return { error: "Utilities not available" }; } const { client, sql } = moose; const result = await client.query.execute(sql` SELECT ${MyTable.columns.id}, ${MyTable.columns.name}, ${MyTable.columns.status} FROM ${MyTable} WHERE ${MyTable.columns.status} = 'active' LIMIT 10 `); const data = await result.json(); return app.utility.formatResponse(data); }); await app.ready(); ``` ## Type-Safe Routes Leverage TypeScript for type-safe routes: ```ts interface UserQueryParams { limit?: number; offset?: number; status?: "active" | "inactive"; } interface UserResponse { id: string; name: string; email: string; } app.get<{ Querystring: UserQueryParams; Reply: UserResponse[] }>("/users", async (request, reply) => { const { limit = 10, offset = 0, status } = request.query; // TypeScript knows the shape of query params const moose = getMooseUtils(request.raw); // ... query logic // Return type is checked return [ { id: "1", name: "John", email: "john@example.com" } ]; }); ``` ## WebApp Configuration ```ts new WebApp(name, app, config) ``` **WebAppConfig:** ```ts interface WebAppConfig { mountPath: string; metadata?: { description?: string }; injectMooseUtils?: boolean; // default: true } ``` ## Best Practices 1. **Call .ready() before WebApp**: Always await `app.ready()` before creating WebApp 2. **Use request.raw for utilities**: Access Moose utilities via `getMooseUtils(request.raw)` 3. **Define schemas**: Use Fastify's JSON Schema validation for request/response 4. **Type your routes**: Use TypeScript generics for type-safe route handlers 5. **Leverage plugins**: Use Fastify's rich plugin ecosystem 6. **Handle errors**: Use `setErrorHandler` for global error handling 7. **Enable logging**: Use Fastify's built-in logger for debugging ## Troubleshooting ### "Moose utilities not available" **Solution:** Use `request.raw` to access the underlying request: ```ts const moose = getMooseUtils(request.raw); // Not request! ``` ### App not responding after mounting **Solution:** Ensure you called `.ready()`: ```ts await app.ready(); // Must call before WebApp interface Query { limit?: number } ``` --- ## Koa with MooseStack Source: moose/app-api-frameworks/typescript-koa.mdx Use Koa framework with MooseStack # Koa with MooseStack Mount Koa applications within your MooseStack project using the `WebApp` class. Koa is an expressive, minimalist framework by the Express team, designed for modern async/await patterns. - Already running Koa outside MooseStack? Keep it separate and call MooseStack data with the client SDK. The [Querying Data guide](/moose/olap/read-data) has TypeScript examples. - Want to mount Koa in your MooseStack project? Continue below with `WebApp` for unified deployment and access to MooseStack utilities. ## Basic Example ```ts filename="app/apis/koaApp.ts" copy const app = new Koa(); const router = new Router(); app.use(bodyParser()); router.get("/health", (ctx) => { ctx.body = { status: "ok" }; }); router.get("/data", async (ctx) => { const moose = getMooseUtils(ctx.req); if (!moose) { ctx.status = 500; ctx.body = { error: "Moose utilities not available" }; return; } const { client, sql } = moose; const limit = parseInt((ctx.query.limit as string) || "10"); try { const query = sql` SELECT ${MyTable.columns.id}, ${MyTable.columns.name}, ${MyTable.columns.createdAt} FROM ${MyTable} ORDER BY ${MyTable.columns.createdAt} DESC LIMIT ${limit} `; const result = await client.query.execute(query); ctx.body = await result.json(); } catch (error) { ctx.status = 500; ctx.body = { error: String(error) }; } }); app.use(router.routes()); app.use(router.allowedMethods()); ); ``` **Access your API:** - `GET http://localhost:4000/koa/health` - `GET http://localhost:4000/koa/data?limit=20` ## Complete Example with Middleware ```ts filename="app/apis/advancedKoaApp.ts" copy const app = new Koa(); const router = new Router(); // Middleware app.use(logger()); app.use(bodyParser()); // Custom error handling middleware app.use(async (ctx: Context, next: Next) => { try { await next(); } catch (error) { ctx.status = error.status || 500; ctx.body = { error: error.message || "Internal Server Error" }; ctx.app.emit("error", error, ctx); } }); // Custom logging middleware app.use(async (ctx: Context, next: Next) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); }); // Health check router.get("/health", (ctx) => { ctx.body = { status: "ok", timestamp: new Date().toISOString() }; }); // GET with params and query router.get("/users/:userId/events", async (ctx) => { const moose = getMooseUtils(ctx.req); if (!moose) { ctx.throw(500, "Moose utilities not available"); } const { client, sql } = moose; const { userId } = ctx.params; const limit = parseInt((ctx.query.limit as string) || "10"); const eventType = ctx.query.eventType as string; const cols = UserEvents.columns; const query = sql` SELECT ${cols.id}, ${cols.event_type}, ${cols.timestamp} FROM ${UserEvents} WHERE ${cols.user_id} = ${userId} ${eventType ? sql`AND ${cols.event_type} = ${eventType}` : sql``} ORDER BY ${cols.timestamp} DESC LIMIT ${limit} `; const result = await client.query.execute(query); const events = await result.json(); ctx.body = { userId, count: events.length, events }; }); // POST endpoint router.post("/users/:userId/events", async (ctx) => { const moose = getMooseUtils(ctx.req); if (!moose) { ctx.throw(500, "Moose utilities not available"); } const { userId } = ctx.params; const { eventType, data } = ctx.request.body as any; // Validation if (!eventType || !data) { ctx.throw(400, "eventType and data are required"); } // Handle POST logic ctx.body = { success: true, userId, eventType, data }; ctx.status = 201; }); // Protected route with JWT router.get("/protected", async (ctx) => { const moose = getMooseUtils(ctx.req); if (!moose?.jwt) { ctx.throw(401, "Unauthorized"); } const userId = moose.jwt.sub; const userRole = moose.jwt.role; ctx.body = { message: "Authenticated", userId, role: userRole }; }); // Multiple route handlers (middleware chain) const checkAuth = async (ctx: Context, next: Next) => { const moose = getMooseUtils(ctx.req); if (!moose?.jwt) { ctx.throw(401, "Unauthorized"); } await next(); }; router.get("/admin/stats", checkAuth, async (ctx) => { const moose = getMooseUtils(ctx.req); // moose.jwt is guaranteed to exist here ctx.body = { stats: "admin stats" }; }); app.use(router.routes()); app.use(router.allowedMethods()); // Error listener app.on("error", (err, ctx) => { console.error("Server error:", err); }); ); ``` ## Accessing Moose Utilities Use `ctx.req` to access the underlying Node.js request: ```ts const moose = getMooseUtils(ctx.req); if (!moose) { ctx.throw(500, "Utilities not available"); } const { client, sql, jwt } = moose; ``` ## Middleware Patterns ### Composition ```ts const authMiddleware = async (ctx: Context, next: Next) => { const moose = getMooseUtils(ctx.req); if (!moose?.jwt) { ctx.throw(401, "Unauthorized"); } await next(); }; const adminMiddleware = async (ctx: Context, next: Next) => { const moose = getMooseUtils(ctx.req); if (!moose?.jwt || moose.jwt.role !== "admin") { ctx.throw(403, "Forbidden"); } await next(); }; // Compose middleware const requireAdmin = compose([authMiddleware, adminMiddleware]); router.get("/admin", requireAdmin, async (ctx) => { ctx.body = { message: "Admin access granted" }; }); ``` ### Custom Context Extensions ```ts // Extend context type interface CustomContext extends Context { formatResponse: (data: any) => { success: boolean; data: any }; } const app = new Koa(); // Add custom method to context app.context.formatResponse = function(data: any) { return { success: true, timestamp: new Date().toISOString(), data }; }; router.get("/data", async (ctx: CustomContext) => { const moose = getMooseUtils(ctx.req); if (!moose) { ctx.throw(500, "Utilities not available"); } const { client, sql } = moose; const result = await client.query.execute(sql` SELECT ${MyTable.columns.id}, ${MyTable.columns.name}, ${MyTable.columns.status} FROM ${MyTable} WHERE ${MyTable.columns.status} = 'active' LIMIT 10 `); const data = await result.json(); ctx.body = ctx.formatResponse(data); }); ``` ## Error Handling Koa uses try-catch for error handling: ```ts // Error middleware app.use(async (ctx, next) => { try { await next(); } catch (err) { // Custom error handling ctx.status = err.statusCode || err.status || 500; ctx.body = { error: { message: err.message, status: ctx.status } }; // Emit error event ctx.app.emit("error", err, ctx); } }); // Error listener app.on("error", (err, ctx) => { console.error("Error:", err); }); ``` ## Router Nesting Organize routes with nested routers: ```ts filename="app/apis/routers/usersRouter.ts" ); usersRouter.get("/:userId", async (ctx) => { const moose = getMooseUtils(ctx.req); if (!moose) { ctx.throw(500, "Utilities not available"); } const { userId } = ctx.params; // Query logic ctx.body = { userId }; }); ``` ```ts filename="app/apis/mainApp.ts" const app = new Koa(); const mainRouter = new Router(); // Nest routers mainRouter.use("/api", usersRouter.routes(), usersRouter.allowedMethods()); app.use(mainRouter.routes()); app.use(mainRouter.allowedMethods()); ); ``` ## WebApp Configuration ```ts new WebApp(name, app, config) ``` **WebAppConfig:** ```ts interface WebAppConfig { mountPath: string; metadata?: { description?: string }; injectMooseUtils?: boolean; // default: true } ``` ## Best Practices 1. **Use ctx.req for utilities**: Access Moose utilities via `getMooseUtils(ctx.req)` 2. **Use ctx.throw()**: Koa's built-in error throwing for cleaner code 3. **Leverage async/await**: Koa is designed for modern async patterns 4. **Compose middleware**: Use `koa-compose` for reusable middleware chains 5. **Handle errors globally**: Use error middleware at the top of middleware stack 6. **Type your context**: Extend Context type for custom properties 7. **Organize with routers**: Split large applications into nested routers ## Troubleshooting ### "Moose utilities not available" **Solution:** Use `ctx.req` not `ctx.request`: ```ts const moose = getMooseUtils(ctx.req); // Correct const moose = getMooseUtils(ctx.request); // Wrong ``` ### Middleware order issues **Solution:** Apply middleware in correct order: ```ts app.use(logger()); // 1. Logging app.use(bodyParser()); // 2. Body parsing app.use(errorHandler); // 3. Error handling app.use(router.routes()); // 4. Routes ``` ### TypeScript errors with Context **Solution:** Import and use correct types: ```ts router.get("/path", async (ctx: Context, next: Next) => { // ctx is properly typed }); ``` --- ## Raw Node.js with MooseStack Source: moose/app-api-frameworks/typescript-raw-nodejs.mdx Use raw Node.js HTTP handlers with MooseStack # Raw Node.js with MooseStack Use raw Node.js HTTP handlers without any framework. This gives you maximum control and minimal dependencies, ideal for performance-critical applications or when you want to avoid framework overhead. - Running standalone Node.js elsewhere? Keep it separate and query Moose with the client SDK—see the [Querying Data guide](/moose/olap/read-data) for examples. - Want to mount your raw handlers in your MooseStack project? Follow the `WebApp` approach below so your endpoints deploy alongside the rest of your Moose project. ## Basic Example ```ts filename="app/apis/rawApp.ts" copy const handler = async (req: IncomingMessage, res: ServerResponse) => { const url = parseUrl(req.url || "", true); const pathname = url.pathname || "/"; if (pathname === "/health" && req.method === "GET") { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "ok" })); return; } if (pathname === "/data" && req.method === "GET") { const moose = getMooseUtils(req); if (!moose) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Moose utilities not available" })); return; } const { client, sql } = moose; const limit = parseInt((url.query.limit as string) || "10"); try { const query = sql` SELECT ${MyTable.columns.id}, ${MyTable.columns.name}, ${MyTable.columns.createdAt} FROM ${MyTable} ORDER BY ${MyTable.columns.createdAt} DESC LIMIT ${limit} `; const result = await client.query.execute(query); const data = await result.json(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(data)); } catch (error) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: String(error) })); } return; } res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not found" })); }; ); ``` **Access your API:** - `GET http://localhost:4000/raw/health` - `GET http://localhost:4000/raw/data?limit=20` ## Complete Example with Advanced Features ```ts filename="app/apis/advancedRawApp.ts" copy // Helper to parse request body const parseBody = (req: IncomingMessage): Promise => { return new Promise((resolve, reject) => { let body = ""; req.on("data", chunk => body += chunk.toString()); req.on("end", () => { try { resolve(body ? JSON.parse(body) : {}); } catch (error) { reject(new Error("Invalid JSON")); } }); req.on("error", reject); }); }; // Helper to send JSON response const sendJSON = (res: ServerResponse, status: number, data: any) => { res.writeHead(status, { "Content-Type": "application/json", "X-Powered-By": "MooseStack" }); res.end(JSON.stringify(data)); }; // Helper to send error const sendError = (res: ServerResponse, status: number, message: string) => { sendJSON(res, status, { error: message }); }; // Route handlers const handleHealth = (req: IncomingMessage, res: ServerResponse) => { sendJSON(res, 200, { status: "ok", timestamp: new Date().toISOString() }); }; const handleGetUserEvents = async ( req: IncomingMessage, res: ServerResponse, userId: string, query: any ) => { const moose = getMooseUtils(req); if (!moose) { return sendError(res, 500, "Moose utilities not available"); } const { client, sql } = moose; const limit = parseInt(query.limit || "10"); const eventType = query.eventType; try { const cols = UserEvents.columns; const querySQL = sql` SELECT ${cols.id}, ${cols.event_type}, ${cols.timestamp} FROM ${UserEvents} WHERE ${cols.user_id} = ${userId} ${eventType ? sql`AND ${cols.event_type} = ${eventType}` : sql``} ORDER BY ${cols.timestamp} DESC LIMIT ${limit} `; const result = await client.query.execute(querySQL); const events = await result.json(); sendJSON(res, 200, { userId, count: events.length, events }); } catch (error) { sendError(res, 500, String(error)); } }; const handleCreateEvent = async ( req: IncomingMessage, res: ServerResponse, userId: string ) => { try { const body = await parseBody(req); const { eventType, data } = body; if (!eventType || !data) { return sendError(res, 400, "eventType and data are required"); } // Handle POST logic sendJSON(res, 201, { success: true, userId, eventType, data }); } catch (error) { sendError(res, 400, "Invalid request body"); } }; const handleProtected = (req: IncomingMessage, res: ServerResponse) => { const moose = getMooseUtils(req); if (!moose?.jwt) { return sendError(res, 401, "Unauthorized"); } sendJSON(res, 200, { message: "Authenticated", userId: moose.jwt.sub, claims: moose.jwt }); }; // Main handler with routing const handler = async (req: IncomingMessage, res: ServerResponse) => { const url = parseUrl(req.url || "", true); const pathname = url.pathname || "/"; const method = req.method || "GET"; // CORS headers res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); // Handle preflight if (method === "OPTIONS") { res.writeHead(204); res.end(); return; } // Route matching if (pathname === "/health" && method === "GET") { return handleHealth(req, res); } // Match /users/:userId/events const userEventsMatch = pathname.match(/^\/users\/([^\/]+)\/events$/); if (userEventsMatch) { const userId = userEventsMatch[1]; if (method === "GET") { return handleGetUserEvents(req, res, userId, url.query); } if (method === "POST") { return handleCreateEvent(req, res, userId); } return sendError(res, 405, "Method not allowed"); } if (pathname === "/protected" && method === "GET") { return handleProtected(req, res); } // 404 sendError(res, 404, "Not found"); }; ); ``` ## Pattern Matching for Routes ```ts // Simple pattern matching const matchRoute = (pathname: string, pattern: string): { [key: string]: string } | null => { const patternParts = pattern.split("/"); const pathParts = pathname.split("/"); if (patternParts.length !== pathParts.length) { return null; } const params: { [key: string]: string } = {}; for (let i = 0; i < patternParts.length; i++) { if (patternParts[i].startsWith(":")) { const paramName = patternParts[i].slice(1); params[paramName] = pathParts[i]; } else if (patternParts[i] !== pathParts[i]) { return null; } } return params; }; // Usage const handler = async (req: IncomingMessage, res: ServerResponse) => { const url = parseUrl(req.url || "", true); const pathname = url.pathname || "/"; const userParams = matchRoute(pathname, "/users/:userId"); if (userParams) { const { userId } = userParams; // Handle user route return; } const eventParams = matchRoute(pathname, "/users/:userId/events/:eventId"); if (eventParams) { const { userId, eventId } = eventParams; // Handle event route return; } }; ``` ## Streaming Responses ```ts const handleStreamData = async (req: IncomingMessage, res: ServerResponse) => { const moose = getMooseUtils(req); if (!moose) { return sendError(res, 500, "Utilities not available"); } const { client, sql } = moose; res.writeHead(200, { "Content-Type": "application/x-ndjson", "Transfer-Encoding": "chunked" }); const query = sql` SELECT ${MyTable.columns.id}, ${MyTable.columns.name}, ${MyTable.columns.data} FROM ${MyTable} ORDER BY ${MyTable.columns.createdAt} DESC LIMIT 1000 `; const result = await client.query.execute(query); const data = await result.json(); // Stream data in chunks for (const row of data) { res.write(JSON.stringify(row) + "\n"); await new Promise(resolve => setTimeout(resolve, 10)); } res.end(); }; ``` ## WebApp Configuration ```ts new WebApp(name, handler, config) ``` **WebAppConfig:** ```ts interface WebAppConfig { mountPath: string; metadata?: { description?: string }; injectMooseUtils?: boolean; // default: true } ``` ## Accessing Moose Utilities ```ts const moose = getMooseUtils(req); if (!moose) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Utilities not available" })); return; } const { client, sql, jwt } = moose; ``` ## Best Practices 1. **Parse URL with url module**: Use `url.parse()` for query parameters and pathname 2. **Set Content-Type headers**: Always set appropriate response headers 3. **Handle errors gracefully**: Wrap async operations in try-catch 4. **Use helper functions**: Extract common patterns (sendJSON, parseBody) 5. **Implement routing logic**: Use pattern matching for dynamic routes 6. **Handle CORS**: Set CORS headers if needed for browser clients 7. **Stream large responses**: Use chunked encoding for large datasets ## Middleware Pattern Create your own middleware pattern: ```ts type Middleware = ( req: IncomingMessage, res: ServerResponse, next: () => Promise ) => Promise; const createMiddlewareChain = (...middlewares: Middleware[]) => { return async (req: IncomingMessage, res: ServerResponse) => { let index = 0; const next = async (): Promise => { if (index < middlewares.length) { const middleware = middlewares[index++]; await middleware(req, res, next); } }; await next(); }; }; // Example middleware const loggerMiddleware: Middleware = async (req, res, next) => { console.log(`${req.method} ${req.url}`); await next(); }; const authMiddleware: Middleware = async (req, res, next) => { const moose = getMooseUtils(req); if (!moose?.jwt) { sendError(res, 401, "Unauthorized"); return; } await next(); }; const routeHandler: Middleware = async (req, res, next) => { sendJSON(res, 200, { message: "Success" }); }; // Create handler with middleware chain const handler = createMiddlewareChain( loggerMiddleware, authMiddleware, routeHandler ); ``` ## When to Use Raw Node.js **Ideal for:** - Maximum control over request/response - Performance-critical applications - Minimal dependencies - Custom protocols or streaming - Learning HTTP fundamentals **Not ideal for:** - Rapid development (frameworks are faster) - Complex routing (use Express/Koa instead) - Large teams (frameworks provide structure) - Standard REST APIs (frameworks have better DX) ## Troubleshooting ### Response not closing **Solution:** Always call `res.end()`: ```ts res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(data)); // Don't forget this! ``` ### Query parameters not parsing **Solution:** Use `url.parse()` with `true` for query parsing: ```ts const url = parseUrl(req.url || "", true); // true enables query parsing const limit = url.query.limit; ``` ### POST body not available **Solution:** Manually parse the request stream: ```ts const parseBody = (req: IncomingMessage): Promise => { return new Promise((resolve, reject) => { let body = ""; req.on("data", chunk => body += chunk.toString()); req.on("end", () => { try { resolve(body ? JSON.parse(body) : {}); } catch (error) { reject(new Error("Invalid JSON")); } }); req.on("error", reject); }); }; ```