Capstone: Build a Report Card Service

By Syncfusion®, Inc. · Last updated

A complete Node.js project in five steps: a command-line tool with parseArgs and an env file, streaming a marks file with readline and saving results safely, an EventEmitter-based report builder with custom errors, an HTTP API with health checks and graceful shutdown, and tests with node:test, followed by three extension exercises.

Capstone: Build a Report Card Service

Capstone = one project that uses what the whole course taught.

You will build Report Card Service for Greenfield School:

  • A command-line tool that reads a marks file and builds report cards.
  • A small web API that serves the reports.
  • Tests that check it all.

Simple lesson: a real project is many small lessons joined together.

A Real-World Example

At the end of every term, the class teacher at Greenfield School:

  • gets a marks sheet with one row per student and subject,
  • adds up each student's marks and works out the grade,
  • finds the topper and the students who need help,
  • shares the results with parents,
  • and checks that nothing was copied wrongly.

Your program will do the same job in less than a second.

What You Will Learn

In this lesson, you will:

  1. Plan the project as small modules
  2. Build the command line with parseArgs and a .env file
  3. Stream the marks file with readline and save results safely
  4. Report progress with events and catch bad rows with a custom error
  5. Serve reports over HTTP with health checks and graceful shutdown
  6. Test the grades, the builder and the server with node:test
  7. Extend the project in three exercises

The Plan

Diagram: marks.csv is streamed by the ReportBuilder, which uses grades.ts, emits events and writes report.json, which the CLI prints and the HTTP server serves, with tests checking both marks.csv ReportBuilderstreams, events, errors grades.ts report.json CLI HTTP API tests (node:test)

Data flows from the marks file through the builder into a saved report, which the CLI and the API share.

The files:

  • src/grades.ts = small pure functions: total, average, grade. Easy to test.
  • src/report.ts = the ReportBuilder: reads the CSV as a stream, emits events, throws BadRowError.
  • src/config.ts = reads and checks settings.
  • cli.ts = the command-line tool.
  • server.ts = the HTTP API.
  • test/*.test.ts = the tests.

Build one step at a time, and run it after every change.

Simple lesson: keep pure logic, input and output, and the entry points in separate modules.

Step 1: Command Line and Config

  • parseArgs reads --class, --term and --help (see Process, OS and CLI Tools).
  • --env-file=.env gives PASS_MARK and SCHOOL_NAME.
  • loadConfig checks the settings and fails fast.

Simple lesson: read options and settings once, check them, and stop early if anything is wrong.

Step 2: Stream the Marks and Save Safely

  • The marks file can be big, so read it line by line with readline (see Buffers, Streams and Compression).
  • grades.ts holds tiny pure functions.
  • The result is saved with write-temp-then-rename (see Path and File System).

Simple lesson: stream the input, keep the maths in pure functions, and save with a rename.

Step 3: Events and Custom Errors

  • The ReportBuilder extends EventEmitter (see Events and Utilities).
  • It emits student for each finished card, bad-row for broken lines, and done at the end.
  • A broken line throws a BadRowError with a code and the line number (see Error Handling).
  • The CLI listens to the events, so the builder never prints anything itself.

The same ReportBuilder could feed the HTTP API, a test or an email job, because it does not print.

Simple lesson: the builder announces what happens, and the caller decides what to print.

Step 4: The HTTP API

  • GET /health = liveness for the platform (see Production Readiness).
  • GET /reports/:class = the saved report as JSON, or 404.
  • SIGTERM = close the server gracefully.

Simple lesson: a small API needs routes, JSON errors, a health check and a clean shutdown.

Step 5: Tests

  • Pure functions are tested directly.
  • The builder is tested with a temp file and its events.
  • Run them all with node --test (see Testing with the Built-in Test Runner).

Simple lesson: test the pure parts directly, and test the stream parts with temp files.

Running It on Your Own Computer

  1. Make a folder report-card and copy the files from the steps into it.
  2. Run node --env-file=.env cli.ts --class 8 to build the report.
  3. Run node --test to check everything.
  4. Run node server.ts and open http://localhost:3000/reports/8.
  5. Add "bin" to package.json and run npm link, so report-card works anywhere.

Running .ts files directly needs a recent Node.js version. On older versions, run the JavaScript files, or use npx tsx.

What You Used From Each Chapter

  • Foundations: ES modules, .ts files, package.json with type and bin.
  • Asynchronous Node.js: await, events, custom errors.
  • System and Files: parseArgs, env files, fs/promises, readline streams, safe writes.
  • Networking: an HTTP API with JSON and 404s.
  • Quality and Production: tests, health checks, graceful shutdown.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
One big file for everythingSmall projects need no structureSplit pure logic, I/O and entry pointsHard to test and change
Printing inside the builderEvery part can logEmit events, let the caller printThe builder cannot be reused in the API
Stopping at the first bad rowBad data must stop everythingSkip it, report it and keep goingOne typo blocks the whole class report
Reading the whole CSV with readFileFiles are always smallStream it with readlineBig files fill memory
No test for bad inputHappy paths are enoughTest broken rows tooBugs hide in the edge cases

TL;DR

  1. Plan = pure functions, a builder, config, CLI, server, tests.
  2. CLI = parseArgs + --env-file + fail-fast config.
  3. Data = readline stream in, write-temp-then-rename out.
  4. Builder = EventEmitter + BadRowError with a code and line.
  5. API = routes, JSON errors, /health, SIGTERM shutdown.
  6. Tests = node:test for functions, temp files for streams.

Check Yourself

Exercises

What to Remember

  • Split a project into pure logic, I/O, entry points and tests.
  • Check options and settings at start-up.
  • Stream input, report bad data with custom errors, and keep going.
  • Emit events from reusable parts, and let callers decide what to show.
  • Give every API a health check and a graceful shutdown.
  • Test the pure parts directly and the I/O parts with temp files.

Next: Node.js Cheat Sheet. Keep it open while you build your own projects.