Skip to content
shipped

Full-Stack Angular and Node.js at an E-Learning Company

A workplace language-learning product: an Angular web app for learners and admins, a Node.js API that also served the mobile apps, a manager statistics view, and a Jest test modernization.

Managers couldn't see how their employees were actually learning, and content updates had to reach every user before they could keep practicing.

role:
Full-stack developer: the Angular web app and the Node.js API behind every client
stack:
Angular · Node.js · TypeScript · Jest · Jasmine · GitLab
when:
2022–2023
published:
Workplace language-learning product: architecture Learners work through the exercise and level flow in either the wrapped Angular web app or the Flutter apps for iOS and Android. Managers and admins use only the web app, where they read statistics tables on usage, time and answers and set up the mobile apps. Update notifications announce new questions, answers and levels, and both clients are blocked until updated. Both clients run on one Node.js API in TypeScript that serves content, records results, computes statistics and reports the current content version. Learners exercise & level flow Managers & admins statistics · setup web app only Update notifications new questions · answers · levels Angular web app (wrapped) learning-flow components TypeScript · Jest + Jasmine blocked until updated Flutter apps · iOS, Android blocked until updated Node.js API content · results statistics current content version
TypeScript on both layers: learners use the wrapped Angular web app or the Flutter apps, managers and admins use the web app, and all of them run on one Node.js API that serves content, records results, and reports which content version is current. · scroll sideways to see all of it

Problem & constraints

At an e-learning company, the product taught languages to employees of client companies: learners practiced in the apps, and their managers needed to see what that practice amounted to, not just whether people were succeeding, but how hard they were trying.

The team was small: two developers and a project manager steering priorities. I built the Node.js backend and the Angular web app; the other developer built the Flutter apps for iOS and Android on top of that backend. That left no dedicated CI/CD pipeline (version control ran on GitLab) and put a premium on pragmatic choices that two people could carry.

One constraint mattered more than the rest: the learning content itself (questions, answers, levels) changed with releases, so every learner had to be on the current version to keep practicing, and the clients and the backend had to agree on what “current” meant.

Architecture

The web app is an Angular SPA, wrapped for installation, over a Node.js API, both in TypeScript, so the same language and much of the same thinking ran from the screen to the server. Learners used it or the Flutter apps. Managers and admins used only the web app, which also set up, controlled and administered the mobile apps.

On the frontend, components built from the Figma designs formed the interactive heart: the exercise, answer, and progress building blocks a learning flow is assembled from. For managers, a statistics view reported usage frequency, time spent, and correct-versus-wrong answers per employee, as plain tables rather than a charting package.

On the backend, the Node.js API delivered the learning content to every client and recorded what learners did with it: answers given, time spent, sessions completed. It turned those raw results into the per-employee figures the manager view displayed, so the frontend received ready-to-render data rather than doing the counting itself.

The layers met at the update mechanism: new releases were announced through the native notification channel, the API exposed the current content version, and once an update was out, learners could not continue past their current session, on web or mobile, until they had updated. The decisions behind it are logged below, each with the alternative it beat.

Angular and Node.js after Vue

I came to this role from years of Vue with only brief Angular exposure, and the first surprise was structural: so many declarations, spread across separate files, for every single component, where Vue gives you the whole component in one file. But the discipline grew on me. Angular’s enforced structure keeps a codebase coherent in a way that matters when a very small team carries it. Working in TypeScript on both the Angular client and the Node.js server added a second benefit: one language, and no context switch when a feature crossed from UI to API. Working productively inside that model, while still preferring Vue’s ergonomics, is exactly the kind of range I’d want on any team I lead.

Outcomes

Managers got what they were missing: per-employee visibility into usage frequency, time invested, and correct-versus-wrong answers, collected by the API and presented in the manager view. It showed effort, not just success. The update gate, shared between clients and server, kept every learner on current content, with engagement on new features observable in UX analysis after each release. And the test modernization made the daily development loop faster, with tests quick enough to run alongside the coding.

Lessons

Three takeaways stayed with me. First, framework discipline is a feature, not a constraint; Angular’s structure did for our small team what a style guide does for bigger ones. Second, owning both ends of a feature changes how you design it: deciding where a calculation lives, in the API or in the browser, is easier when the same person writes both sides. Third, when there is no CI pipeline, test tooling ergonomics are the quality gate: the investment in Jest paid off not in a dashboard but in how fast the development loop could run.

Key decisions

  1. 01 A hard update gate

    Blocking continued use, on web and mobile, until a released update is installed, announced through the native notification channel and checked against the content version reported by the API.

    Each release could change questions, answers, and levels, so every learner had to be on the up-to-date package before continuing, and results recorded on the server had to match the content the learner actually saw.

    Learners could finish what they were in, then had to update to continue. Engagement with newly shipped features was visible in the UX analysis after releases.

    rejected: Letting learners finish on stale content and update whenever convenient

  2. 02 Statistics computed on the server

    Aggregating usage frequency, time spent, and correct-versus-wrong answers per employee in the Node.js API, and sending the manager view ready-made figures.

    Raw answer records grow with every session; computing on the server kept the payload small, the Angular view simple, and the numbers consistent wherever they were shown.

    The manager view stayed a plain table component that rendered what the API returned, which also made it straightforward to test.

    rejected: Sending raw results to the browser and calculating statistics in Angular

  3. 03 Jest for unit tests

    Introducing Jest for new unit tests while updating the existing Jasmine workflows.

    In a large TypeScript codebase, Jest's parallel workers, changed-file reruns, and responsive watch mode meant tests could keep pace with the coding loop.

    Jest also brought the tooling into one package (runner, assertions, mocks and spies, coverage), and snapshot testing gave a regression check for generated configuration and complex serialized output. Because it runs on Node.js, the same test tooling fit both the Angular client and the backend code. The development loop was our quality gate, which is exactly where test ergonomics matter most.

    rejected: Staying with Angular's default Jasmine/Karma setup alone

Key figures

Key figures (self-reported)
MetricValueStatus
Core components built14reported

Reported: Components built from the Figma designs for the core learning flow