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.
WebApp approach below so your endpoints deploy alongside the rest of your Moose project.import { WebApp, getMooseUtils } from "@514labs/moose-lib";import { MyTable } from "../tables/MyTable";import { IncomingMessage, ServerResponse } from "http";import { parse as parseUrl } from "url"; 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" }));}; export const rawApp = new WebApp("rawApp", handler, { mountPath: "/raw", metadata: { description: "Raw Node.js handler" }});Access your API:
GET http://localhost:4000/raw/healthGET http://localhost:4000/raw/data?limit=20import { WebApp, getMooseUtils } from "@514labs/moose-lib";import { UserEvents } from "../tables/UserEvents";import { IncomingMessage, ServerResponse } from "http";import { parse as parseUrl } from "url"; // Helper to parse request bodyconst parseBody = (req: IncomingMessage): Promise<any> => { 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 responseconst 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 errorconst sendError = (res: ServerResponse, status: number, message: string) => { sendJSON(res, status, { error: message });}; // Route handlersconst 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 routingconst 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");}; export const advancedRawApp = new WebApp("advancedRaw", handler, { mountPath: "/api/v1", metadata: { description: "Advanced raw Node.js handler with routing" }});// Simple pattern matchingconst 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;}; // Usageconst 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; }};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();};new WebApp(name, handler, config)WebAppConfig:
interface WebAppConfig { mountPath: string; metadata?: { description?: string }; injectMooseUtils?: boolean; // default: true}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;url.parse() for query parameters and pathnameCreate your own middleware pattern:
type Middleware = ( req: IncomingMessage, res: ServerResponse, next: () => Promise<void>) => Promise<void>; const createMiddlewareChain = (...middlewares: Middleware[]) => { return async (req: IncomingMessage, res: ServerResponse) => { let index = 0; const next = async (): Promise<void> => { if (index < middlewares.length) { const middleware = middlewares[index++]; await middleware(req, res, next); } }; await next(); };}; // Example middlewareconst 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 chainconst handler = createMiddlewareChain( loggerMiddleware, authMiddleware, routeHandler);Ideal for:
Not ideal for:
Solution: Always call res.end():
res.writeHead(200, { "Content-Type": "application/json" });res.end(JSON.stringify(data)); // Don't forget this!Solution: Use url.parse() with true for query parsing:
import { parse as parseUrl } from "url";const url = parseUrl(req.url || "", true); // true enables query parsingconst limit = url.query.limit;Solution: Manually parse the request stream:
const parseBody = (req: IncomingMessage): Promise<any> => { 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); });};