HazelJS Core Package
@hazeljs/core is the HTTP / DI kernel every HazelJS app boots. Modules, controllers, routing, middleware — Agent OS (@hazeljs/agent) runs in the same HazelApp. Other packages depend on core.
Quick Reference
- Purpose:
@hazeljs/coreis the foundation package for HazelJS. It provides the module system, dependency injection container, decorator-based routing, request pipeline (middleware, guards, interceptors, pipes, exception filters), application bootstrap, lifecycle hooks, health checks, and testing utilities. - When to use: Every HTTP HazelJS app needs
@hazeljs/core. Agent OS apps use it as the same DI container as agents. Install it first for any HazelJS project. - Key concepts: HazelApp, HazelModule, Controller, Service, Injectable, DI Container, Guards, Interceptors, Pipes, Exception Filters, Middleware, Scopes.
- Inputs: HTTP requests routed to controller methods via decorators.
- Outputs: JSON responses, status codes, response headers.
- Dependencies: Node.js 18+, TypeScript with
experimentalDecorators: true.@hazeljs/coreloadsreflect-metadataautomatically — do not install or import it in your app.emitDecoratorMetadatais optional for most apps. - Common patterns: Controller delegates to Service; Service contains business logic; Module groups Controller + Service; Guard checks auth before handler; Interceptor wraps handler for logging/caching; Pipe validates input DTO.
- Common mistakes: Forgetting to register a provider in a module's
providersarray; forgetting to export a provider that another module needs; putting business logic in controllers instead of services; using@Injectable()for business services instead of@Service(); not enablingexperimentalDecoratorsin tsconfig.json; installing or importingreflect-metadatain application code (@hazeljs/corealready does this).
Installation
npm install @hazeljs/core
Do not add reflect-metadata as an application dependency. @hazeljs/core declares it and imports it on load, so generated HazelJS apps should not list or import it.
Peer dependencies (for validation):
npm install class-validator class-transformer
TypeScript configuration required in tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
Architecture Mental Model
Every HTTP request in a HazelJS application flows through the @hazeljs/core request pipeline:
HTTP Request → Router → Middleware → Guards → Interceptors (before) → Pipes → Controller Handler → Interceptors (after) → Response
↓ (on error)
Exception Filters → Error Response
The module system organizes code:
AppModule (root)
├── imports: [UserModule, AuthModule, AIModule]
│
├── UserModule
│ ├── controllers: [UserController]
│ ├── providers: [UserService]
│ └── exports: [UserService]
│
└── AuthModule
├── controllers: [AuthController]
├── providers: [AuthService, JwtService]
└── exports: [AuthService]
The DI container resolves dependencies:
UserController ← constructor(UserService) ← DI Container resolves UserService automatically
What HazelJS Core Provides
| Feature | Description |
|---|---|
HazelApp | Application bootstrap class. Creates and starts the HTTP server. |
@HazelModule | Module decorator. Groups controllers, providers, imports, and exports. |
@Controller | Controller decorator. Defines route prefix and registers HTTP handlers. |
@Get, @Post, @Put, @Delete, @Patch | HTTP method decorators for controller methods. |
@Body, @Param, @Query, @Headers, @Req, @Res | Parameter decorators for extracting request data. |
@Service, @Injectable | Provider decorators for dependency injection registration. |
@Inject | Explicit injection by token (string, symbol, or class). |
@UseGuards | Apply guard classes to controllers or methods for authorization. |
@UseInterceptors | Apply interceptor classes to controllers or methods. |
@UsePipes | Apply pipe classes for input validation and transformation. |
@HttpCode, @Header, @Redirect | Response decorators for status codes, headers, and redirects. |
@Public, @SkipAuth | Mark routes as public (skip authentication guards). |
@Timeout, @Retry | Per-route timeout and retry decorators. |
ValidationPipe, ParseIntPipe | Built-in pipes for DTO validation and type conversion. |
HttpExceptionFilter | Built-in exception filter for HTTP errors. |
PerformanceService | Core service for managing traces, spans, and metrics. |
@Trace | Decorator for automatic performance monitoring and tracing via OTel. |
EnhancedErrors | Helper for creating unified, type-safe API error responses. |
Container | Singleton DI container with resolve(), register(), registerProvider(). |
Test | Testing utility for creating isolated test modules with provider overrides. |
Quick Start: Minimal HazelJS Application
// File: src/main.ts
import { HazelApp, HazelModule, Controller, Get, Service } from '@hazeljs/core';
@Service()
class HealthService {
getStatus() {
return { ok: true, timestamp: new Date().toISOString() };
}
}
@Controller('health')
class HealthController {
constructor(private readonly health: HealthService) {}
@Get()
getHealth() {
return this.health.getStatus();
}
}
@HazelModule({
controllers: [HealthController],
providers: [HealthService],
})
class AppModule {}
async function bootstrap() {
const app = new HazelApp(AppModule);
await app.listen(3000);
}
bootstrap();
// GET http://localhost:3000/health → { "ok": true, "timestamp": "2024-01-01T00:00:00.000Z" }
CORS (enableCors)
HazelApp.enableCors never reflects an arbitrary request Origin. Use a string, allowlist array, or predicate:
app.enableCors({
origin: process.env.ALLOWED_ORIGINS?.split(',') ?? [
'https://app.example.com',
],
credentials: true,
});
// Or: origin: (origin) => origin.endsWith('.example.com')
- Allowed non-
*origins setAccess-Control-Allow-Originto the request Origin andVary: Origin. - Preflight (
OPTIONS) for an unlisted origin returns 403{ message: 'CORS origin not allowed' }. - Full options and patterns: Middleware — CORS.
Recipes
Recipe: Build a CRUD REST API
// File: src/user/user.service.ts
import { Service } from '@hazeljs/core';
interface User {
id: number;
name: string;
email: string;
}
@Service()
export class UserService {
private users: User[] = [];
findAll(): User[] {
return this.users;
}
findOne(id: number): User | undefined {
return this.users.find(u => u.id === id);
}
create(data: { name: string; email: string }): User {
const user = { id: this.users.length + 1, ...data };
this.users.push(user);
return user;
}
remove(id: number): boolean {
const idx = this.users.findIndex(u => u.id === id);
if (idx === -1) return false;
this.users.splice(idx, 1);
return true;
}
}
// File: src/user/user.controller.ts
import { Controller, Get, Post, Delete, Param, Body, HttpCode, NotFoundException } from '@hazeljs/core';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
findAll() {
return this.userService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
const user = this.userService.findOne(parseInt(id, 10));
if (!user) throw new NotFoundException(`User #${id} not found`);
return user;
}
@Post()
@HttpCode(201)
create(@Body() body: { name: string; email: string }) {
return this.userService.create(body);
}
@Delete(':id')
@HttpCode(204)
remove(@Param('id') id: string) {
const removed = this.userService.remove(parseInt(id, 10));
if (!removed) throw new NotFoundException(`User #${id} not found`);
}
}
// File: src/user/user.module.ts
import { HazelModule } from '@hazeljs/core';
import { UserController } from './user.controller';
import { UserService } from './user.service';
@HazelModule({
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
// File: src/main.ts
import { HazelApp, HazelModule } from '@hazeljs/core';
import { UserModule } from './user/user.module';
@HazelModule({ imports: [UserModule] })
class AppModule {}
async function bootstrap() {
const app = new HazelApp(AppModule);
await app.listen(3000);
}
bootstrap();
HazelJS Core Request Pipeline
Step 1: Routing
The HazelJS router matches the HTTP method and URL path to a controller method registered with @Controller and @Get/@Post/@Put/@Delete/@Patch decorators.
Step 2: Middleware
HazelJS middleware runs before guards. Built-in HazelJS middleware includes CorsMiddleware, LoggerMiddleware, SecurityHeadersMiddleware, RateLimitMiddleware, and TimeoutMiddleware.
Step 3: Guards
HazelJS guards implement CanActivate and decide whether a request is allowed. Guards run after routing and before the handler. Use guards for authentication and authorization.
Step 4: Interceptors
HazelJS interceptors implement Interceptor and wrap the handler execution. Interceptors run before and after the handler. Use interceptors for logging, caching, response transformation, and retry logic.
Step 5: Pipes
HazelJS pipes implement PipeTransform and validate or transform individual input values (route params, query params, request body). The built-in ValidationPipe validates DTOs using class-validator.
Step 6: Controller Handler
The controller method executes and returns data. HazelJS automatically serializes the return value as JSON.
Step 7: Exception Filters
If any step throws an error, HazelJS exception filters catch the error and format the HTTP error response. The built-in HttpExceptionFilter handles HttpError subclasses.
HazelJS Core Dependency Injection
The HazelJS DI container supports three provider scopes:
| Scope | Behavior | When to use |
|---|---|---|
singleton (default) | One instance shared across the entire application | Stateless services, configuration, shared resources |
transient | New instance for every injection point | Per-consumer state, lightweight services |
request | New instance for each HTTP request | Request-specific context, user state per request |
Decorator Conventions
| Decorator | Use for |
|---|---|
@Service() | Business logic services, repositories, data access |
@Injectable() | Guards, interceptors, pipes, framework constructs |
@Inject(token) | Explicit injection by string/symbol token |
When to Use HazelJS Core Features
| Feature | Use this when... | Avoid this when... |
|---|---|---|
| Guard | Checking authentication or authorization before a handler | Transforming response data (use Interceptor) |
| Interceptor | Wrapping handler execution for logging, caching, or retry | Checking auth (use Guard) |
| Pipe | Validating or transforming a single input value | Wrapping the whole handler (use Interceptor) |
| Middleware | Running logic before routing (CORS, body parsing) | Needing access to the matched route or handler metadata (use Guard) |
| Exception Filter | Formatting error responses consistently | Normal response handling |
Recipe: Add Validation to a POST Endpoint
// File: src/user/create-user.dto.ts
import { IsString, IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
}
// File: src/user/user.controller.ts
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@hazeljs/core';
import { CreateUserDto } from './create-user.dto';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
@UsePipes(new ValidationPipe())
create(@Body(CreateUserDto) dto: CreateUserDto) {
return this.userService.create(dto);
}
}
Recipe: Add an Auth Guard
// File: src/auth/auth.guard.ts
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@hazeljs/core';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const token = req.headers?.authorization;
if (!token) throw new UnauthorizedException('Missing authorization header');
// Validate token here
return true;
}
}
// File: src/user/user.controller.ts
import { Controller, Get, UseGuards } from '@hazeljs/core';
import { AuthGuard } from '../auth/auth.guard';
@Controller('users')
@UseGuards(AuthGuard)
export class UserController {
@Get()
findAll() {
return []; // protected endpoint
}
}
Recipe: Write a Unit Test
// File: src/user/user.service.spec.ts
import { Test } from '@hazeljs/core';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [UserService],
}).compile();
service = module.get(UserService);
});
it('should create a user', () => {
const user = service.create({ name: 'Alice', email: 'alice@example.com' });
expect(user.id).toBe(1);
expect(user.name).toBe('Alice');
});
it('should find all users', () => {
service.create({ name: 'Alice', email: 'alice@example.com' });
expect(service.findAll()).toHaveLength(1);
});
});
HazelJS Core Error Classes
| Error Class | Alias | HTTP Status | Usage |
|---|---|---|---|
BadRequestError | BadRequestException | 400 | Invalid input, validation failure |
UnauthorizedError | UnauthorizedException | 401 | Missing or invalid authentication |
ForbiddenError | ForbiddenException | 403 | Authenticated but insufficient permissions |
NotFoundError | NotFoundException | 404 | Resource not found |
RequestTimeoutError | — | 408 | Handler exceeded timeout |
ConflictError | ConflictException | 409 | Resource conflict (duplicate) |
InternalServerError | InternalServerErrorException | 500 | Unexpected server error |
Unified Error Responses with EnhancedErrors
HazelJS 0.3.0 introduces EnhancedErrors, a helper for creating consistent error responses across your entire application.
import { EnhancedErrors } from '@hazeljs/core';
// In a service or controller
throw EnhancedErrors.notFound('User not found in our database');
throw EnhancedErrors.badRequest('Validation failed: name is required');
throw EnhancedErrors.internal('Database connection failed');
Performance Monitoring & Tracing
HazelJS provides native support for performance monitoring and OpenTelemetry-based tracing via PerformanceService and the @Trace decorator.
Using @Trace
Automatically measure method execution time and report to your observability backend.
import { Service, Trace } from '@hazeljs/core';
@Service()
export class DataService {
@Trace('data.expensive_operation')
async expensiveOperation() {
// Execution time is tracked automatically
return await this.process();
}
}
Programmatic Tracing
import { Service, PerformanceService } from '@hazeljs/core';
@Service()
export class PaymentService {
constructor(private readonly performance: PerformanceService) {}
async checkout() {
const span = this.performance.startSpan('payment.checkout');
try {
await this.processPayment();
} finally {
span.end();
}
}
}
HazelJS Core API Reference
For complete symbol-level documentation, see:
- API Reference — Full list of decorators, classes, interfaces, and types
- Controllers Guide — Routing, parameter extraction, response handling
- Providers Guide — Dependency injection, scopes, custom providers
- Modules Guide — Module organization, imports, exports, dynamic modules
- Guards Guide — Authentication and authorization
- Interceptors Guide — Wrapping handler execution
- Pipes Guide — Input validation and transformation
- Exception Filters Guide — Error handling
- Middleware Guide — Cross-cutting concerns
Related Pages
- Config Package — Type-safe configuration for HazelJS applications
- Auth Package — Authentication, JWT, and role-based guards
- Swagger Package — OpenAPI documentation generation
- Cache Package — Response caching with Redis or in-memory
- AI Package — LLM provider integration (builds on
@hazeljs/core)
Prerequisites
- Installation — Install Node.js, TypeScript, and
@hazeljs/core
Used Together With
@hazeljs/ai— AI integration requires@hazeljs/corefor modules and DI@hazeljs/agent— Agent Runtime requires@hazeljs/corefor modules and DI@hazeljs/auth— Authentication guards require@hazeljs/coreguard system@hazeljs/config— Configuration requires@hazeljs/coremodule system