Skip to content

Commit ce1d64e

Browse files
committed
feat: implement frontend server with static file serving and API configuration
1 parent 08efdaa commit ce1d64e

5 files changed

Lines changed: 84 additions & 12 deletions

File tree

chat-app/frontend/Dockerfile

Lines changed: 0 additions & 9 deletions
This file was deleted.

chat-app/frontend/app.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ const statusEl = document.querySelector('#status');
55
const messagesEl = document.querySelector('#messages');
66
const refreshButton = document.querySelector('#refresh-button');
77

8+
const API_BASE_URL = (window.API_BASE_URL || '').replace(/\/$/, '');
9+
10+
function apiUrl(path) {
11+
return `${API_BASE_URL}${path}`;
12+
}
13+
814
let lastMessageCount = 0;
915

1016
function setStatus(message, type = '') {
@@ -54,7 +60,7 @@ function escapeHtml(value) {
5460
}
5561

5662
async function loadMessages() {
57-
const response = await fetch('/api/messages');
63+
const response = await fetch(apiUrl('/api/messages'));
5864

5965
if (!response.ok) {
6066
throw new Error('Could not load messages');
@@ -78,7 +84,7 @@ async function sendMessage(event) {
7884
setStatus('Sending message...');
7985

8086
try {
81-
const response = await fetch('/api/messages', {
87+
const response = await fetch(apiUrl('/api/messages'), {
8288
method: 'POST',
8389
headers: {
8490
'Content-Type': 'application/json',

chat-app/frontend/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ <h2>Messages</h2>
4949
</section>
5050
</main>
5151

52+
<script src="config.js"></script>
5253
<script src="app.js"></script>
5354
</body>
5455
</html>

chat-app/frontend/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
"name": "chat-app-frontend",
33
"version": "1.0.0",
44
"description": "Frontend for chat app",
5-
"main": "app.js",
5+
"main": "server.js",
66
"keywords": ["chat", "frontend"],
77
"author": "",
8+
"scripts": {
9+
"start": "node server.js",
10+
"dev": "node server.js"
11+
},
812
"license": "UNLICENSED",
913
"private": true,
1014
"dependencies": {}

chat-app/frontend/server.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const http = require('http');
2+
const fs = require('fs');
3+
const path = require('path');
4+
5+
const PORT = process.env.PORT || 3000;
6+
const API_BASE_URL = (process.env.API_BASE_URL || '').replace(/\/$/, '');
7+
const PUBLIC_DIR = __dirname;
8+
9+
const contentTypes = {
10+
'.html': 'text/html; charset=utf-8',
11+
'.css': 'text/css; charset=utf-8',
12+
'.js': 'application/javascript; charset=utf-8',
13+
'.json': 'application/json; charset=utf-8',
14+
'.svg': 'image/svg+xml',
15+
'.png': 'image/png',
16+
'.jpg': 'image/jpeg',
17+
'.jpeg': 'image/jpeg',
18+
'.ico': 'image/x-icon',
19+
};
20+
21+
function send(res, statusCode, body, contentType = 'text/plain; charset=utf-8') {
22+
res.writeHead(statusCode, { 'Content-Type': contentType });
23+
res.end(body);
24+
}
25+
26+
function readFile(filePath) {
27+
return fs.readFileSync(filePath);
28+
}
29+
30+
function serveFile(res, filePath) {
31+
try {
32+
const ext = path.extname(filePath).toLowerCase();
33+
const contentType = contentTypes[ext] || 'application/octet-stream';
34+
const body = readFile(filePath);
35+
res.writeHead(200, { 'Content-Type': contentType });
36+
res.end(body);
37+
} catch {
38+
send(res, 404, 'Not found');
39+
}
40+
}
41+
42+
const server = http.createServer((req, res) => {
43+
const urlPath = new URL(req.url, `http://${req.headers.host}`).pathname;
44+
45+
if (urlPath === '/config.js') {
46+
const config = `window.API_BASE_URL = ${JSON.stringify(API_BASE_URL)};\n`;
47+
return send(res, 200, config, 'application/javascript; charset=utf-8');
48+
}
49+
50+
const safePath = urlPath === '/' ? '/index.html' : urlPath;
51+
const filePath = path.join(PUBLIC_DIR, safePath);
52+
53+
if (!filePath.startsWith(PUBLIC_DIR)) {
54+
return send(res, 403, 'Forbidden');
55+
}
56+
57+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
58+
return serveFile(res, filePath);
59+
}
60+
61+
if (urlPath === '/' || urlPath === '/index.html') {
62+
return serveFile(res, path.join(PUBLIC_DIR, 'index.html'));
63+
}
64+
65+
send(res, 404, 'Not found');
66+
});
67+
68+
server.listen(PORT, () => {
69+
console.log(`Frontend running at http://localhost:${PORT}`);
70+
});

0 commit comments

Comments
 (0)