-
Notifications
You must be signed in to change notification settings - Fork 218
feat: implement form data #163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,4 @@ | |
|
|
||
| const { createServer } = require('./createServer'); | ||
|
|
||
| createServer().listen(5701, () => { | ||
| console.log(`Server is running on http://localhost:${5701} 🚀`); | ||
| console.log('Available at http://localhost:5701'); | ||
| }); | ||
| createServer().listen(5701); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using an environment-configurable port and a listen callback for clarity. For example, use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider making the port configurable and adding a listen callback/logging. For example: const port = process.env.PORT || 5701;
createServer().listen(port, () => console.log(`Listening on ${port}`));This is non-blocking — the current hard-coded port works — but it improves flexibility and observability when running in different environments. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,203 @@ | ||
| 'use strict'; | ||
|
|
||
| /* eslint-disable no-console */ | ||
|
|
||
| const { createReadStream, createWriteStream, mkdirSync } = require('fs'); | ||
| const http = require('http'); | ||
| const path = require('path'); | ||
|
|
||
| function createServer() { | ||
| /* Write your code here */ | ||
| // Return instance of http.Server class | ||
| const server = new http.Server(); | ||
|
|
||
| function getHandler(req, res) { | ||
| if (req.method !== 'GET') { | ||
| return; | ||
| } | ||
|
|
||
| if (req.url !== '/' && req.url !== '') { | ||
| res.statusCode = 404; | ||
| res.setHeader('Content-Type', 'text/plain'); | ||
| res.end('Not Found'); | ||
|
Comment on lines
+18
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The GET branch returns a 404 with Content-Type 'text/plain' for unknown URLs. It's not required to change this, but for consistency you may want to return an HTML 404 page (text/html; charset=utf-8) so browser users see a friendly page. If you keep it as plain text that's acceptable for the task, but ensure the form page itself is served with text/html. |
||
|
|
||
| return; | ||
| } | ||
|
|
||
| res.statusCode = 200; | ||
| res.setHeader('Content-Type', 'text/html; charset=utf-8'); | ||
|
Comment on lines
+25
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good: the GET handler explicitly sets status 200 and Content-Type to |
||
|
|
||
| const form = createReadStream(path.join(__dirname, 'index.html')); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GET handler streams index.html but does not set |
||
|
|
||
| form.on('error', (err) => { | ||
| console.error('Read stream error:', err); | ||
|
|
||
| if (!res.headersSent) { | ||
| res.statusCode = 500; | ||
| res.setHeader('Content-Type', 'text/html; charset=utf-8'); | ||
|
|
||
| res.end(); | ||
|
Comment on lines
+33
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The read-stream error handler checks whether headers were already sent, which is good. However, when responding with a 500 it currently calls |
||
| } else { | ||
| res.destroy(); | ||
| } | ||
| }); | ||
|
|
||
| res.on('close', () => { | ||
| form.destroy(); | ||
| }); | ||
|
|
||
| form.pipe(res); | ||
|
Comment on lines
+28
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When creating the read stream and piping ( |
||
| } | ||
|
|
||
| function postHandler(req, res) { | ||
| if (req.method !== 'POST') { | ||
| return; | ||
| } | ||
|
|
||
| if (req.url !== '/add-expense' && req.url !== '/submit-expense') { | ||
| res.statusCode = 404; | ||
| res.setHeader('Content-Type', 'text/plain'); | ||
| res.end('Not Found'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const file = path.resolve('db', 'expense.json'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code sets |
||
|
|
||
| let raw = ''; | ||
|
|
||
| req.setEncoding('utf8'); | ||
|
Comment on lines
+65
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no Content-Type inspection before parsing the body. Inspect |
||
|
|
||
| req.on('data', (chunk) => { | ||
| raw += chunk; | ||
| }); | ||
|
|
||
| req.on('end', () => { | ||
| const contentType = req.headers['content-type'] || ''; | ||
| let payload; | ||
|
|
||
| try { | ||
| if (contentType.includes('application/json')) { | ||
| payload = raw ? JSON.parse(raw) : {}; | ||
| } else if (contentType.includes('application/x-www-form-urlencoded')) { | ||
| const params = new URLSearchParams(raw); | ||
|
|
||
| payload = Object.fromEntries(params.entries()); | ||
| } else { | ||
| payload = {}; | ||
| } | ||
| } catch (parseErr) { | ||
| res.statusCode = 400; | ||
| res.setHeader('Content-Type', 'text/plain'); | ||
| res.end('Bad Request'); | ||
|
Comment on lines
+87
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error branches currently respond with text/plain (e.g., parse error, missing fields, internal server error). Per checklist, return HTML error pages for validation and save failures so the client receives readable messages in the browser. Keep appropriate status codes, but use |
||
|
|
||
| return; | ||
| } | ||
|
|
||
| const expense = { | ||
| date: payload.date, | ||
| title: payload.title, | ||
| amount: payload.amount, | ||
| }; | ||
|
|
||
| // Validation | ||
| const parsedAmount = | ||
| typeof expense.amount === 'string' | ||
| ? parseFloat(expense.amount) | ||
| : Number(expense.amount); | ||
| const parsedDate = new Date(expense.date); | ||
|
|
||
| const hasAll = Boolean(expense.date && expense.title && expense.amount); | ||
| const amountValid = | ||
| !Number.isNaN(parsedAmount) && Number.isFinite(parsedAmount); | ||
| const dateValid = !Number.isNaN(parsedDate.getTime()); | ||
|
|
||
| if (!hasAll || !amountValid || !dateValid) { | ||
| if (contentType.includes('application/json')) { | ||
| res.statusCode = 400; | ||
| res.setHeader('Content-Type', 'text/plain'); | ||
| res.end('Invalid payload'); | ||
| } else { | ||
| res.statusCode = 400; | ||
| res.setHeader('Content-Type', 'text/html; charset=utf-8'); | ||
|
|
||
| res.end( | ||
| `<!doctype html><html><head><meta charset="utf-8"><title>Error</title></head><body><h1>Invalid input</h1><p>Please provide a valid date, title, and numeric amount.</p></body></html>`, | ||
| ); | ||
| } | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const normalizedExpense = { | ||
| date: expense.date, | ||
| title: expense.title, | ||
| amount: String(expense.amount), | ||
| }; | ||
|
|
||
| try { | ||
| mkdirSync(path.dirname(file), { recursive: true }); | ||
|
Comment on lines
+63
to
+137
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Path to the data file uses |
||
| } catch (mkdirErr) { | ||
| console.error('Directory create error:', mkdirErr); | ||
| res.statusCode = 500; | ||
| res.setHeader('Content-Type', 'text/plain'); | ||
| res.end('Internal Server Error'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const writer = createWriteStream(file); | ||
|
|
||
| writer.on('error', (error) => { | ||
| console.error('Write stream error:', error); | ||
| res.statusCode = 500; | ||
| res.setHeader('Content-Type', 'text/plain'); | ||
| res.end('Internal Server Error'); | ||
|
Comment on lines
+149
to
+153
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The writer error handler sets headers and ends the response on write-stream errors. If the response has already begun, setting headers will throw. Consider checking |
||
| }); | ||
|
Comment on lines
+149
to
+154
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the |
||
|
|
||
| const pretty = JSON.stringify(normalizedExpense, null, 2); | ||
|
|
||
| writer.end(pretty, () => { | ||
| res.statusCode = 200; | ||
|
|
||
| if (contentType.includes('application/json')) { | ||
| res.setHeader('Content-Type', 'application/json'); | ||
| res.end(JSON.stringify(normalizedExpense)); | ||
| } else { | ||
| const escaped = pretty | ||
| .replace(/&/g, '&') | ||
| .replace(/</g, '<') | ||
| .replace(/>/g, '>'); | ||
|
|
||
| res.setHeader('Content-Type', 'text/html; charset=utf-8'); | ||
|
|
||
| res.end( | ||
| `<!doctype html><html><head><meta charset="utf-8"><title>Saved</title></head><body><h1>Expense saved</h1><pre>${escaped}</pre></body></html>`, | ||
|
Comment on lines
+156
to
+173
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The saved JSON is pretty-printed ( |
||
| ); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| req.on('error', (err) => { | ||
| console.error('Request error:', err); | ||
| res.statusCode = 400; | ||
| res.setHeader('Content-Type', 'text/plain'); | ||
| res.end('Bad Request'); | ||
| }); | ||
| } | ||
|
|
||
| server.on('request', getHandler); | ||
| server.on('request', postHandler); | ||
|
|
||
| server.on('request', (req, res) => { | ||
| if (req.method !== 'GET' && req.method !== 'POST') { | ||
| res.statusCode = 405; | ||
| res.setHeader('Content-Type', 'text/plain'); | ||
| res.end('Method Not Allowed'); | ||
| } | ||
| }); | ||
|
|
||
| server.on('error', () => {}); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. server.on('error', () => {}) currently swallows server-level errors. At minimum log the error so issues are visible during development (and optionally handle fatal errors). For example: server.on('error', (err) => {
console.error('Server error:', err);
});This will help debugging if the server fails to bind to a port or encounters other system-level errors. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The server currently registers an empty server-level error handler: |
||
|
|
||
| return server; | ||
| } | ||
|
|
||
| module.exports = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Document</title> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional improvement: the document <title> is currently |
||
| </head> | ||
| <body> | ||
| <form action="/add-expense" method="POST"> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Form action and method are correct: |
||
| <label for="date">Date:</label> | ||
| <input type="date" id="date" name="date" required /><br /><br /> | ||
|
Comment on lines
+10
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| <label for="title">Name:</label> | ||
| <input type="text" id="title" name="title" required /><br /><br /> | ||
|
Comment on lines
+13
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor UX suggestion: the label reads |
||
|
|
||
| <label for="amount">Sum:</label> | ||
| <input | ||
| type="number" | ||
| id="amount" | ||
| name="amount" | ||
| step="0.01" | ||
|
Comment on lines
+17
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| required | ||
|
Comment on lines
+11
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inputs have the correct |
||
| /><br /><br /> | ||
|
Comment on lines
+16
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| <button type="submit">Submit</button> | ||
| </form> | ||
| </body> | ||
| </html> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a listen callback and/or using an environment variable for the port so the startup is observable and configurable. For example, you could use
const PORT = process.env.PORT || 5701; createServer().listen(PORT, () => console.log(Server listening on ${PORT}));— this is optional but helpful for debugging and deployments.