Skip to content

Commit 1cf51b6

Browse files
authored
Merge pull request #16 from fuzziecoder/codex/create-backend-for-application
Add starter Node backend API for spots, catalog, and orders
2 parents 401f6de + 96c8441 commit 1cf51b6

5 files changed

Lines changed: 305 additions & 1 deletion

File tree

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,30 @@ BroCode Spot is a full-stack web application designed to streamline group orderi
9898
npm run dev
9999
```
100100

101+
### Optional: Run the local backend API
102+
103+
This project now includes a lightweight Node.js backend in `backend/server.js` that provides starter APIs for:
104+
- health check
105+
- login (mock)
106+
- catalog retrieval
107+
- spot listing
108+
- order creation/listing
109+
- bill summary
110+
111+
Start it with:
112+
113+
```bash
114+
npm run backend
115+
```
116+
117+
Run frontend + backend together:
118+
119+
```bash
120+
npm run dev:all
121+
```
122+
123+
By default, the backend runs on `http://localhost:4000`.
124+
101125
6. **Open in browser**
102126
```
103127
http://localhost:5173

backend/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Backend API (Starter)
2+
3+
A minimal Node.js backend for BroCode Spot.
4+
5+
## Start
6+
7+
```bash
8+
npm run backend
9+
```
10+
11+
Server starts at `http://localhost:4000` by default.
12+
13+
## Available endpoints
14+
15+
- `GET /api/health`
16+
- `POST /api/auth/login`
17+
- `GET /api/catalog`
18+
- `GET /api/catalog/:category` (`drinks`, `food`, `cigarettes`)
19+
- `GET /api/spots`
20+
- `GET /api/orders?spotId=...&userId=...`
21+
- `POST /api/orders`
22+
- `GET /api/bills/:spotId`
23+
24+
## Example login payload
25+
26+
```json
27+
{
28+
"username": "brocode",
29+
"password": "changeme"
30+
}
31+
```
32+
33+
## Note
34+
35+
Data is currently in-memory and resets whenever the process restarts. See `backend/store.js`.

backend/server.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { createServer } from 'node:http';
2+
import { randomUUID } from 'node:crypto';
3+
import { URL } from 'node:url';
4+
import { dataStore } from './store.js';
5+
6+
const port = Number(process.env.PORT || 4000);
7+
8+
const sendJson = (res, statusCode, body) => {
9+
res.writeHead(statusCode, {
10+
'Content-Type': 'application/json',
11+
'Access-Control-Allow-Origin': '*',
12+
'Access-Control-Allow-Headers': 'Content-Type',
13+
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
14+
});
15+
res.end(JSON.stringify(body));
16+
};
17+
18+
const readBody = (req) =>
19+
new Promise((resolve, reject) => {
20+
let data = '';
21+
22+
req.on('data', (chunk) => {
23+
data += chunk;
24+
});
25+
26+
req.on('end', () => {
27+
if (!data) {
28+
resolve({});
29+
return;
30+
}
31+
32+
try {
33+
resolve(JSON.parse(data));
34+
} catch {
35+
reject(new Error('Invalid JSON payload'));
36+
}
37+
});
38+
39+
req.on('error', reject);
40+
});
41+
42+
const server = createServer(async (req, res) => {
43+
const method = req.method || 'GET';
44+
const parsedUrl = new URL(req.url || '/', `http://localhost:${port}`);
45+
const path = parsedUrl.pathname;
46+
47+
if (method === 'OPTIONS') {
48+
sendJson(res, 204, {});
49+
return;
50+
}
51+
52+
if (method === 'GET' && path === '/api/health') {
53+
sendJson(res, 200, { status: 'ok', service: 'brocode-backend', timestamp: new Date().toISOString() });
54+
return;
55+
}
56+
57+
if (method === 'POST' && path === '/api/auth/login') {
58+
try {
59+
const { username, password } = await readBody(req);
60+
61+
if (!username || !password) {
62+
sendJson(res, 400, { error: 'username and password are required' });
63+
return;
64+
}
65+
66+
const user = dataStore.users.find((item) => item.username === username && item.password === password);
67+
68+
if (!user) {
69+
sendJson(res, 401, { error: 'invalid credentials' });
70+
return;
71+
}
72+
73+
const { password: _password, ...publicUser } = user;
74+
sendJson(res, 200, { token: `demo-token-${user.id}`, user: publicUser });
75+
return;
76+
} catch (error) {
77+
sendJson(res, 400, { error: error.message });
78+
return;
79+
}
80+
}
81+
82+
if (method === 'GET' && path === '/api/catalog') {
83+
sendJson(res, 200, dataStore.catalog);
84+
return;
85+
}
86+
87+
if (method === 'GET' && path.startsWith('/api/catalog/')) {
88+
const category = path.replace('/api/catalog/', '');
89+
const items = dataStore.catalog[category];
90+
91+
if (!items) {
92+
sendJson(res, 404, { error: `Unknown category: ${category}` });
93+
return;
94+
}
95+
96+
sendJson(res, 200, items);
97+
return;
98+
}
99+
100+
if (method === 'GET' && path === '/api/spots') {
101+
sendJson(res, 200, dataStore.spots);
102+
return;
103+
}
104+
105+
if (method === 'GET' && path === '/api/orders') {
106+
const spotId = parsedUrl.searchParams.get('spotId');
107+
const userId = parsedUrl.searchParams.get('userId');
108+
109+
const filteredOrders = dataStore.orders.filter((order) => {
110+
if (spotId && order.spotId !== spotId) return false;
111+
if (userId && order.userId !== userId) return false;
112+
return true;
113+
});
114+
115+
sendJson(res, 200, filteredOrders);
116+
return;
117+
}
118+
119+
if (method === 'POST' && path === '/api/orders') {
120+
try {
121+
const { spotId, userId, items } = await readBody(req);
122+
123+
if (!spotId || !userId || !Array.isArray(items) || items.length === 0) {
124+
sendJson(res, 400, { error: 'spotId, userId and at least one order item are required' });
125+
return;
126+
}
127+
128+
const parsedItems = items.map((item) => {
129+
const quantity = Number(item.quantity || 0);
130+
const unitPrice = Number(item.unitPrice || 0);
131+
132+
return {
133+
productId: item.productId,
134+
name: item.name,
135+
quantity,
136+
unitPrice,
137+
total: quantity * unitPrice,
138+
};
139+
});
140+
141+
const totalAmount = parsedItems.reduce((sum, item) => sum + item.total, 0);
142+
const newOrder = {
143+
id: randomUUID(),
144+
spotId,
145+
userId,
146+
items: parsedItems,
147+
totalAmount,
148+
createdAt: new Date().toISOString(),
149+
};
150+
151+
dataStore.orders.push(newOrder);
152+
sendJson(res, 201, newOrder);
153+
return;
154+
} catch (error) {
155+
sendJson(res, 400, { error: error.message });
156+
return;
157+
}
158+
}
159+
160+
if (method === 'GET' && path.startsWith('/api/bills/')) {
161+
const spotId = path.replace('/api/bills/', '');
162+
const orders = dataStore.orders.filter((order) => order.spotId === spotId);
163+
164+
const userTotals = orders.reduce((acc, order) => {
165+
acc[order.userId] = (acc[order.userId] || 0) + order.totalAmount;
166+
return acc;
167+
}, {});
168+
169+
const total = orders.reduce((sum, order) => sum + order.totalAmount, 0);
170+
171+
sendJson(res, 200, {
172+
spotId,
173+
total,
174+
userTotals,
175+
orderCount: orders.length,
176+
});
177+
return;
178+
}
179+
180+
sendJson(res, 404, { error: 'Route not found' });
181+
});
182+
183+
server.listen(port, () => {
184+
console.log(`Backend API running on http://localhost:${port}`);
185+
});

backend/store.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
export const dataStore = {
2+
users: [
3+
{
4+
id: 'u-1',
5+
username: 'brocode',
6+
password: 'changeme',
7+
name: 'Ram',
8+
role: 'admin',
9+
},
10+
{
11+
id: 'u-2',
12+
username: 'dhanush',
13+
password: 'changeme',
14+
name: 'Dhanush',
15+
role: 'user',
16+
},
17+
],
18+
spots: [
19+
{
20+
id: 'spot-2025-07-26',
21+
location: 'Attibele Toll Plaza',
22+
date: '2025-07-26T10:00:00.000Z',
23+
hostUserId: 'u-1',
24+
},
25+
],
26+
catalog: {
27+
drinks: [
28+
{ id: 'd-1', name: 'Brocode Beer', price: 180 },
29+
{ id: 'd-2', name: 'Kingfisher Beer', price: 170 },
30+
],
31+
food: [
32+
{ id: 'f-1', name: 'Beef Biriyani', price: 220 },
33+
{ id: 'f-2', name: 'Parotta', price: 30 },
34+
],
35+
cigarettes: [
36+
{ id: 'c-1', name: 'Marlboro', price: 25 },
37+
{ id: 'c-2', name: 'Classic', price: 20 },
38+
],
39+
},
40+
orders: [
41+
{
42+
id: 'ord-1',
43+
spotId: 'spot-2025-07-26',
44+
userId: 'u-2',
45+
items: [
46+
{
47+
productId: 'd-1',
48+
name: 'Brocode Beer',
49+
quantity: 2,
50+
unitPrice: 180,
51+
total: 360,
52+
},
53+
],
54+
totalAmount: 360,
55+
createdAt: '2025-07-26T10:30:00.000Z',
56+
},
57+
],
58+
};

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
"scripts": {
77
"dev": "vite",
88
"build": "vite build",
9-
"preview": "vite preview"
9+
"preview": "vite preview",
10+
"backend": "node backend/server.js",
11+
"dev:all": "npm run backend & npm run dev"
1012
},
1113
"dependencies": {
1214
"@google/genai": "^0.3.0",

0 commit comments

Comments
 (0)