AI-powered customer support chatbot for Spur (Spur Commerce Pvt Ltd) — built with Node.js, TypeScript, React, and OpenAI.
I did not start coding first. I spent time in in-depth discussion with an LLM (and in Chatgpt) on everything related to the assignment — requirements, edge cases, token/cost/memory, prompting, safeguards, and UX — so I could complete as many aspects as possible in one go. I then gave that full discussion to Cursor, which produced PLAN.md with my suggestions, brainstorm, step-by-step phases, and problem–solution notes. Where something was wrong, I took the LLM’s advice and refined the plan. The result is a step-by-step markdown that reflects my thinking and balance.
For full context, build order, and my POV, please read PLAN.md.
If anything in this README is unclear, reading README + PLAN.md together should make the picture complete.
Execution (backend + frontend) was done with Cursor, after checking and understanding the plan. I accept that this end-to-end flow — discussion (e.g. with ChatGPT), planning, and execution (with Cursor) — was done in roughly 6 hours.
git clone <repo-url>
cd Spur-Software-Engineer-Hiring-Assignment # or your folder name
cd backend
npm install
cp .env.example .env
OPENAI_API_KEY=sk-your-keyPORT=3001 (optional, default 3001)FRONTEND_URL=http://localhost:5173 (optional, for CORS)npm run dev
database.db in backend/).Open a new terminal:
cd frontend
npm install
cp .env.example .env
VITE_API_URL=http://localhost:3001npm run dev
| App | Port | URL |
|---|---|---|
| Backend | 3001 | http://localhost:3001 |
| Frontend | 5173 | http://localhost:5173 |
| App | URL |
|---|---|
| Frontend | https://spur-frontend-livid.vercel.app/ |
| Backend | https://spur-software-engineer-hiring-assignment.onrender.com |
For the deployed frontend, the backend API is set as:
VITE_API_URL=https://spur-software-engineer-hiring-assignment.onrender.com| Where | Variable | Purpose |
|---|---|---|
| Backend | OPENAI_API_KEY |
OpenAI API key (required) |
| Backend | PORT |
Server port (default 3001) |
| Backend | FRONTEND_URL |
Frontend origin (for CORS in production) |
| Frontend | VITE_API_URL |
Backend API base URL. Local: http://localhost:3001. Production: https://spur-software-engineer-hiring-assignment.onrender.com |
better-sqlite3) — lightweight and easy to run locally, without the weight of a full RDBMS. Schema: conversations (id, createdAt, summary), messages (id, conversationId, sender, text, timestamp). History is fetched by sessionId on reload.├── backend/ # Express API, SQLite, OpenAI
│ ├── src/
│ │ ├── server.ts
│ │ ├── db.ts, dbInit.ts, types.ts
│ │ ├── routes/chat.ts
│ │ └── services/
│ │ ├── conversationService.ts
│ │ ├── llmService.ts # system prompt, few-shot, summarization
│ │ ├── queryRewriter.ts # enhance unclear queries
│ │ └── faqService.ts # quick FAQ match
│ └── .env.example, package.json, tsconfig.json
├── frontend/ # Vite + React + TypeScript
│ └── src/
│ ├── App.tsx # chat UI, pills, streaming, history
│ ├── main.tsx, index.css
│ └── .env.example
├── PLAN.md # Full execution plan, phases, and my reasoning (read this)
├── ASSIGNMENT.md # Assignment checklist
├── STEP-BY-STEP-GUIDE.md
└── README.md # This file
sessionId from localStorage and GET /chat/history/:sessionId; sends new messages via POST /chat/message and shows reply with streaming + typing indicator.sessionId (UUID); no login. Fine for a demo; real product would add auth.Base URL (local): http://localhost:3001
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Root; returns { message, status }. |
| GET | /chat/health |
Health check. Returns { status: "ok", timestamp }. |
| POST | /chat/message |
Send a chat message; returns AI reply and session id. |
| GET | /chat/history/:sessionId |
Get all messages for a session (for restoring chat on reload). |
{ "message": "user text", "sessionId": "optional-uuid" }{ "reply": "AI response", "sessionId": "uuid" }message required, non-empty string, max 1000 chars. Empty/long messages get 400 or a friendly reply in body.curl -X POST http://localhost:3001/chat/message \
-H "Content-Type: application/json" \
-d '{"message":"Do you ship to USA?"}'
sessionId — conversation/session UUID (e.g. from previous POST response).{ "messages": [ { "id", "conversationId", "sender", "text", "timestamp" }, ... ] }{ "messages": [] }.{ "status": "ok", "timestamp": 1234567890 }MIT