sentiment-node
v1.0.0
Published
Lightweight sentiment analysis library for Node.js that predicts whether text is positive, negative, or neutral.
Maintainers
Readme
sentiment-node
Offline sentiment analysis for Node.js — classifies English text as positive, negative, or neutral using a TF-IDF vectorizer and an ONNX machine learning model. No API keys. No internet required.
import { init, analyze } from 'sentiment-node'
await init()
await analyze('I absolutely love this!')
// → { label: 'positive', confidence: 0.83, scores: { negative: 0.06, neutral: 0.11, positive: 0.83 } }Installation
npm install sentiment-nodeGetting Started
Follow these three steps to go from zero to running predictions.
Step 1 — Install dependencies
npm installStep 2 — Import and initialize
Always call init() once before doing any analysis. It loads the vocabulary and model into memory.
import { init, analyze } from 'sentiment-node'
await init()
// ✅ Vocabulary loaded: 44,961 tokens
// ✅ ONNX model loaded
// 🚀 Ready!Step 3 — Analyze text
const result = await analyze('The product quality exceeded my expectations.')
console.log(result)// Output:
{
label: 'positive',
confidence: 0.8315,
scores: {
negative: 0.0617,
neutral: 0.1068,
positive: 0.8315
}
}That's it. See the full API below for batch analysis, thresholds, and statistics.
API
init(options?)
Loads the model files into memory. Call this once at startup before anything else.
// Default — loads from the built-in models/ folder
await init()
// Custom paths (optional)
await init({
vocabPath: './path/to/tfidf_vocab.json',
modelPath: './path/to/sentiment_model.onnx'
})analyze(text, options?)
Analyzes a single string and returns its sentiment.
const result = await analyze('Shipping was fast and packaging was great!')Return value:
{
label: 'positive', // the predicted sentiment
confidence: 0.8315, // how confident the model is (0 to 1)
scores: {
negative: 0.0617, // raw probability for each class
neutral: 0.1068,
positive: 0.8315
}
}With threshold option — if confidence is too low, output is forced to neutral:
const result = await analyze('meh it was fine I guess', { threshold: 0.6 })
// confidence 0.45 < threshold 0.6 → label forced to 'neutral'With detectLanguage option — skips non-English text safely:
const result = await analyze('Je suis content', { detectLanguage: true })// Output:
{
label: 'unknown',
confidence: 0,
scores: { negative: 0, neutral: 0, positive: 0 },
language: { code: 'fra', isEnglish: false, supported: false },
warning: "Language 'fra' is not supported. Model trained on English only."
}analyzeBatch(texts[], options?)
Analyzes an array of strings in parallel. Returns all results plus timing metadata.
import { init, analyzeBatch } from 'sentiment-node'
await init()
const { results, meta } = await analyzeBatch([
'Amazing product, highly recommend!',
'Terrible quality, waste of money.',
'It arrived on time.',
'Not bad at all.',
'I am so happy with this purchase!'
])Return value:
{
results: [
{ index: 0, text: 'Amazing product...', label: 'positive', confidence: 0.80, scores: { ... } },
{ index: 1, text: 'Terrible quality...', label: 'negative', confidence: 0.42, scores: { ... } },
{ index: 2, text: 'It arrived on time.', label: 'neutral', confidence: 0.46, scores: { ... } },
{ index: 3, text: 'Not bad at all.', label: 'negative', confidence: 0.54, scores: { ... } },
{ index: 4, text: 'I am so happy...', label: 'positive', confidence: 0.68, scores: { ... } }
],
meta: {
total: 5,
elapsed: '11ms',
avg: '2.20ms per item'
}
}All
analyzeoptions (threshold,detectLanguage) are supported here too.
summary(texts[], options?)
Analyzes an array of strings and returns aggregate statistics — useful for dashboards, reviews, or feedback forms.
import { init, summary } from 'sentiment-node'
await init()
const stats = await summary([
'Love it!',
'Hate it!',
'Its okay',
'Great product',
'Worst ever',
'Not sure about this',
'Would buy again',
'Never buying again'
])
console.log(stats)Return value:
{
total: 8,
counts: {
positive: 4,
neutral: 1,
negative: 3
},
percentages: {
positive: 50,
neutral: 13,
negative: 38
},
dominant: 'positive' // the most common sentiment
}Options Reference
All options are passed as the second argument to analyze, analyzeBatch, or summary.
| Option | Type | Default | Description |
| ---------------- | --------- | ------- | ---------------------------------------------------------------------------- |
| threshold | number | 0.0 | Minimum confidence to trust — anything below is returned as neutral |
| detectLanguage | boolean | false | When true, non-English text returns { label: 'unknown', warning: '...' } |
Choosing a threshold:
| Threshold | Behaviour |
| --------------- | ------------------------------------------ |
| 0.0 (default) | Always returns the model's best guess |
| 0.5 | Returns neutral when the model is unsure |
| 0.7 | Only trusts high-confidence predictions |
How It Works
Input text
│
▼
① Clean lowercase, remove noise
│
▼
② Vectorize TF-IDF (sublinear_tf, bigrams, 44k vocab) → Float32Array
│
▼
③ Infer ONNX model → class label + probabilities
│
▼
④ Threshold confidence < threshold? → force neutral
│
▼
Result object { label, confidence, scores }The model was trained with scikit-learn's TfidfVectorizer (settings: max_features=50000, ngram_range=(1,2), sublinear_tf=True) and exported to ONNX format. The JavaScript vectorizer replicates the exact same transform so predictions are consistent with the Python training environment.
Contributing
Contributions, bug reports, and suggestions are welcome! Please read CONTRIBUTING.md before opening a pull request.
Community
| Document | Description | | ---------------------------------------- | ----------------------------------------- | | CONTRIBUTING.md | How to contribute code, docs, or fixes | | CODE_OF_CONDUCT.md | Expected behaviour in project spaces | | SECURITY.md | How to responsibly report a vulnerability |
License
Apache-2.0 © Anas Alam
