Capstone: The Complete Canteen API

By Syncfusion®, Inc. · Last updated

One full project: SQLite tables, hashed passwords, JWT login, staff and student roles, validated orders with ownership checks, central errors, supertest tests, OpenAPI docs, health checks and graceful shutdown.

What you will learn

  • Combine every part of the course in one project
  • Store data in SQLite with safe queries
  • Protect routes with JWTs, roles and ownership
  • Handle every error in one place
  • Test the API and prepare it for deployment

Capstone: The Complete Canteen API

Time to put the whole course together. You will build the real Green Valley School Canteen API, step by step.

  • Capstone = one final project that uses everything you have learned.
  • It has a database, login with tokens, roles, validation, error handling, tests and API docs.
  • Each step adds a few files. The final project is the same one you would deploy.
  • Every lab runs the full project, so you can read and change any file.

Simple lesson: Real APIs are many small parts. Each part does one job.

What You Will Build

WhoCan do
AnyoneRead the menu
Students (Priya, Arjun)Log in, place orders, read their own orders
Staff (Meera)Add menu items, read every order

The project layout:

canteen-api/
  package.json
  src/
    config.js        settings from the environment
    db.js            SQLite tables and sample data
    errors.js        HttpError, 404 and the error handler
    auth.js          signToken, requireAuth, requireRole
    validate.js      checks for orders and menu items
    openapi.js       API description
    routes/
      menu.js  auth.js  orders.js
    app.js           builds the app (used by tests)
    server.js        starts it and handles SIGTERM
  test/
    api.test.js

In this project, you will:

  1. Store the menu in SQLite and answer clear errors.
  2. Log in with hashed passwords and JWTs.
  3. Place orders with validation and ownership checks.
  4. Test the whole API with node --test and supertest.
  5. Add docs, a health check and graceful shutdown.

Step 1: Database, Menu and Errors

  • db.js creates three tables: users, menu, orders. Prices are whole rupees.
  • errors.js has HttpError. Any route can throw new HttpError(404, "...").
  • Express 5 catches thrown errors, even in async code, and sends them to errorHandler.
  • The error handler hides details of 500 errors, but shows clear messages for 4xx errors.
  • See Working with a Database and Error Handling.
  • Adding a menu item without a token gets 401. You will log in next.

Simple lesson: Throw an HttpError anywhere. One error handler answers for all of them.

Step 2: Logging In

  • Passwords are stored as a salt and a PBKDF2 hash, never as plain text.
  • Login hashes the given password with the same salt and compares with timingSafeEqual.
  • A correct login returns a 15-minute JWT with sub, role and name.
  • requireAuth checks the token. requireRole("staff") checks the role.
  • See Cookies and Sessions and Token Authentication and Authorization.
  • The same message for "no such user" and "wrong password" stops attackers from guessing usernames.
  • Try logging in as priya with idli123, then use her token on POST /api/menu.

Simple lesson: Hash passwords. Sign a token at login. Check the token and role on protected routes.

Step 3: Orders

  • The whole orders router uses requireAuth, so every order route needs a login.
  • validateOrder rejects bad input with 400 and a details object per field.
  • The user ID comes from the token (req.user.sub), never from the request body.
  • Students see only their own orders. Staff see all. Other people's orders answer 404.
  • A JOIN adds the item name and price, so the total is worked out by the server.

Simple lesson: Validate, take the user from the token, and check ownership on every order route.

Step 4: Testing Everything

  • app.js builds the app without calling listen. Tests import it directly.
  • supertest sends requests to the app in memory. No port needed.
  • One test per rule: public menu, login needed, own orders, other orders hidden, bad input.
  • Run npm test before every commit and in CI. See Application Structure.

When you add a feature, add a test for it first. Then change the code until it passes. This habit is called test-driven development.

Simple lesson: If it is not tested, assume it is broken.

Step 5: Docs, Health and Shutdown

  • GET /api/openapi.json describes every route, so other teams know how to use your API. See Building REST APIs.
  • GET /healthz lets the host check the app is alive.
  • Broken JSON gets a clean 400, not a crash.
  • On SIGTERM, the server finishes current requests, closes the database, then exits.
  • See Deploying an Express App.

To run this on your computer, create the folders, copy each file, run npm install, then npm run dev. Node.js 22.5 or later is needed for node:sqlite.

Simple lesson: Document it, check its health, and stop it cleanly. Then it is ready to ship.

Where to Go Next

Your API is complete. Here are good next steps:

  • A real database file or PostgreSQL, with migrations. See Working with a Database.
  • Refresh tokens in httpOnly cookies, so students stay logged in.
  • Rate limits on /api/auth/login. See Express Security.
  • Request logs with request IDs. See Logging and Debugging.
  • Live order updates for the kitchen screen with SSE. See Calling APIs, Background Jobs and Real-Time.
  • TypeScript for the whole project. See Express with TypeScript.
  • A front end: a React or Angular app that calls this API.

Before going live, set a long random JWT_SECRET on the host. The default in config.js is only for learning.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
app.listen inside app.jsOne file is simplerBuild in app.js, listen in server.jsTests try to open a port and clash
Taking userId from the bodyThe client knows who it isUse req.user.sub from the tokenStudents place orders as other students
Different login errors for user and passwordClear messages helpOne message for bothAttackers learn valid usernames
Total price sent by the clientSaves a queryWork it out on the serverStudents pay 1 rupee for a dosa
Each route sends its own errorsIt is more flexibleThrow HttpError, one handler answersMessages and status codes do not match
No tests for "not allowed" casesHappy paths are enoughTest 401, 403 and 404 paths tooSecurity bugs reach production

Check Yourself

Exercises

What to Remember

  • Split the project into small files: config, database, errors, auth, validation, routes, app, server.
  • Throw HttpError from anywhere. One error handler answers every error.
  • Hash passwords, sign JWTs at login, and check tokens and roles with middleware.
  • Validate every input, take the user from the token, and check ownership.
  • Test with supertest on app.js, including the 401, 403 and 404 cases.
  • Add docs, a health check and SIGTERM handling, then deploy.

Next: Express.js Cheat Sheet. Every important Express 5 API on one page.