@hazeljs/serverless
v1.0.2
Published
Serverless adapters (AWS Lambda, Google Cloud Functions) for HazelJS framework
Downloads
1,459
Maintainers
Readme
@hazeljs/serverless
Deploy to Lambda or Cloud Functions. Zero code changes.
Same HazelJS app, same controllers, same routing. Wrap it and ship. Cold start optimization, request mapping, env handling — built in.
Features
- ☁️ AWS Lambda - Deploy to AWS Lambda
- 🌐 Google Cloud Functions - Deploy to GCP
- 🔄 Zero Config - No code changes needed
- 🎯 Cold Start Optimization - Minimize cold start times
- 📊 Request/Response Mapping - Automatic event transformation
- 🔐 Environment Variables - Seamless config management
- 🎨 Decorator Support -
@Serverlessdecorator - 📦 Bundle Optimization - Tree-shaking and minification
Installation
npm install @hazeljs/serverlessAWS Lambda
Quick Start
// lambda.ts
import { createLambdaHandler } from '@hazeljs/serverless';
import { AppModule } from './app.module';
export const handler = createLambdaHandler(AppModule);With Options
import { createLambdaHandler } from '@hazeljs/serverless';
import { AppModule } from './app.module';
export const handler = createLambdaHandler(AppModule, {
// Enable binary response
binaryMimeTypes: ['image/*', 'application/pdf'],
// Custom initialization
onInit: async (app) => {
console.log('Lambda initialized');
},
// Custom error handling
onError: (error) => {
console.error('Lambda error:', error);
},
});Deployment
Using AWS SAM
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
HazelFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: dist/
Handler: lambda.handler
Runtime: nodejs20.x
MemorySize: 512
Timeout: 30
Environment:
Variables:
NODE_ENV: production
DATABASE_URL: !Ref DatabaseUrl
Events:
ApiEvent:
Type: Api
Properties:
Path: /{proxy+}
Method: ANYDeploy:
npm run build
sam build
sam deploy --guidedUsing Serverless Framework
# serverless.yml
service: hazeljs-app
provider:
name: aws
runtime: nodejs20.x
stage: ${opt:stage, 'dev'}
region: us-east-1
environment:
NODE_ENV: ${self:provider.stage}
DATABASE_URL: ${env:DATABASE_URL}
functions:
api:
handler: dist/lambda.handler
events:
- http:
path: /{proxy+}
method: ANY
cors: true
package:
individually: true
patterns:
- '!node_modules/**'
- 'node_modules/@hazeljs/**'
- 'dist/**'Deploy:
npm run build
serverless deployGoogle Cloud Functions
Quick Start
// index.ts
import { createCloudFunctionHandler } from '@hazeljs/serverless';
import { AppModule } from './app.module';
export const hazelApp = createCloudFunctionHandler(AppModule);With Options
import { createCloudFunctionHandler } from '@hazeljs/serverless';
import { AppModule } from './app.module';
export const hazelApp = createCloudFunctionHandler(AppModule, {
// Custom initialization
onInit: async (app) => {
console.log('Cloud Function initialized');
},
// Custom error handling
onError: (error) => {
console.error('Cloud Function error:', error);
},
});Event handler (Pub/Sub, Storage): createCloudFunctionEventHandler(Module) returns a stub that initializes the app and logs the event but does not route to user code. Implement your own event handling and call it from that handler, or use a separate entrypoint for event-triggered functions.
Deployment
Using gcloud CLI
# Build
npm run build
# Deploy
gcloud functions deploy hazeljs-app \
--runtime nodejs20 \
--trigger-http \
--allow-unauthenticated \
--entry-point hazelApp \
--source dist \
--set-env-vars NODE_ENV=production,DATABASE_URL=your-db-urlUsing Cloud Functions YAML
# function.yaml
runtime: nodejs20
entryPoint: hazelApp
environmentVariables:
NODE_ENV: production
DATABASE_URL: ${DATABASE_URL}Deploy:
gcloud functions deploy hazeljs-app --config function.yamlDecorator-Based Optimization
Mark controllers (classes) for serverless optimization with @Serverless:
import { Controller, Get } from '@hazeljs/core';
import { Serverless } from '@hazeljs/serverless';
@Controller('/api')
@Serverless({ memory: 512, timeout: 30, coldStartOptimization: true })
export class ApiController {
@Get('/hello')
hello() {
return { message: 'Hello from serverless!' };
}
}Note: The decorator is class-level only. Method-level
@Serverless({ optimize, cache })and per-routebinaryResponseare planned for a future release.
Cold Start Optimization
ColdStartOptimizer preloads the DI container and built-in modules (http, https, crypto, buffer); behavior is best-effort and may vary by Node version. KeepAliveHelper runs an interval but does not perform an actual HTTP request—use provisioned concurrency, a cron job, or implement fetch/http.get in the interval to warm the function.
Minimize Bundle Size
// Use dynamic imports for heavy dependencies
@Get('/heavy')
async heavyOperation() {
const { processData } = await import('./heavy-processor');
return processData();
}Connection Pooling
// Reuse database connections across invocations
let cachedDb: any = null;
async function connectToDatabase() {
if (cachedDb) {
return cachedDb;
}
cachedDb = await createDatabaseConnection();
return cachedDb;
}
@Injectable()
export class DatabaseService {
async query(sql: string) {
const db = await connectToDatabase();
return db.query(sql);
}
}Provisioned Concurrency (AWS Lambda)
# template.yaml
Resources:
HazelFunction:
Type: AWS::Serverless::Function
Properties:
# ... other properties
ProvisionedConcurrencyConfig:
ProvisionedConcurrentExecutions: 5Environment-Specific Configuration
// config/serverless.config.ts
export const serverlessConfig = {
development: {
timeout: 30,
memorySize: 512,
},
production: {
timeout: 60,
memorySize: 1024,
provisionedConcurrency: 5,
},
};
// lambda.ts
const config = serverlessConfig[process.env.NODE_ENV || 'development'];
export const handler = createLambdaHandler(AppModule, {
timeout: config.timeout,
});Request/Response Handling
AWS Lambda Event Types
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
// Access raw Lambda event
@Get('/lambda-info')
getLambdaInfo(@Req() req: any) {
const event: APIGatewayProxyEvent = req.apiGateway.event;
return {
requestId: event.requestContext.requestId,
sourceIp: event.requestContext.identity.sourceIp,
userAgent: event.headers['user-agent'],
};
}Binary Responses
You can pass binaryMimeTypes in createLambdaHandler options for future use. Automatic base64 encoding of binary response bodies for Lambda is planned.
export const handler = createLambdaHandler(AppModule, {
binaryMimeTypes: ['image/png', 'application/pdf'],
});Logging
CloudWatch Logs (AWS)
import { Logger } from '@hazeljs/core';
@Injectable()
export class MyService {
private logger = new Logger(MyService.name);
async doSomething() {
this.logger.log('Processing request');
this.logger.error('An error occurred');
this.logger.warn('Warning message');
}
}Cloud Logging (GCP)
import { Logger } from '@hazeljs/core';
@Injectable()
export class MyService {
private logger = new Logger(MyService.name);
async doSomething() {
// Logs automatically sent to Cloud Logging
this.logger.log('Processing request', {
userId: '123',
action: 'create',
});
}
}Best Practices
- Minimize Dependencies - Only include necessary packages
- Reuse Connections - Cache database and API connections
- Use Environment Variables - Store configuration externally
- Optimize Bundle Size - Use tree-shaking and minification
- Handle Cold Starts - Implement warming strategies
- Monitor Performance - Track execution time and memory usage
- Set Appropriate Timeouts - Balance cost and functionality
- Use Provisioned Concurrency - For latency-sensitive endpoints
Monitoring
AWS Lambda
// Add X-Ray tracing
import AWSXRay from 'aws-xray-sdk-core';
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
// Custom metrics
import { CloudWatch } from 'aws-sdk';
const cloudwatch = new CloudWatch();
async function recordMetric(name: string, value: number) {
await cloudwatch
.putMetricData({
Namespace: 'HazelJS',
MetricData: [
{
MetricName: name,
Value: value,
Unit: 'Count',
},
],
})
.promise();
}Google Cloud Functions
// Add Cloud Trace
import { TraceAgent } from '@google-cloud/trace-agent';
TraceAgent.start();
// Custom metrics
import { Monitoring } from '@google-cloud/monitoring';
const monitoring = new Monitoring.MetricServiceClient();
async function recordMetric(name: string, value: number) {
const request = {
name: monitoring.projectPath(projectId),
timeSeries: [
{
metric: { type: `custom.googleapis.com/${name}` },
points: [
{
interval: { endTime: { seconds: Date.now() / 1000 } },
value: { doubleValue: value },
},
],
},
],
};
await monitoring.createTimeSeries(request);
}Cost Optimization
Request Batching
// Batch multiple operations
@Post('/batch')
async batchProcess(@Body() items: any[]) {
const results = await Promise.all(
items.map(item => this.processItem(item))
);
return results;
}Caching
import { Cache } from '@hazeljs/cache';
@Injectable()
export class DataService {
@Cache({ key: 'expensive-data', ttl: 3600 })
async getExpensiveData() {
// This will be cached for 1 hour
return await this.fetchFromDatabase();
}
}Examples
See the examples directory for complete working examples.
Testing
npm testContributing
Contributions are welcome! Please read our Contributing Guide for details.
License
Apache 2.0 © HazelJS
