We value your privacy

This site uses cookies to improve your browsing experience, analyze site traffic, and show personalized content. See our Privacy Policy.

  1. MooseStack
  2. Moose APIs & Web Apps
  3. Express 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.

Choose your integration path
  • Already run Express elsewhere? Keep it outside your MooseStack project and query data with the MooseStack client. The Querying Data guide 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

app/apis/expressApp.ts
import express from "express";import { WebApp, getMooseUtils } from "@514labs/moose-lib";import { MyTable } from "../tables/MyTable"; const app = express(); app.use(express.json()); app.get("/health", (req, res) => {  res.json({ status: "ok" });}); app.get("/data", async (req, res) => {  const { client, sql } = await getMooseUtils();  const limit = parseInt(req.query.limit as string) || 10;   try {    const query = sql.statement`      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) });  }}); export const expressApp = new WebApp("expressApp", app, {  mountPath: "/express",  metadata: { description: "Express API with custom middleware" }});

Access your API:

  • GET http://localhost:4000/express/health
  • GET http://localhost:4000/express/data?limit=20

Complete Example with Features

app/apis/advancedExpressApp.ts
import express, { Request, Response, NextFunction } from "express";import { WebApp, getMooseUtils } from "@514labs/moose-lib";import { UserEvents } from "../tables/UserEvents";import { UserProfile } from "../tables/UserProfile"; const app = express(); // Middleware setupapp.use(express.json()); // Custom logging middlewareapp.use((req: Request, res: Response, next: NextFunction) => {  console.log(`${req.method} ${req.path}`);  next();}); // Error handling middlewareconst asyncHandler = (fn: Function) => (req: Request, res: Response, next: NextFunction) => {  Promise.resolve(fn(req, res, next)).catch(next);}; // Health check endpointapp.get("/health", (req, res) => {  res.json({    status: "ok",    timestamp: new Date().toISOString()  });}); // GET endpoint with query parametersapp.get("/users/:userId/events", asyncHandler(async (req: Request, res: Response) => {  const { client, sql } = await getMooseUtils();  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.statement`    SELECT      ${cols.id},      ${cols.event_type},      ${cols.timestamp}    FROM ${UserEvents}    WHERE ${cols.user_id} = ${userId}      ${eventType ? sql.fragment`AND ${cols.event_type} = ${eventType}` : sql.fragment``}    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 endpointapp.post("/users/:userId/profile", asyncHandler(async (req: Request, res: Response) => {  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 JWTapp.get("/protected", asyncHandler(async (req: Request, res: Response) => {  const moose = await getMooseUtils();   if (!moose.jwt) {    return res.status(401).json({ error: "Unauthorized" });  }   const userId = moose.jwt.sub;  res.json({    message: "Authenticated",    userId,    claims: moose.jwt  });})); // Error handlingapp.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 WebAppexport const advancedExpressApp = new WebApp("advancedExpress", app, {  mountPath: "/api/v1",  metadata: {    description: "Advanced Express API with routing and middleware"  }});

WebApp Configuration

new WebApp(name, app, config)

Parameters:

  • name (string): Unique identifier for your WebApp
  • app: Your Express application instance
  • config (WebAppConfig): Configuration object

WebAppConfig:

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

import { getMooseUtils } from "@514labs/moose-lib"; app.get("/data", async (req, res) => {  const { client, sql, jwt } = await getMooseUtils();  // Use client and sql for database queries});

getMooseUtils() is async and automatically detects the runtime context -- no request parameter is needed. It returns a MooseUtils object (never undefined) and throws on error.

For row-level security, pass an rlsContext:

const { client, sql } = await getMooseUtils({ rlsContext: { org_id: orgId } });

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:

import helmet from "helmet";import compression from "compression";import rateLimit from "express-rate-limit"; const app = express(); // Securityapp.use(helmet()); // Compressionapp.use(compression()); // Rate limitingconst limiter = rateLimit({  windowMs: 15 * 60 * 1000, // 15 minutes  max: 100 // limit each IP to 100 requests per windowMs});app.use(limiter); // Body parsingapp.use(express.json());

Router Pattern

Organize routes using Express Router:

app/apis/routers/usersRouter.ts
import { Router } from "express";import { getMooseUtils } from "@514labs/moose-lib";import { UserProfile } from "../../tables/UserProfile"; export const usersRouter = Router(); usersRouter.get("/:userId", async (req, res) => {  const { client, sql } = await getMooseUtils();  const { userId } = req.params;   const query = sql.statement`    SELECT      ${UserProfile.columns.id},      ${UserProfile.columns.name},      ${UserProfile.columns.email},      ${UserProfile.columns.createdAt}    FROM ${UserProfile}    WHERE ${UserProfile.columns.id} = ${userId}  `;   const result = await client.query.execute(query);  const users = await result.json();   if (users.length === 0) {    return res.status(404).json({ error: "User not found" });  }   res.json(users[0]);});
app/apis/mainApp.ts
import express from "express";import { WebApp } from "@514labs/moose-lib";import { usersRouter } from "./routers/usersRouter"; const app = express(); app.use(express.json()); // Mount routersapp.use("/users", usersRouter); export const mainApp = new WebApp("mainApp", app, {  mountPath: "/api",  metadata: { description: "Main API with routers" }});

Authentication with JWT

app.get("/protected", async (req, res) => {  const moose = await getMooseUtils();   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 for JWT configuration.

Best Practices

  1. Use await getMooseUtils(): It auto-detects the runtime context with no request parameter needed
  2. Use async error handling: Wrap async routes with error handler
  3. Organize with routers: Split large applications into multiple routers
  4. Use TypeScript types: Import Request, Response types from Express
  5. Handle errors globally: Use Express error handling middleware

Troubleshooting

getMooseUtils() throws an error

Solution: Ensure you are calling getMooseUtils() inside a route handler that runs within the MooseStack runtime. The function detects its runtime context automatically, but it must be invoked during request handling. Wrap the call in a try/catch if you need custom error responses:

try {  const { client, sql } = await getMooseUtils();} catch (error) {  return res.status(500).json({ error: String(error) });}

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

On this page

Basic ExampleComplete Example with FeaturesWebApp ConfigurationAccessing Moose UtilitiesMiddleware IntegrationRouter PatternAuthentication with JWTBest PracticesTroubleshooting`getMooseUtils()` throws an errorMount path conflicts
Edit this page
FiveonefourFiveonefour
Fiveonefour Docs
MooseStackHostingTemplatesGuides
Release Notes
Source563
  • Overview
Build a New App
  • 5 Minute Quickstart
  • Browse Templates
  • Existing ClickHouse
Add to Existing App
  • Next.js
  • Fastify
Fundamentals
  • Moose Runtime
  • MooseDev MCP
  • Language Server
  • Data Modeling
Moose Modules
  • Moose OLAP
  • Moose Streaming
  • Moose Workflows
  • Moose APIs & Web Apps
    • Native APIs
    • Ingest API
    • Analytics API
    • Semantic Layer
    • Workflow Trigger
    • Admin APIs
    • Authentication
    • Use Your Web Framework
    • Overview
    • Express
    • Fastify
    • Koa
    • Raw Node.js
Deployment & Lifecycle
  • Moose Dev
  • Moose Migrate
  • Moose Deploy
Reference
  • API Reference
  • Query Layer
  • Testing Utilities
  • Data Types
  • Table Engines
  • CLI
  • Configuration
  • Observability Metrics
  • Help
  • Release Notes
Contribution
  • Documentation
  • Framework