How the HazelJS core framework approaches the dev server, bootstrap, and production readiness—and what sets it apart from NestJS, Express, and Fastify.
Overview
HazelJS is a TypeScript-first Node.js framework built for AI-native and enterprise applications. The core (@hazeljs/core) provides dependency injection, decorator-based routing, middleware, and production features—without the weight of a custom runtime or complex build pipeline.
This post focuses on the core architecture: how the dev server works, what we did differently, and the competitive advantages that most Node.js frameworks lack out of the box.
1. The Dev Server: Simple and Fast
No Custom Dev Runtime
HazelJS does not ship a custom dev server or bundler. Instead, it uses standard Node.js tooling:
# From hazel new or package.json
"dev": "ts-node-dev --respawn --transpile-only src/index.ts"
Why this approach?
- Zero lock-in — Your app runs on plain Node.js. No framework-specific dev runtime to debug or upgrade.
-
Fast startup —
ts-node-devwith--transpile-onlyskips type-checking on reload; restarts are sub-second. -
Familiar — Developers already know
ts-node,ts-node-dev, ortsx. No new concepts. -
Flexible — Swap to
tsx,nodemon, ornode --watchwithout changing framework code.
What Competitors Do
| Framework | Dev Server Approach |
|---|---|
| NestJS |
nest start --watch (CLI wraps ts-node-dev or webpack) |
| Express | No built-in dev; you add nodemon or ts-node-dev yourself |
| Fastify | Same as Express—manual setup |
| HazelJS | CLI scaffolds ts-node-dev --respawn --transpile-only; hazel start -d runs it |
HazelJS keeps the dev experience explicit and minimal: you see exactly what runs, and you can change it.
2. Bootstrap and Application Lifecycle
Single Entry Point
import { HazelApp } from '@hazeljs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = new HazelApp(AppModule);
await app.listen(3000);
}
bootstrap();
No NestFactory, no createApp builder chain. You instantiate HazelApp with your root module and call listen().
What Happens on listen()
- Module resolution — The framework recursively collects controllers from the root module and its imports.
-
Route registration — All
@Controllerclasses are registered with the router; decorators define routes. -
HTTP server — A native Node.js
http.Serveris created (no Express dependency in core). -
Health endpoints —
/health,/ready, and/startupare registered automatically. - Graceful shutdown — Signal handlers (SIGTERM, SIGINT, SIGUSR2) are attached.
- Startup output — Local and network URLs are printed, plus health endpoint hints.
Built-in Health Probes (Kubernetes-Ready)
Most frameworks require you to add health checks manually. HazelJS ships them:
| Endpoint | Purpose | Use Case |
|---|---|---|
/health |
Liveness — is the process alive? | Kubernetes liveness probe |
/ready |
Readiness — can it accept traffic? | Kubernetes readiness probe |
/startup |
Startup — has initialization finished? | Kubernetes startup probe |
You get memory and event-loop checks by default. Custom checks are one line:
app.registerHealthCheck({
name: 'database',
check: async () => ({ status: 'healthy', message: 'DB connected' }),
critical: true,
timeout: 5000,
});
Competitive gap: Express and Fastify have no built-in health system. NestJS has @nestjs/terminus, but it's a separate package and requires setup. HazelJS includes it in core.
3. Graceful Shutdown
ShutdownManager
When the process receives SIGTERM or SIGINT, HazelJS runs registered shutdown handlers in order:
app.registerShutdownHandler({
name: 'http-server',
handler: async () => {
await app.close();
},
timeout: 10000,
});
The framework:
- Handles SIGTERM, SIGINT, SIGUSR2
- Handles uncaughtException and unhandledRejection (logs, runs handlers, then exits)
- Enforces per-handler timeouts to avoid hanging
- Has a global shutdown timeout (default 30s) before forcing exit
The HTTP server is registered as a shutdown handler automatically when listen() is called, so connections drain before the process exits.
Competitive gap: Express has no built-in graceful shutdown. Fastify has close(), but you wire signals yourself. NestJS has enableShutdownHooks(), but it's opt-in and less configurable. HazelJS makes it first-class.
4. Native Node.js HTTP Server: What No Other Framework Does
The Key Difference
HazelJS uses Node.js http.Server directly—no Express, no Fastify, no third-party HTTP abstraction. The request handler is a single callback that receives IncomingMessage and ServerResponse from Node's built-in http module:
import { Server, IncomingMessage, ServerResponse } from 'http';
this.server = new Server(async (req: IncomingMessage, res: ServerResponse) => {
// Direct handling: route match → controller → response
});
How Other Frameworks Work
| Framework | HTTP Layer | What It Adds |
|---|---|---|
| Express | Wraps Node http
|
Middleware stack (every request passes through body-parser, cookie-parser, etc.), routing layer, req/res extensions |
| NestJS | Uses Express (or Fastify) by default | Nest's own middleware + Express/Fastify stack + DI, guards, interceptors |
| Fastify | Custom HTTP abstraction | Schema validation, plugin system, request/reply wrappers—faster than Express but still an abstraction |
| HazelJS | Raw Node.js http.Server |
Router + DI; no middleware stack, no extra HTTP wrapper |
Express and NestJS sit on top of Node's HTTP with multiple abstraction layers. Fastify is faster than Express but still introduces its own request/response model. HazelJS stays at the Node.js HTTP level and adds only what's needed for routing and DI.
Performance Edges
1. No middleware chain overhead
Express runs every request through a chain of middleware functions. Even a "hello world" app typically has body-parser, cookie-parser, and other middleware. Each adds a function call and potential I/O. HazelJS has no generic middleware chain—body parsing runs only for POST/PUT/PATCH and only when the route needs it.
2. Lazy body parsing
Body parsing happens only for methods that expect a body (POST, PUT, PATCH). GET and DELETE requests skip it entirely. Express's body-parser often runs on every request or is configured globally.
3. Minimal abstraction
HazelJS uses Node's native IncomingMessage and ServerResponse. There's no req.body or res.json() until the framework explicitly parses or writes. The HttpResponse wrapper is thin—it just adds status(), json(), and setHeader() for controller convenience. No deep prototype chains or proxy objects.
4. Smaller bundle and faster cold start
@hazeljs/core has no Express dependency. That means:
- Fewer modules to load at startup
- No Express internals (router, Layer, Route, etc.)
- Lower memory footprint
Benchmarks typically show native Node HTTP and Fastify at ~95k–105k req/s vs Express at ~23k req/s for simple endpoints. HazelJS aligns with the native-HTTP approach: minimal layers between the TCP socket and your handler.
5. Serverless-friendly
Because core doesn't depend on Express, the same application can run on AWS Lambda or Google Cloud Functions via @hazeljs/serverless without pulling in Express. The serverless adapter invokes the router directly with an event object—no HTTP server at all in serverless mode.
Trade-offs
HazelJS does not provide Express's vast middleware ecosystem (e.g. express-session, passport). For those, you'd use dedicated packages (e.g. @hazeljs/auth for JWT) or integrate Express as a nested app if needed. The design choice is: optimize for performance and simplicity in core, and add abstractions only where they're required.
5. Request Timeout and CORS
Request Timeout
Long-running requests can be capped:
app.setRequestTimeout(30000); // 30 seconds default
If a handler exceeds the timeout, the client gets 408 Request Timeout and the handler is not left hanging.
CORS
CORS is built-in and configurable:
app.enableCors({
origin: 'https://myapp.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
});
No cors package to add or configure manually.
6. Startup Experience
When the server starts, HazelJS prints:
Server listening on:
→ Local: http://localhost:3000
→ Network: http://192.168.1.10:3000
Health endpoints:
→ /health - Liveness probe
→ /ready - Readiness probe
→ /startup - Startup probe
You get both local and network URLs (useful for mobile or LAN testing) and a reminder of the health endpoints. Many frameworks only log Listening on port 3000.
7. Dependency Injection and Scopes
The DI container supports three scopes:
| Scope | Behavior |
|---|---|
| Singleton | One instance per process (default) |
| Transient | New instance per resolution |
| Request | One instance per HTTP request |
Circular dependency detection is built in. Providers can use useClass, useValue, or useFactory.
Competitive note: NestJS has a richer DI system. HazelJS keeps it simple but sufficient for most apps—and avoids the complexity of dynamic modules and custom providers for common cases.
8. Module System
Modules are the unit of organization:
@HazelModule({
controllers: [UserController],
providers: [UserService],
imports: [AuthModule, CacheModule],
exports: [UserService],
})
export class UserModule {}
The framework recursively collects controllers from imports, so you can compose features without manual registration. This is similar to NestJS but with a lighter metadata model.
9. What Competitors Don’t Have (Summary)
| Feature | Express | Fastify | NestJS | HazelJS |
|---|---|---|---|---|
| Built-in health probes | ❌ | ❌ | Via Terminus | ✅ In core |
| Graceful shutdown | ❌ | Manual | Opt-in | ✅ First-class |
| Request timeout | ❌ | Plugin | Manual | ✅ Built-in |
| CORS built-in | ❌ | Plugin | Via config | ✅ Built-in |
| DI container | ❌ | ❌ | ✅ | ✅ |
| Decorator routing | ❌ | ❌ | ✅ | ✅ |
| Module system | ❌ | ❌ | ✅ | ✅ |
Native Node http.Server (no Express/Fastify) |
N/A | Custom (not raw http) | ❌ (Express/Fastify) | ✅ |
| Dev server | Manual | Manual | CLI | CLI + ts-node-dev |
| AI/Agent packages | ❌ | ❌ | Community | ✅ First-party |
10. CLI and Scaffolding
The @hazeljs/cli provides:
-
hazel new <app>— Interactive project creation with package selection -
hazel generate controller|service|module|...— 21 generators for all packages -
hazel start -d— Runnpm run dev -
hazel build -w— Watch mode builds -
hazel add— Add HazelJS packages interactively
Generated projects include:
-
ts-node-devfor hot reload -
.envand.env.example - Health endpoints and graceful shutdown wired by default
Summary
HazelJS core is designed for:
-
Simplicity — No custom dev runtime; standard Node.js and
ts-node-dev. - Production readiness — Health probes, graceful shutdown, and request timeout out of the box.
- Explicitness — You see the bootstrap flow; no magic.
- Modularity — Install only what you need; core stays lean.
If you're building AI-native backends or want a framework that ships production features from day one, HazelJS is worth a look. Clone the CSR agent example or run hazel new my-app -i to get started.
Top comments (0)