Learn how to build a smarter recommendation engine using user ratings, feedback loops, and model retraining with Neural Collaborative Filtering in JavaScript.
Awesome! Let’s implement a feedback loop so users can rate movies, and the system will retrain the model to improve future recommendations.
🎯 Goal
Allow users to:
- View movie recommendations.
- Rate recommended movies.
- Store ratings in a database or file.
- Periodically retrain the model using the updated ratings data.
🧱 Architecture Overview
[React UI] ⇄ [Node.js API] ⇄ [ratings.json or DB] ⇄ [TF.js Training Script]
↑
User feedback loop
1️⃣ React UI: Add Rating Input
client/src/App.jsx
(add feedback UI)
function App() {
const [userId, setUserId] = useState("");
const [recommendations, setRecommendations] = useState([]);
const getRecommendations = async () => {
const res = await axios.get(`http://localhost:5000/api/recommend/${userId}`);
setRecommendations(res.data);
};
const sendRating = async (movieId, rating) => {
await axios.post(`http://localhost:5000/api/rate`, {
userId: parseInt(userId),
movieId,
rating,
});
alert("Rating submitted!");
};
return (
<div className="p-6 max-w-md mx-auto">
<h1 className="text-2xl mb-4 font-bold">Movie Recommendations</h1>
<input
type="number"
value={userId}
onChange={(e) => setUserId(e.target.value)}
placeholder="Enter user ID"
className="border p-2 w-full"
/>
<button onClick={getRecommendations} className="mt-2 bg-blue-500 text-white px-4 py-2">
Recommend
</button>
<ul className="mt-4">
{recommendations.map((rec) => (
<li key={rec.id}>
<div>{rec.title} — Score: {rec.score.toFixed(2)}</div>
<select onChange={(e) => sendRating(rec.id, e.target.value)}>
<option value="">Rate</option>
{[1,2,3,4,5].map((r) => <option key={r} value={r}>{r}</option>)}
</select>
</li>
))}
</ul>
</div>
);
}
2️⃣ Node.js API: Handle Ratings
server/routes/ratings.js
const express = require('express');
const fs = require('fs');
const router = express.Router();
const RATINGS_PATH = 'data/user_ratings.json';
router.post('/rate', (req, res) => {
const { userId, movieId, rating } = req.body;
let ratings = [];
if (fs.existsSync(RATINGS_PATH)) {
ratings = JSON.parse(fs.readFileSync(RATINGS_PATH));
}
ratings.push({ userId, movieId, rating, timestamp: Date.now() });
fs.writeFileSync(RATINGS_PATH, JSON.stringify(ratings, null, 2));
res.sendStatus(200);
});
module.exports = router;
Update server/index.js
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const { loadModel, recommendForUser } = require('./model/recommender');
const ratingsRoute = require('./routes/ratings');
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use('/api', ratingsRoute);
app.get('/api/recommend/:userId', async (req, res) => {
const userId = parseInt(req.params.userId);
const recommendations = await recommendForUser(userId);
res.json(recommendations);
});
3️⃣ Periodic Retraining Script
Create a script to retrain the model from both:
u.data
user_ratings.json
server/model/retrain.js
const tf = require('@tensorflow/tfjs-node');
const fs = require('fs');
async function loadData() {
const original = fs.readFileSync('data/u.data', 'utf8')
.split('\n')
.filter(Boolean)
.map(l => {
const [user, item, rating] = l.split('\t');
return { user: +user, item: +item, rating: +rating };
});
const feedback = fs.existsSync('data/user_ratings.json')
? JSON.parse(fs.readFileSync('data/user_ratings.json'))
: [];
return original.concat(feedback);
}
async function retrain() {
const data = await loadData();
const numUsers = Math.max(...data.map(d => d.user)) + 1;
const numItems = Math.max(...data.map(d => d.item)) + 1;
const userTensor = tf.tensor1d(data.map(d => d.user), 'int32');
const itemTensor = tf.tensor1d(data.map(d => d.item), 'int32');
const ratingTensor = tf.tensor2d(data.map(d => d.rating), [data.length, 1]);
const embeddingDim = 8;
const model = createModel(numUsers, numItems, embeddingDim);
model.compile({ optimizer: tf.train.adam(0.01), loss: 'meanSquaredError' });
await model.fit([userTensor, itemTensor], ratingTensor, {
epochs: 10,
batchSize: 64,
});
await model.save('file://server/model');
console.log("Model retrained and saved.");
}
function createModel(numUsers, numItems, embeddingDim) {
const userInput = tf.input({ shape: [1] });
const itemInput = tf.input({ shape: [1] });
const userEmbed = tf.layers.embedding({ inputDim: numUsers, outputDim: embeddingDim }).apply(userInput);
const itemEmbed = tf.layers.embedding({ inputDim: numItems, outputDim: embeddingDim }).apply(itemInput);
const merged = tf.layers.concatenate().apply([
tf.layers.flatten().apply(userEmbed),
tf.layers.flatten().apply(itemEmbed),
]);
const dense = tf.layers.dense({ units: 32, activation: 'relu' }).apply(merged);
const output = tf.layers.dense({ units: 1 }).apply(dense);
return tf.model({ inputs: [userInput, itemInput], outputs: output });
}
retrain();
Run with:
node server/model/retrain.js
Or schedule it with a cron job or trigger manually from admin UI.
✅ Summary
You now have:
- A UI where users rate movies.
- Ratings saved via API to
user_ratings.json
. - A script to retrain the neural model with feedback.
🔜 Next Ideas
- ✅ Store feedback in SQLite/PostgreSQL
- ✅ Add UI feedback summary (rated movies)
- ✅ Retrain automatically after N ratings
- ✅ Add user profile features (age, genre preference)