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
| Who | Can do |
|---|---|
| Anyone | Read 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:
- Store the menu in SQLite and answer clear errors.
- Log in with hashed passwords and JWTs.
- Place orders with validation and ownership checks.
- Test the whole API with
node --testand supertest. - Add docs, a health check and graceful shutdown.
Step 1: Database, Menu and Errors
db.jscreates three tables:users,menu,orders. Prices are whole rupees.errors.jshasHttpError. Any route canthrow 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,roleandname. requireAuthchecks 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
priyawithidli123, then use her token onPOST /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. validateOrderrejects bad input with 400 and adetailsobject 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.jsbuilds the app without callinglisten. 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 testbefore 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.jsondescribes every route, so other teams know how to use your API. See Building REST APIs.GET /healthzlets 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
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
app.listen inside app.js | One file is simpler | Build in app.js, listen in server.js | Tests try to open a port and clash |
Taking userId from the body | The client knows who it is | Use req.user.sub from the token | Students place orders as other students |
| Different login errors for user and password | Clear messages help | One message for both | Attackers learn valid usernames |
| Total price sent by the client | Saves a query | Work it out on the server | Students pay 1 rupee for a dosa |
| Each route sends its own errors | It is more flexible | Throw HttpError, one handler answers | Messages and status codes do not match |
| No tests for "not allowed" cases | Happy paths are enough | Test 401, 403 and 404 paths too | Security bugs reach production |
Check Yourself
Exercises
What to Remember
- Split the project into small files: config, database, errors, auth, validation, routes, app, server.
- Throw
HttpErrorfrom 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.