Backend API for the Speed Puzzle app.
- Language: TypeScript
- Framework: Express (Node.js)
- Runtime: Vercel Functions (Node.js)
- Database: MongoDB Atlas
This repo is now configured to run as Vercel Serverless Functions:
- The Express app lives in
/api/index.ts
and exports the app (export default app
), noapp.listen()
. - On cold start, we await DB initialization before serving requests.
- API routes return 500 on internal errors (instead of 406) and log a helpful message.
- A diagnostic route
GET /__debug
is available during testing.
api/
index.ts # Express app exported for Vercel Functions
src/
constants/events.ts
controllers/Global.ts
services/
MongoDB.ts # MongoDB client (Node driver)
Users.ts
Scores.ts
scripts/
seed.ts
Create these for local dev in a .env
file at the project root and set the same keys in Vercel → Project → Settings → Environment Variables (Production scope):
MONGODB_URI="mongodb+srv://<user>:<pass>@<cluster>.mongodb.net/?retryWrites=true&w=majority&appName=<YourApp>"
API_PORT=3000 # used only for local non-serverless runs
For Vercel → Atlas connectivity, Atlas recommends allowing
0.0.0.0/0
(all IPs) because Vercel uses dynamic egress IPs. Tighten later with Secure Compute / private networking if required.
npm install
npm run dev # uses `vercel dev` to emulate the platform
Seed demo data
npm run seed # inserts users + scores
npm run seed -- --reset # wipe & reseed
The app runs as a Vercel Function locally; you don’t need
npm start
for serverless.
One-time:
npm i -g vercel
vercel login
Deploy:
vercel # Preview deployment
vercel --prod # Production deployment
Vercel project settings (for an API-only app):
- Framework Preset: Other
- Build Command: empty
- Output Directory: empty
This avoids the “Missing public directory” error that applies to static sites. If you previously set an Output Directory (e.g., public/
), clear it.
Base URL (prod): https://<your-deployment>.vercel.app
Health check.
200 OK → "<h1>Hello world</h1>"
curl -X GET https://<your-deployment>.vercel.app/ | jq
Returns a few runtime facts to confirm env setup.
{ "hasMongoURI": true, "vercelEnv": "production", "nodeVersion": "v20.x" }
curl -X GET https://<your-deployment>.vercel.app/__debug | jq
Create a user aligned with the mobile schema (optionally with an initial score).
Body
{ "userName": "Ada Lovelace", "password": "SeedUser#2025", "score": 420 }
Responses
200 OK
→ array of users (public fields only)409 Conflict
→ "User Already Exist."
curl -X POST https://<your-deployment>.vercel.app/adduser \
-H "Content-Type: application/json" \
-d '{"userName": "Ada Lovelace", "password": "SeedUser#2025", "score": 420}' | jq
Add a score for an existing user.
Body
{ "value": 451 }
Responses
201 Created
→{ "userId": "<ObjectId>", "value": 451 }
400 Bad Request
ifvalue
is not a number409 Conflict
if rejected by the acceptance rule
curl -X POST https://<your-deployment>.vercel.app/users/AdaLovelace/scores \
-H "Content-Type: application/json" \
-d '{"value": 451}' | jq
Check a raw score against the global minimum across all scores. Does not persist.
Body
{ "score": 300 }
Responses
200 OK
if accepted409 Conflict
if rejected
curl -X POST https://<your-deployment>.vercel.app/score \
-H "Content-Type: application/json" \
-d '{"score": 300}' | jq
List all users (public fields only).
Response (example)
[
{
"_id": "665e...",
"userName": "Ava Johnson",
"createdAt": 1720000000000,
"updatedAt": 1720000000000
}
]
curl -X GET https://<your-deployment>.vercel.app/users | jq
Top limit
scores (default 10). Each item returns { score, user }
.
Response (example)
[
{ "score": 497, "user": { "_id": "665e...", "userName": "Noah Smith", "createdAt": 172..., "updatedAt": 172... } }
]
curl -X GET https://<your-deployment>.vercel.app/scores/top?limit=10 | jq
Bottom limit
scores (default 10). Each item returns { score, user }
.
Response (example)
[
{ "score": 50, "user": { "_id": "665e...", "userName": "Jane Doe", "createdAt": 172..., "updatedAt": 172... } }
]
curl -X GET https://<your-deployment>.vercel.app/scores/bottom?limit=10 | jq
Compare a raw score against the top 10 scores. Does not persist.
Body
{ "score": 300 }
Responses
200 OK
if accepted409 Conflict
if rejected
curl -X POST https://<your-deployment>.vercel.app/scores/compare \
-H "Content-Type: application/json" \
-d '{"score": 300}' | jq
Compare a raw score against the bottom 10 scores. Does not persist.
Body
{ "score": 300 }
Responses
200 OK
if accepted409 Conflict
if rejected
curl -X POST https://<your-deployment>.vercel.app/scores/compare-bottom \
-H "Content-Type: application/json" \
-d '{"score": 300}' | jq
- Express on Vercel: Files under
/api
become functions; export the Express app and let Vercel handle the server. Don’t callapp.listen()
. - Cold start readiness: We use an async
ready
promise to awaitinitDB()
on first request. - MongoDB Node driver:
serverSelectionTimeoutMS
defaults to 30000ms; we set shorter timeouts to fail fast during testing. - Connection reuse: Prefer a singleton client cached across invocations to avoid reconnect storms in serverless environments. (See
MongoDB.ts
.)
Your project is configured like a static site. For an API-only app, clear Build Command and Output Directory (Project → Settings).
Usually network access to Atlas. Ensure Atlas Network Access allows your deployment to connect. For Vercel, allow 0.0.0.0/0
during testing (use strong creds), or adopt a fixed-egress solution for production. Also verify you use the SRV URI (mongodb+srv://…
).
Set them in Vercel → Project → Settings → Environment Variables (correct environment), then redeploy.
We now return 500 for server errors. If you still see 406, ensure your routes aren’t catching and rethrowing as 406.
{
"dev": "vercel dev",
"build": "echo \"No build step for Vercel Functions\"",
"seed": "tsx src/scripts/seed.ts"
}
Vercel builds TypeScript in
/api
automatically; a realbuild
step isn’t required for this API-only project.
MIT