DocumentationReference

Build a TypeScript RAG API with HazelJS

Ship a production-shaped RAG HTTP API in TypeScript: ingest documents, retrieve context, generate answers — all in one HazelJS app with @hazeljs/rag and @hazeljs/ai.

Quick Reference

  • Purpose: End-to-end tutorial for a Node.js / TypeScript RAG API without assembling Express + LangChain glue.
  • When to use: You searched for “build RAG API TypeScript”, “Node.js RAG backend”, or are comparing stacks for a retrieval service.
  • Key concepts: embeddings, vector store, RAGPipeline, controller endpoints, HCEL .rag(), optional Agentic RAG.
  • Dependencies: @hazeljs/core, @hazeljs/ai, @hazeljs/rag.
  • Related: RAG patterns, RAG vs Agentic RAG, Vector stores, vs LangChain.

What you will build

  1. A HazelJS module with DI
  2. Ingest (index) text or docs into a vector store
  3. POST /rag/query that retrieves + generates an answer
  4. Optional upgrade path to Agentic RAG

Install

npm install @hazeljs/core @hazeljs/ai @hazeljs/rag

Set OPENAI_API_KEY (or configure another supported provider in @hazeljs/ai).

Step 1 — Bootstrap the app

import { HazelApp, HazelModule } from '@hazeljs/core';
import { RagController } from './rag.controller';
import { RagService } from './rag.service';

@HazelModule({
  controllers: [RagController],
  providers: [RagService],
})
class AppModule {}

async function bootstrap() {
  const app = new HazelApp(AppModule);
  await app.listen(3000);
  console.log('RAG API on :3000');
}

bootstrap();

Step 2 — RAG service (index + query)

import { Injectable } from '@hazeljs/core';
import { RAGPipeline, MemoryVectorStore, OpenAIEmbeddings } from '@hazeljs/rag';
import { AIService } from '@hazeljs/ai';

@Injectable()
export class RagService {
  private pipeline: RAGPipeline;
  private ready = false;

  constructor(private ai: AIService) {
    const embeddings = new OpenAIEmbeddings({
      apiKey: process.env.OPENAI_API_KEY,
    });
    const vectorStore = new MemoryVectorStore(embeddings);
    this.pipeline = new RAGPipeline({
      vectorStore,
      embeddingProvider: embeddings,
      topK: 4,
    });
  }

  async init() {
    if (this.ready) return;
    await this.pipeline.vectorStore.initialize?.();
    this.ready = true;
  }

  async ingest(docs: { id: string; text: string }[]) {
    await this.init();
    // Adapt to your package’s ingest API — see RAG package docs for loaders
    for (const doc of docs) {
      await this.pipeline.addDocuments?.([
        { id: doc.id, content: doc.text, metadata: { source: doc.id } },
      ]);
    }
    return { indexed: docs.length };
  }

  async answer(question: string) {
    await this.init();
    const results = await this.pipeline.query(question);
    const context = (results.sources ?? [])
      .map((s: { content: string }) => s.content)
      .join('\n\n');

    return this.ai.hazel
      .prompt(
        `Answer using only the context.\n\nContext:\n${context}\n\nQuestion: {{input}}`
      )
      .execute(question);
  }
}

Exact ingest helpers vary by vector store. Prefer document loaders from the Document loaders guide and Vector stores for Pinecone, Qdrant, Weaviate, or Chroma in production.

Step 3 — HTTP controllers

import { Controller, Post, Body, Get } from '@hazeljs/core';
import { RagService } from './rag.service';

@Controller({ path: '/rag' })
export class RagController {
  constructor(private rag: RagService) {}

  @Get('health')
  health() {
    return { ok: true };
  }

  @Post('ingest')
  ingest(@Body() body: { docs: { id: string; text: string }[] }) {
    return this.rag.ingest(body.docs ?? []);
  }

  @Post('query')
  query(@Body() body: { question: string }) {
    return this.rag.answer(body.question);
  }
}

Step 4 — Try it

curl -s localhost:3000/rag/ingest -H 'content-type: application/json' \
  -d '{"docs":[{"id":"1","text":"HazelJS is an AI-native TypeScript backend framework."}]}'

curl -s localhost:3000/rag/query -H 'content-type: application/json' \
  -d '{"question":"What is HazelJS?"}'

HCEL one-liner (when the collection already exists)

If documents are already indexed under a named collection:

return this.ai.hazel
  .prompt('Answer with citations from context: {{input}}')
  .rag('product-docs')
  .execute(question);

See HCEL guide.

When to upgrade to Agentic RAG

Use standard RAG for simple Q&A over a curated corpus. Move to Agentic RAG when queries need planning, reflection, or multi-hop retrieval:

Production checklist

  • Swap MemoryVectorStore for a durable store (vector stores)
  • Add auth / rate limits (security, @hazeljs/auth)
  • Chunking + metadata strategy (RAG patterns)
  • Eval retrieval quality separately from generation (@hazeljs/eval)
  • Observability for LLM cost and latency

Next steps