Project · Full-Stack Development
Project: make the Notes app production-ready
The brief
Take the Notes app from "works on my laptop" to "runs safely on the internet": load and check configuration from environment variables, send security headers, allow only your own frontend through CORS, add a health check, prove it with automated tests, deploy it with HTTPS and set up monitoring.
Why it matters
This is the gap between a student project and a professional one. Clients and employers ask: where are the secrets kept, what happens when the database goes down, how do you know it is up, and how do you know a change did not break it? Each step here is one of those answers.
Skills you practise
- environment variables and fail-fast config
- Content-Security-Policy, HSTS and other security headers
- CORS with credentials
- health checks with timeouts
- node --test
- deploying with HTTPS
- uptime monitoring and alerts
You need
- Node.js 20 or newer
- A free account on a hosting platform such as Render, Railway or Fly.io, and a free PostgreSQL database (Neon, Supabase or the platform's own)
- A free uptime monitor such as UptimeRobot or Better Stack
What you will have at the end
A live, monitored Notes API with validated config, security headers, CORS, a health check, a passing test suite in CI and a README runbook.
How it is marked (rubric, 100 points)
| Checks and outputs passEvery code check passes; the tests are green; the live health check shows 200 with the security headers. | 35 |
|---|---|
| Secure configurationSecrets only in environment variables, never logged; strict CORS; CSP and HSTS in production. | 25 |
| ReliabilityA health check that tests the database with a timeout; monitoring with alerts; a practised runbook. | 20 |
| Testing and CIMeaningful tests that run on every push with GitHub Actions. | 20 |
0/7 steps done
Step 1 of 7
Configuration that fails fast
loadConfig(env) returns a frozen object (nodeEnv, isProduction, port, databaseUrl, sessionSecret, allowedOrigins) from environment variables, with PORT defaulting to 3000, and otherwise throws one Error listing every problem, without ever including a secret's value.
Settings that differ between your laptop and the server (database address, secrets, allowed origins) come from environment variables, never from the code. Check them all when the app starts: a server that refuses to boot with a clear message is far better than one that crashes at 2 a.m. on the first login. Report every problem at once, so a fix takes one deploy, not five.
Never echo secret values in errors or logs. Object.freeze stops code elsewhere from changing the config by accident.
Run shows what your code prints. Check runs the step's tests. Ctrl+Enter checks too.