Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/test.yml-template
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Test

on:
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"license": "GPL-3.0",
"devDependencies": {
"@mate-academy/eslint-config": "latest",
"@mate-academy/scripts": "^1.8.6",
"@mate-academy/scripts": "^2.1.3",
"axios": "^1.7.2",
"eslint": "^8.57.0",
"eslint-plugin-jest": "^28.6.0",
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Form data (with Node.js)

Implement an app that
- shows an HTML form with an info about an expense (date, title and amount)
- receives its data in a POST request
Expand Down
94 changes: 92 additions & 2 deletions src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,98 @@
'use strict';

const http = require('http');
const fs = require('fs');
const path = require('path');

function createServer() {
/* Write your code here */
// Return instance of http.Server class
return http.createServer(async (req, res) => {
const { url, method } = req;

if (method === 'GET' && url === '/') {
const htmlPath = path.join(__dirname, 'index.html');

res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
fs.createReadStream(htmlPath).pipe(res);

return;
}

if (
method === 'POST' &&
(url === '/add-expense' || url === '/submit-expense')
) {
let body = '';

req.on('data', (chunk) => {
body += chunk;
});

req.on('end', () => {
const contentType = req.headers['content-type'] || '';
const isJsonRequest = contentType.includes('application/json');
let expense;

try {
if (contentType.includes('application/json')) {
expense = JSON.parse(body);
} else if (
contentType.includes('application/x-www-form-urlencoded')
) {
expense = Object.fromEntries(new URLSearchParams(body));
} else {
expense = {};
}
} catch {
res.writeHead(400);

return res.end('Invalid JSON');
}

if (!expense.date || !expense.title || expense.amount === undefined) {
res.writeHead(400);

return res.end('Invalid form data');
}

const dbPath = path.resolve('db', 'expense.json');

// NOTE:
// expense.json overwrites on each request
// because task tests expect the file to contain
// a single expense object.
// In a real application we would store an array of expenses.
fs.writeFileSync(dbPath, JSON.stringify(expense, null, 2), 'utf-8');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation overwrites expense.json with the new expense on every submission, so only the last submitted expense is stored. To save a history of expenses, you should read the existing data from the file, add the new expense to a list (array), and then write the entire updated list back to the file.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation still overwrites the expense.json file with each new submission, which goes against the core requirement to preserve all previous entries. The correct logic is to read the file first, parse its JSON content into an array, add the new expense to that array, and then write the entire updated array back to the file.


if (isJsonRequest) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(expense));
} else {
const prettyJson = JSON.stringify(expense, null, 2);

res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });

res.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>Saved expense</h1>
<pre>${prettyJson}</pre>
</body>
</html>
`);
}
});

return;
}

res.writeHead(404);
res.end('Not Found');
});
}

module.exports = {
Expand Down
5 changes: 5 additions & 0 deletions src/db/expense.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"date": "2024-01-25",
"title": "Test Expense",
"amount": "100"
}
Comment on lines +1 to +5

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To support saving multiple expenses, the root of this JSON file should be an array. For example, it could start as an empty array []. This will allow your server logic to read the file, parse it into an array, add new expenses, and write the full array back, preserving all entries.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But tests doesn't pass with it

17 changes: 17 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/add-expense" method="POST">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A standard HTML form submission sends data as application/x-www-form-urlencoded. However, the server is expecting the data in application/json format, as it uses JSON.parse() to process the request body. You'll need to use JavaScript to intercept the form's submit event, create a JSON object from the form data, and send it using the fetch API.

<input type="date" name="date" required>
<input type="text" name="title" required>
<input type="number" name="amount" required>

<button type="submit">Save</button>
</form>
</body>
</html>