Express Integration
Using UnRAG with Express.js for traditional Node.js server applications.
Express remains one of the most straightforward ways to build a Node.js API. UnRAG integrates cleanly—you import the engine, call its methods in your route handlers, and return JSON responses. No special setup required beyond what UnRAG already generates.
Basic search endpoint
Here's a complete Express app with search and ingestion endpoints:
import express from "express";
import { createUnragEngine } from "@unrag/config";
const app = express();
app.use(express.json());
// Search endpoint
app.get("/api/search", async (req, res) => {
const query = String(req.query.q ?? "").trim();
if (!query) {
return res.status(400).json({ error: "Missing query parameter 'q'" });
}
if (query.length > 500) {
return res.status(400).json({ error: "Query too long" });
}
try {
const engine = createUnragEngine();
const result = await engine.retrieve({ query, topK: 10 });
res.json({
query,
results: result.chunks.map((chunk) => ({
id: chunk.id,
content: chunk.content,
source: chunk.sourceId,
score: chunk.score,
})),
meta: {
embeddingModel: result.embeddingModel,
durationMs: result.durations.totalMs,
},
});
} catch (error) {
console.error("Search error:", error);
res.status(500).json({ error: "Search failed" });
}
});
// Ingestion endpoint (protect this in production!)
app.post("/api/ingest", async (req, res) => {
const { sourceId, content, metadata } = req.body;
if (!sourceId || !content) {
return res.status(400).json({ error: "Missing sourceId or content" });
}
try {
const engine = createUnragEngine();
const result = await engine.ingest({
sourceId,
content,
metadata: metadata ?? {},
});
res.json({
success: true,
documentId: result.documentId,
chunkCount: result.chunkCount,
durationMs: result.durations.totalMs,
});
} catch (error) {
console.error("Ingest error:", error);
res.status(500).json({ error: "Ingestion failed" });
}
});
const PORT = process.env.PORT ?? 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});TypeScript path aliases
Express projects typically don't have path alias support out of the box. You have a few options:
Option 1: Update your TypeScript config and use ts-node or tsx
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@unrag/*": ["./lib/unrag/*"],
"@unrag/config": ["./unrag.config.ts"]
}
}
}Run with tsx or ts-node with path support enabled:
npx tsx src/server.tsOption 2: Use relative imports
import { createUnragEngine } from "../../unrag.config";Less elegant, but works without additional configuration.
Connection management
Express apps are long-running processes, so connection pooling behaves differently than serverless environments. The singleton pattern in unrag.config.ts works well—the pool is created once when the module first loads and reused for all requests.
For high-traffic applications, you might want to tune pool settings:
// In unrag.config.ts
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Allow more concurrent connections
idleTimeoutMillis: 30000, // Release idle connections after 30s
connectionTimeoutMillis: 5000, // Fail fast if pool is exhausted
});Adding authentication
UnRAG doesn't handle auth—that's your application's responsibility. Here's a simple pattern:
import type { Request, Response, NextFunction } from "express";
const requireAuth = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token || !isValidToken(token)) {
return res.status(401).json({ error: "Unauthorized" });
}
next();
};
// Protect ingestion endpoint
app.post("/api/ingest", requireAuth, async (req, res) => {
// ... ingestion logic
});
// Search might be public or protected depending on your needs
app.get("/api/search", async (req, res) => {
// ... search logic
});Error handling
Wrap UnRAG operations in try-catch blocks and return appropriate HTTP status codes. The engine throws on failures (network errors, database issues, invalid configuration), and you should handle these gracefully:
app.get("/api/search", async (req, res) => {
try {
const engine = createUnragEngine();
const result = await engine.retrieve({ query, topK: 10 });
res.json(result);
} catch (error) {
if (error.message.includes("rate limit")) {
return res.status(429).json({ error: "Rate limited by embedding provider" });
}
if (error.message.includes("connection")) {
return res.status(503).json({ error: "Database temporarily unavailable" });
}
console.error("Unexpected error:", error);
res.status(500).json({ error: "Internal server error" });
}
});Running in production
For production Express deployments:
- Use a process manager like PM2 or run in a container
- Set
NODE_ENV=productionto disable development behaviors - Configure your database for production (SSL, connection limits)
- Add health check endpoints that verify database connectivity
- Set up logging and monitoring for the embedding and database operations