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.tsand 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 /__debugis 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 runsFor 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 platformSeed demo data
npm run seed # inserts users + scores
npm run seed -- --reset # wipe & reseedThe app runs as a Vercel Function locally; you don’t need
npm startfor serverless.
One-time:
npm i -g vercel
vercel loginDeploy:
vercel # Preview deployment
vercel --prod # Production deploymentVercel 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/ | jqReturns a few runtime facts to confirm env setup.
{ "hasMongoURI": true, "vercelEnv": "production", "nodeVersion": "v20.x" }curl -X GET https://<your-deployment>.vercel.app/__debug | jqCreate 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}' | jqAdd a score for an existing user.
Body
{ "value": 451 }Responses
201 Created→{ "userId": "<ObjectId>", "value": 451 }400 Bad Requestifvalueis not a number409 Conflictif rejected by the acceptance rule
curl -X POST https://<your-deployment>.vercel.app/users/AdaLovelace/scores \
-H "Content-Type: application/json" \
-d '{"value": 451}' | jqCheck a raw score against the global minimum across all scores. Does not persist.
Body
{ "score": 300 }Responses
200 OKif accepted409 Conflictif rejected
curl -X POST https://<your-deployment>.vercel.app/score \
-H "Content-Type: application/json" \
-d '{"score": 300}' | jqList 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 | jqTop 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 | jqBottom 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 | jqCompare a raw score against the top 10 scores. Does not persist.
Body
{ "score": 300 }Responses
200 OKif accepted409 Conflictif rejected
curl -X POST https://<your-deployment>.vercel.app/scores/compare \
-H "Content-Type: application/json" \
-d '{"score": 300}' | jqCompare a raw score against the bottom 10 scores. Does not persist.
Body
{ "score": 300 }Responses
200 OKif accepted409 Conflictif 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
/apibecome functions; export the Express app and let Vercel handle the server. Don’t callapp.listen(). - Cold start readiness: We use an async
readypromise to awaitinitDB()on first request. - MongoDB Node driver:
serverSelectionTimeoutMSdefaults 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
/apiautomatically; a realbuildstep isn’t required for this API-only project.
MIT