DocumentationReference

Migrate from Express to HazelJS

Map Express routes and middleware to HazelJS controllers and modules — then add agents and RAG without bolting on a second stack.

Quick Reference

  • Purpose: Move from unopinionated Express APIs to HazelJS modules/DI, optionally with @hazeljs/ai, @hazeljs/agent, and @hazeljs/rag.
  • When to use: Express apps growing into structured TypeScript backends or AI product APIs.
  • Key concepts: @Controller, @HazelModule, providers, middleware → guards/middleware, AI packages.
  • Related: HazelJS vs Express, REST API tutorial, Migrate from NestJS.

Should you migrate?

Move to HazelJS when:

  • The Express app is accumulating ad-hoc structure (folders of routers, manual DI)
  • You need agents, RAG, or multi-provider LLMs as product features
  • You want Nest-style organization without Nest + LangChain glue

Keep Express when:

  • The service is a thin webhook or proxy
  • You deliberately want zero framework
  • There is no AI or growth path that needs modules

Concept mapping

ExpressHazelJS
app.get/post / Router@Controller + @Get / @Post
app.use(middleware)Middleware + guards / pipes
Manual service singletons@Injectable() providers + DI
express.json()Framework body parsing (see core docs)
openai SDK in a route@AITask / AIService / agents
Ad-hoc folders@HazelModule composition

Step 1 — Bootstrap

npm install @hazeljs/core
import { HazelApp, HazelModule, Controller, Get } from '@hazeljs/core';

@Controller({ path: '/health' })
class HealthController {
  @Get()
  ok() {
    return { ok: true };
  }
}

@HazelModule({ controllers: [HealthController] })
class AppModule {}

async function bootstrap() {
  const app = new HazelApp(AppModule);
  await app.listen(3000);
}

bootstrap();

Step 2 — Port a route

Express:

router.post('/chat', async (req, res) => {
  const message = req.body.message;
  res.json({ reply: message });
});

HazelJS:

import { Controller, Post, Body } from '@hazeljs/core';

@Controller({ path: '/chat' })
export class ChatController {
  @Post()
  chat(@Body() body: { message: string }) {
    return { reply: body.message };
  }
}

Step 3 — Add AI when ready

import { Controller, Post, Body } from '@hazeljs/core';
import { AITask } from '@hazeljs/ai';

@Controller({ path: '/chat' })
export class ChatController {
  @AITask({ provider: 'openai', model: 'gpt-4o' })
  @Post()
  async chat(@Body() body: { message: string }) {
    return body.message;
  }
}

For tools and memory, prefer @Agent / @Tool — see Agent package and Support agent example.

Incremental strategies

  1. New AI service — Keep Express for legacy; put agents on HazelJS behind your gateway.
  2. Route group rewrite — Move /v2/* to HazelJS controllers first.
  3. Strangler — Proxy paths gradually until Express can retire.

Checklist

  • Core app listens with a health controller
  • Critical routes mapped to controllers
  • Auth/rate limits replaced with packages or edge controls
  • AI surfaces use @hazeljs/ai or agents (not raw SDK sprawl)
  • Read HazelJS vs Express for positioning

Next steps