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
WebAppfor unified deployment and access to MooseStack utilities.
Basic Example
import express from "express";
import { WebApp, expressMiddleware, getMooseUtils } from "@514labs/moose-lib";
import { MyTable } from "../tables/MyTable";
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) });
}
});
export const expressApp = new WebApp("expressApp", app, {
mountPath: "/express",
metadata: { description: "Express API with custom middleware" }
});Access your API:
GET http://localhost:4000/express/healthGET http://localhost:4000/express/data?limit=20
Express Middleware Required
Express applications must use expressMiddleware() to access Moose utilities:
import { expressMiddleware } from "@514labs/moose-lib";
app.use(expressMiddleware());This middleware injects MooseStack utilities into the request object.
Complete Example with Features
import express, { Request, Response, NextFunction } from "express";
import { WebApp, expressMiddleware, getMooseUtils } from "@514labs/moose-lib";
import { UserEvents } from "../tables/UserEvents";
import { UserProfile } from "../tables/UserProfile";
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
export 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 WebAppapp: Your Express application instanceconfig(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 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 queriessql: Template tag for safe SQL queriesjwt: 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();
// 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:
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 moose = getMooseUtils(req);
if (!moose) {
return res.status(500).json({ error: "Utilities not available" });
}
const { client, sql } = moose;
const { userId } = req.params;
const query = sql`
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]);
});import express from "express";
import { WebApp, expressMiddleware } from "@514labs/moose-lib";
import { usersRouter } from "./routers/usersRouter";
const app = express();
app.use(express.json());
app.use(expressMiddleware());
// Mount routers
app.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 = 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 for JWT configuration.
Best Practices
- Always use expressMiddleware(): Required for accessing Moose utilities
- Check for moose utilities: Always verify
getMooseUtils(req)returns a value - Use async error handling: Wrap async routes with error handler
- Organize with routers: Split large applications into multiple routers
- Apply middleware in order: Body parsing before expressMiddleware
- Use TypeScript types: Import Request, Response types from Express
- Handle errors globally: Use Express error handling middleware
Troubleshooting
”Moose utilities not available”
Solution: Ensure expressMiddleware() is added after body parsing:
app.use(express.json());
app.use(expressMiddleware()); // Must come after body parsersTypeScript errors with getMooseUtils
Solution: The utilities may be undefined, always check:
const moose = getMooseUtils(req);
if (!moose) {
return res.status(500).json({ error: "Utilities not available" });
}
// Now moose.client, moose.sql are safely accessibleMount path conflicts
Solution: Ensure your mount path doesn’t conflict with reserved paths:
- Avoid:
/api,/admin,/consumption,/health,/ingest,/mcp - Use:
/myapi,/v1,/custom