A full-stack application for training and visualizing a linear regression model and a neuronal network using C++, Node.js, and React.
This project brings together high-performance C++ computing with a modern web application, making it ideal for both education and rapid prototyping. It features analytical Linear Regression and a customizable Feedforward Neural Network, both implemented in C++11 and seamlessly integrated with a Node.js/Express backend. The frontend, built with React, utilizes REST APIs and WebSockets to provide dynamic visualizations of data points, regression lines, training losses, and predictions in real-time.

git clone https://github.com/Nicolas2912/cpp-ml-react.git
cd cpp-ml-react
cd cpp
make # Creates linear_regression_app (or .exe on Windows)
cd ..
cd server
npm install
npm start
# Server runs on http://localhost:3001
# Open a new terminal
cd client
npm install
npm start
# Opens at http://localhost:3000
cpp/)linear_regression.h/.cpp:
neural_network.h/.cpp:
main_server.cpp:
Makefile:
server/)server.js:
client/)build/: IDE and debugging artifactstest_openmp.cpp: Utility for verifying OpenMP parallel capabilitiesThroughout this project, I encountered three major challenges that pushed me to deepen my understanding of both C++ and full-stack JavaScript development. Below I describe each challenge, how I solved it in my actual implementation, why I chose that approach, and ways I might improve it in the future.
I needed to run high-performance C++ code (both an analytical linear-regression solver and a custom neural network with backpropagation) from a Node.js backend without sacrificing productivity or maintainability.
Implemented Solution
linear_regression_app and the combined main_server for both LR and NN).server/server.js, I use Nodeās child_process.spawn to launch the appropriate C++ binary with commands like lr_train or nn_train_predict.stdin as simple comma-separated lines (e.g. 1,2,3,4\n2,4,6,8\n), and output is printed line-by-line in the form key=value.parseCppLine(line) splits each line into { type: 'loss_update', epoch, mse } or { type: 'final_stat', key, value }, which I then
transform into JSON messages for HTTP responses and WebSocket broadcasts.Why This Approach
Future Improvements
During neural-network training, I print loss after every epoch. For small datasets or many epochs, this floods the WebSocket channel and causes UI lag or even browser buffer overflows.
Implemented Solution
server.js, every time C++ writes a line like epoch=42,mse=0.1234, I broadcast an object { type: 'loss_update', epoch, mse } to all connected WebSocket
clients.App.js), I collect incoming updates into a local batch (lossUpdateBatchRef).lodash.throttle to call a batched updater at most once every 150 ms. That function
Merges new points (filtering out duplicates) into React state,
Clears the batch,
Directly calls chartRef.current.update('none') on the Chart.js instance to redraw without animation.
type: 'final_result' / type: 'error'), I flush() the throttle to process any remaining batches immediately.Why This Approach
'none') eliminate animation overhead and keep the graph in sync with minimal flicker.Future Improvements
report_every_n_epochs (configurable via the API) to reduce network chatter.Writing a fully-featured backpropagation algorithm from scratch in C++ ā complete with flexible layer sizes, safe matrix/vector arithmetic, and periodic loss reportingāis a nontrivial undertaking.
Implemented Solution
NeuralNetwork class (neural_network.h/.cpp) that:
Stores layer_sizes_, a vector of weights matrices, and bias vectors.
Initializes weights with a simple He-style scaling (±0.5/ān) and small positive biases.
Implements forward_pass to compute weighted sums (z) and apply sigmoid on hidden layers, identity on the output.
Stores both pre-activation (layer_inputs_) and post-activation (layer_outputs_) vectors to simplify gradient computation.
Implements backpropagate by
Computing output-layer delta as (Å· ā y),
Back-propagating through each hidden layer via transposed weight multiplication plus sigmoid_derivative(z),
Applying stochastic gradient descent updates to weights (W ā W ā Ī· ā
Ī“ā
aįµ) and biases.
Exposes train_for_epochs(inputs, targets, epochs) that shuffles data each epoch, runs backpropagate on every sample, and prints epoch=<n>,mse=<value>
whenever (epoch+1)%report_every_n_epochs==0 or at the last epoch.
After training, returns a flat vector of final predictions for all inputs, allowing the caller to compute and print final_mse, training_time_ms, and
nn_predictions=ā¦.
Why This Approach
train_for_epochs method handles both loss reporting (for streaming) and final prediction output.Future Improvements
Tackling these challenges significantly improved my skill with low-level C++ (especially designing and debugging the backprop algorithm) and my ability to architect a full-stack pipeline that stitches native code into a smooth, real-time web experience.
Building this end-to-end C++ ā Node.js pipeline and live-streaming neural-network demo taught me invaluable lessons in both low-level systems programming and high-level web architecture:
Process Communication & Protocol Design
I now feel comfortable using child_process.spawn in Node.js to launch native binaries, pipe data into stdin, and read both stdout and stderr as
streaming events.
Designing a minimal text protocolāprinting key=value pairs line by line from C++ and parsing them in JavaScript via a single helper
(parseCppLine)āproved both robust and easy to debug.
I learned to guard against edge cases: always ending the C++ stdin stream (cppProcess.stdin.end()), checking for res.headersSent before writing HTTP
responses, and handling non-zero exit codes or parse failures gracefully.
Real-Time Streaming Over WebSockets
Integrating the ws library on the server and a raw WebSocket in React gave me a front-row seat to the challenges of high-frequency updates.
I discovered that unthrottled epoch-by-epoch messages overwhelm the browser, so batching them in a useRef queue and using lodash.throttle (150 ms) to
update React state is critical for smooth chart rendering.
Directly calling chartRef.current.update('none') on the Chart.js instance taught me how to decouple data ingestion from animated redraws, ensuring a
snappy UI even under load.
Properly cleaning up on unmountācalling scheduleProcessLossBatch.cancel(), closing sockets in useEffect cleanup, and clearing batchesāprevented subtle
memory leaks and dangling callbacks.
Implementing Backpropagation in C++
Writing a full NeuralNetwork class from scratch solidified my understanding of forward passes, storing pre-activation inputs (z) and post-activation
outputs (a), and computing deltas layer by layer.
I sharpened my C++ skills:
⢠Exception-safe parsing with std::strtod and thorough error checks in parseVector and parseLayerSizes.
⢠Random weight initialization using std::mt19937 with scaled uniform distributions.
⢠Timing code precisely with <chrono> to report training_time_ms.
Separating algorithm (train_for_epochs) from I/O (main_server.cpp) made both sides easier to test and maintain. I now appreciate the discipline of
throwing on invalid matrix dimensions and catching every parsing exception at the top level.
Full-Stack Architecture & Tooling
I gained confidence wiring together a JavaScript REST/WebSocket layer (Express + ws) with a React front end (react-chartjs-2, reactflow) and a C++ numerical engine. Clear separation of concerns kept each component focusedāNode.js orchestrates processes and handles HTTP/WebSocket clients, React manages state and visuals, and C++ crunches numbers.
Detailed logging became my best friend: streaming chunks of C++ stdout/stderr to Nodeās console, emitting structured logs (LR Train C++ Stdout Chunk,
WebSocket message received), and surfacing errors early made multi-language debugging tractable.
I reinforced best practices around input validation (both in JS and C++), error propagation, and defensive cleanupāskills that will pay dividends in any large-scale system.
Next Steps
GPU Acceleration: Implement CUDA kernels for parallel training of neural networks, potentially yielding 10-100x speedup for large problem instances.
Other optimizer: Implement other optimization algorithms (e.g. Adam, RMSprop) to improve convergence and performance.
Other activation function: Implement other activation functions (e.g. ReLU, tanh) to improve performance.
