A comprehensive guide to the HazelJS ML Starter—decorator-based machine learning, sentiment analysis, and scalable inference in TypeScript
Introduction
Machine learning in Node.js has matured significantly. With TensorFlow.js, ONNX Runtime, and a growing ecosystem, JavaScript/TypeScript developers can now build and serve ML models without leaving their preferred runtime.
HazelJS is a modern, decorator-first Node.js framework that brings this capability to production with @hazeljs/ml—a module for model registration, training pipelines, inference, and lifecycle management. In this post, we’ll walk through the HazelJS ML Starter—a real-world sentiment analysis API that demonstrates the full stack.
Why ML in Node.js?
Traditionally, ML has lived in Python. For many teams, however, Node.js is the primary backend. Running inference in the same process as your API reduces latency, simplifies deployment, and keeps your stack consistent. Use cases like:
- Sentiment analysis for reviews and support tickets
- Fraud detection on transactional data
- Recommendation models in e-commerce
- Content moderation for user-generated content
can all be served directly from a Node.js API. @hazeljs/ml is designed for exactly this pattern: register models, train (or load) them, and expose them via REST or any transport.
What is HazelJS?
HazelJS is a TypeScript-first Node.js framework inspired by NestJS and Fastify. It provides:
- Decorator-based APIs for controllers, services, and modules
- Dependency injection and modular architecture
- Integrated packages for AI, gRPC, Kafka, Prisma, and more
- Production-ready defaults for health checks, shutdown, and observability
The @hazeljs/ml package adds machine learning primitives on top of this foundation. You can explore the full framework on the HazelJS homepage and GitHub repository.
Architecture of the HazelJS ML Starter
The HazelJS ML Starter implements three widely-used models plus shared infrastructure:
| Component | Responsibility |
|---|---|
| SentimentClassifier | Sentiment (positive/negative/neutral) for reviews and feedback |
| SpamClassifier | Binary spam/ham for email, SMS, content moderation |
| IntentClassifier | Multi-class intent routing for chatbots and support |
| MLModule | Registers models and ML services via MLModule.forRoot()
|
| MLController | REST endpoints for predict, batch predict, train, metrics |
| PipelineService | Preprocessing pipeline (normalize, filter) per model |
| ModelRegistry | Versioned model storage and lookup |
| MetricsService | Evaluation metrics for A/B testing and monitoring |
The starter repository includes a working example you can clone and run locally.
Getting Started
Prerequisites
- Node.js 18+
- HazelJS and @hazeljs/ml installed
Installation
# Clone or navigate to the starter
cd hazeljs-ml-starter
# Install dependencies
npm install
# Build
npm run build
# Start the server
npm start
The API is available at http://localhost:3000. For development with hot reload, use npm run dev. Full setup instructions are in the starter README.
The @Model, @Train, and @Predict Decorators
@hazeljs/ml uses three core decorators to define ML models:
@Model
Registers a class as an ML model with metadata for the Model Registry:
@Model({
name: 'sentiment-classifier',
version: '1.0.0',
framework: 'custom',
description: 'Text sentiment classification',
tags: ['nlp', 'production'],
})
@Injectable()
export class SentimentClassifier {
// ...
}
The ML package documentation covers all options.
@Train
Marks the training method. Options include pipeline name, batch size, and epochs:
@Train({ pipeline: 'sentiment-preprocessing', epochs: 1, batchSize: 32 })
async train(data: SentimentTrainingData): Promise<TrainingResult> {
// Build word weights from labeled samples
return { accuracy: 0.95, loss: 0.05 };
}
The Train decorator source shows the full signature.
@Predict
Marks the inference method for real-time and batch prediction:
@Predict({ batch: true, endpoint: '/predict' })
async predict(input: unknown): Promise<SentimentPrediction> {
// Score text, return sentiment + confidence
return { sentiment: 'positive', confidence: 0.92, scores: {...} };
}
The Predict decorator is used by both PredictorService and BatchService.
REST API Walkthrough
Single Prediction
Use the model parameter to switch between models (default: sentiment-classifier).
# Sentiment
curl -X POST http://localhost:3000/ml/predict \
-H "Content-Type: application/json" \
-d '{"text": "This product is amazing! I love it."}'
# Spam
curl -X POST http://localhost:3000/ml/predict \
-H "Content-Type: application/json" \
-d '{"text": "Win a free iPhone!", "model": "spam-classifier"}'
# Intent
curl -X POST http://localhost:3000/ml/predict \
-H "Content-Type: application/json" \
-d '{"text": "I want a refund.", "model": "intent-classifier"}'
Sentiment response:
{
"result": {
"sentiment": "positive",
"confidence": 0.85,
"scores": { "positive": 2.5, "negative": 0.2, "neutral": 0.3 }
}
}
Batch Prediction
curl -X POST http://localhost:3000/ml/predict/batch \
-H "Content-Type: application/json" \
-d '{"texts": ["Great!", "Terrible.", "Its okay."], "batchSize": 32}'
The BatchService handles batching and concurrency.
Training
curl -X POST http://localhost:3000/ml/train \
-H "Content-Type: application/json" \
-d '{
"samples": [
{"text": "Love this!", "label": "positive"},
{"text": "Hate it.", "label": "negative"},
{"text": "Its fine.", "label": "neutral"}
]
}'
Training data flows through the PipelineService for normalization and validation.
Metrics and Model Listing
# Model evaluation metrics
curl "http://localhost:3000/ml/metrics?model=sentiment-classifier"
# List registered models
curl http://localhost:3000/ml/models
# Model versions
curl http://localhost:3000/ml/models/sentiment-classifier/versions
MetricsService supports A/B testing and comparison of model versions.
Training Pipeline and Data Preparation
Before training, data can be preprocessed via the PipelineService. In the starter, we register a sentiment-preprocessing pipeline in ml.bootstrap.ts:
- normalize – Trim and lowercase text; normalize labels
- filter-invalid – Remove samples with empty text or invalid labels
This pattern extends to richer ETL. @hazeljs/data provides @Pipeline, @Transform, and @Validate for complex data pipelines. See the Data package for integration examples.
Programmatic Training
You can train outside the HTTP API using the train-with-sample-data script:
npm run train:sample
This loads src/data/sample-training-data.json, trains the model, and runs a quick prediction. Useful for:
- Initial model setup
- CI/CD training jobs
- Batch retraining
The starter data file includes 20 labeled samples that typically yield ~95% training accuracy.
Extending to Production
1. Integrate TensorFlow.js or ONNX
The starter uses a bag-of-words model for clarity. For production NLP, you can:
- Load a TensorFlow.js or ONNX model
- Keep the same
@Model,@Train,@Predictinterface - Swap the internal logic without changing controllers or clients
The @hazeljs/ml types support framework: 'tensorflow' | 'onnx' | 'custom'.
2. Model Persistence
Implement save/load in train() and on construction:
- Save weights to
./models/or object storage - Load on startup when the model is first resolved
- Use ModelRegistry for versioning
3. Metrics and A/B Testing
Use MetricsService.recordEvaluation() after validation runs. compareVersions() helps compare model versions for rollout decisions.
4. Data Pipelines
Combine @hazeljs/ml with @hazeljs/data for:
- ETL with @Pipeline, @Transform, @Validate
- Streaming with @Stream and Flink-style pipelines
- Schema validation via Schema
Summary
The HazelJS ML Starter shows how to build a production-oriented ML API in Node.js with:
- Three widely-used models: SentimentClassifier (reviews), SpamClassifier (moderation), IntentClassifier (chatbots)
- Decorator-based models via @hazeljs/ml
- REST API for prediction, batch inference, and training
- Training pipeline for data preprocessing
- Model registry and metrics for versioning and monitoring
You can use it as a template for sentiment analysis, spam detection, intent routing, or any ML workload that fits the same pattern.
Links and Resources
| Resource | URL |
|---|---|
| HazelJS | https://hazeljs.com |
| HazelJS GitHub | https://github.com/hazel-js/hazeljs |
| @hazeljs/ml on npm | https://www.npmjs.com/package/@hazeljs/ml |
| @hazeljs/core on npm | https://www.npmjs.com/package/@hazeljs/core |
| @hazeljs/data on npm | https://www.npmjs.com/package/@hazeljs/data |
| @hazeljs/ai on npm | https://www.npmjs.com/package/@hazeljs/ai |
| TensorFlow.js | https://www.tensorflow.org/js |
| ONNX Runtime JS | https://onnxruntime.ai/docs/get-started/with-javascript.html |
| ML package source | packages/ml |
| Data package source | packages/data |
| Open Collective | https://opencollective.com/hazeljs |
This blog post was created for the HazelJS ML Starter. For questions and contributions, visit the HazelJS GitHub repository or community.
Top comments (0)