@hazeljs/core
AI-Native Foundation - DI, routing, and decorators built for intelligent applications.
Part of the HazelJS AI-Native Backend Framework. Stop wiring boilerplate. Build APIs with dependency injection, decorator-based routing, and middleware that just works. TypeScript-first, production-ready, zero Express dependency.
🚀 Trusted by 200K+ monthly downloads • 37+ GitHub stars • 15+ daily active developers

Why @hazeljs/core?
Built as the foundation for AI-native applications - not just another framework. When you combine @hazeljs/core with our AI packages (@hazeljs/ai, @hazeljs/agent, @hazeljs/rag), you get a complete stack for intelligent backends without glue code.
Perfect for:
- AI startups building production agents
- Teams replacing NestJS/Express with AI-native backends
- Developers who want TypeScript-first architecture
- Projects needing dependency injection without complexity
Features
- 🎯 Dependency Injection - Advanced DI with Singleton, Transient, and Request scopes
- 🎨 Decorator-Based API - Clean, intuitive programming model
- 🛣️ Routing - Express-based routing with parameter extraction
- 🔌 Middleware Support - Global and route-level middleware
- 🛡️ Guards & Interceptors - Request validation and transformation
- 🔧 Pipes - Data transformation and validation
- 🏥 Health Checks - Built-in liveness, readiness, and startup probes
- 🛑 Graceful Shutdown - Proper cleanup and connection draining
- 📊 Logging - Winston-based structured logging
- ✅ Validation - Automatic request validation with class-validator
- 🧪 Testing Utilities - Full testing support with TestingModule
Installation
npm install @hazeljs/core
Quick Start
1. Create a Controller
import { Controller, Get, Post, Body, Param } from '@hazeljs/core';
@Controller('/users')
export class UserController {
@Get()
findAll() {
return { users: [] };
}
@Get('/:id')
findOne(@Param('id') id: string) {
return { id, name: 'John Doe' };
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return { message: 'User created', data: createUserDto };
}
}
2. Create a Service
import { Injectable } from '@hazeljs/core';
@Injectable()
export class UserService {
private users = [];
findAll() {
return this.users;
}
findOne(id: string) {
return this.users.find((user) => user.id === id);
}
create(data: any) {
const user = { id: Date.now().toString(), ...data };
this.users.push(user);
return user;
}
}
3. Create a Module
import { HazelModule } from '@hazeljs/core';
import { UserController } from './user.controller';
import { UserService } from './user.service';
@HazelModule({
controllers: [UserController],
providers: [UserService],
})
export class AppModule {}
4. Bootstrap the Application
import { HazelApp, BuiltInHealthChecks } from '@hazeljs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await HazelApp.create(AppModule);
app.registerHealthCheck(BuiltInHealthChecks.memoryCheck(500));
app.registerHealthCheck(BuiltInHealthChecks.eventLoopCheck(100));
app.registerShutdownHandler({
name: 'cleanup',
handler: async () => {
console.log('Cleaning up resources...');
},
timeout: 5000,
});
await app.listen(3000);
}
bootstrap();
Dependency Injection
Scopes
import { Injectable, Scope } from '@hazeljs/core';
@Injectable()
export class SingletonService {}
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {}
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {}
Constructor Injection
@Injectable()
export class OrderService {
constructor(
private userService: UserService,
private paymentService: PaymentService
) {}
async createOrder(userId: string) {
const user = await this.userService.findOne(userId);
const payment = await this.paymentService.process(user);
return { user, payment };
}
}
Routing & Decorators
HTTP Methods
import { Controller, Get, Post, Put, Delete, Patch } from '@hazeljs/core';
@Controller('/api')
export class ApiController {
@Get('/items')
getItems() {}
@Post('/items')
createItem() {}
@Put('/items/:id')
updateItem() {}
@Patch('/items/:id')
patchItem() {}
@Delete('/items/:id')
deleteItem() {}
}
Parameter Decorators
import { Controller, Get, Post, Param, Query, Body, Headers, Req, Res } from '@hazeljs/core';
@Controller('/users')
export class UserController {
@Get('/:id')
findOne(@Param('id') id: string, @Query('include') include?: string) {
return { id, include };
}
@Post()
create(@Body() createUserDto: CreateUserDto, @Headers('authorization') auth: string) {
return { data: createUserDto, auth };
}
@Get('/raw')
rawAccess(@Req() req: Request, @Res() res: Response) {
res.json({ message: 'Direct access to req/res' });
}
}
Custom metadata and parameter decorators
Attach custom metadata for guards or other layers with SetMetadata / getMetadata, and build your own parameter decorators with createParamDecorator:
import { SetMetadata, getMetadata, createParamDecorator } from '@hazeljs/core';
@SetMetadata('roles', ['admin'])
class AdminController {}
const CurrentUser = createParamDecorator((_req, ctx) => ctx.user);
@Get('profile')
getProfile(@CurrentUser user: User) {
return user;
}
Use the custom param decorator without parentheses: @CurrentUser. See the full API reference for ParamDecoratorContext and CUSTOM_METADATA_PREFIX.
Middleware
Global Middleware
import { Middleware, type RequestContext } from '@hazeljs/core';
@Injectable()
export class LoggerMiddleware implements Middleware {
async use(context: RequestContext, next: () => Promise<unknown>) {
console.log(`${context.method} ${context.url}`);
return next();
}
}
const app = await HazelApp.create(AppModule);
app.useGlobalMiddleware(new LoggerMiddleware());
Route-Level Middleware
import { Controller, Get, UseMiddleware } from '@hazeljs/core';
@Controller('/admin')
@UseMiddleware(AuthMiddleware)
export class AdminController {
@Get('/dashboard')
@UseMiddleware(RoleCheckMiddleware)
getDashboard() {
return { data: 'admin dashboard' };
}
}
Global exception filters
import { HazelApp, HttpExceptionFilter, type ExceptionFilter } from '@hazeljs/core';
const app = await HazelApp.create(AppModule);
app.useGlobalExceptionFilter(new HttpExceptionFilter());
Guards
import { CanActivate, ExecutionContext, Injectable, UseGuards } from '@hazeljs/core';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
const token = context.request.headers.authorization;
return this.validateToken(token);
}
private validateToken(token: string | undefined): boolean {
return !!token;
}
}
@Controller('/protected')
@UseGuards(AuthGuard)
export class ProtectedController {
@Get()
getData() {
return { message: 'Protected data' };
}
}
Interceptors
import { Interceptor, Injectable, UseInterceptors, type RequestContext } from '@hazeljs/core';
@Injectable()
export class TransformInterceptor implements Interceptor {
async intercept(context: RequestContext, next: () => Promise<unknown>) {
const result = await next();
return {
data: result,
timestamp: new Date().toISOString(),
path: context.url,
};
}
}
@Controller('/api')
@UseInterceptors(TransformInterceptor)
export class ApiController {}
Pipes
import { Injectable, PipeTransform, type RequestContext } from '@hazeljs/core';
@Injectable()
export class NonEmptyPipe implements PipeTransform<string, string> {
transform(value: string, _context: RequestContext): string {
if (!value) {
throw new Error('Value is required');
}
return value;
}
}
Validation
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
@IsString()
name: string;
}
@Controller('/users')
export class UserController {
@Post()
create(@Body() createUserDto: CreateUserDto) {
return createUserDto;
}
}
Health Checks
import { HazelApp, BuiltInHealthChecks } from '@hazeljs/core';
const app = await HazelApp.create(AppModule);
app.registerHealthCheck(BuiltInHealthChecks.memoryCheck(500));
app.registerHealthCheck(BuiltInHealthChecks.eventLoopCheck(100));
app.registerHealthCheck({
name: 'database',
check: async () => {
try {
await database.ping();
return { status: 'healthy' };
} catch (error) {
return {
status: 'unhealthy',
message: error.message,
};
}
},
critical: true,
timeout: 3000,
});
Graceful Shutdown
const app = await HazelApp.create(AppModule);
app.registerShutdownHandler({
name: 'database',
handler: async () => {
await database.disconnect();
console.log('Database disconnected');
},
timeout: 5000,
});
app.registerShutdownHandler({
name: 'cache',
handler: async () => {
await redis.quit();
console.log('Redis connection closed');
},
timeout: 3000,
});
await app.listen(3000);
Exception Handling
import { ExceptionFilter, ExceptionContext } from '@hazeljs/core';
@Injectable()
export class HttpExceptionFilter implements ExceptionFilter {
catch(error: Error, context: ExceptionContext) {
const response = context.response;
response.status(500).json({
statusCode: 500,
message: error.message,
timestamp: new Date().toISOString(),
path: context.request.url,
});
}
}
app.useGlobalExceptionFilter(new HttpExceptionFilter());
Testing
import { TestingModule } from '@hazeljs/core';
import { UserController } from './user.controller';
import { UserService } from './user.service';
describe('UserController', () => {
let module: TestingModule;
let controller: UserController;
let service: UserService;
beforeEach(async () => {
module = await TestingModule.create({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: {
findAll: jest.fn().mockResolvedValue([]),
},
},
],
});
controller = module.get(UserController);
service = module.get(UserService);
});
afterEach(async () => {
await module.close();
});
it('should return all users', async () => {
const result = await controller.findAll();
expect(result).toEqual([]);
expect(service.findAll).toHaveBeenCalled();
});
});
Logging
HTTP requests are logged in following format: METHOD path status duration (e.g. GET /api/health 200 3ms).
LOG_LEVEL | info | Log level (error, warn, info, debug) |
LOG_HTTP | true | Set to false to disable HTTP request logs |
LOG_ENABLED | true | Set to false to disable all logging |
Startup and registration logs (controllers, routes, providers) are at debug level. Use LOG_LEVEL=debug for troubleshooting.
API Reference
Decorators
@HazelModule(options) - Define a module
@Controller(path) - Define a controller
@Injectable(options?) - Mark class as injectable
@Get(path?), @Post(path?), @Put(path?), @Delete(path?), @Patch(path?) - HTTP methods
@Param(name), @Query(name), @Body(), @Headers(name), @Req(), @Res(), @Ip(), @Host() - Parameter extraction
@UseGuards(...guards), @UseInterceptors(...interceptors), @UsePipes(...pipes) - Apply guards, interceptors, pipes
@Public() / @SkipAuth() - Mark route as public
@Timeout(ms), @Retry(options), @Optional(), @Session() - Per-route behavior
@ApiTags(...tags), @ApiOperation(options) - OpenAPI metadata
- SetMetadata(key, value) - Attach custom metadata to a class or method (read with
getMetadata(key, target, propertyKey?))
- createParamDecorator(resolve) - Build custom parameter decorators that inject values from
(req, context, container)
Classes
HazelApp - Main application class
TestingModule - Testing utilities
Logger - Logging service
Examples
See the examples directory for complete working examples.
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
Apache 2.0 © HazelJS
Links