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
25 changes: 25 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node Form Data</title>
</head>
<body>
<form method="POST" action="/add-expense">
<div>
<label for="date">Date:</label>
<input type="date" id="date" name="date" required>
</div>
<div>
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
</div>
<div>
<label for="amount">Amount:</label>
<input type="number" id="amount" name="amount" step="0.01" required>
</div>
<button type="submit">Submit</button>
</form>
</body>
</html>
97 changes: 95 additions & 2 deletions src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,101 @@
'use strict';

const { Server } = require('http');
const fs = require('fs');
const path = require('path');
const mime = require('mime-types');
const querystring = require('querystring');

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

function createServer() {
/* Write your code here */
// Return instance of http.Server class
const server = new Server();

server.on('request', (req, res) => {
try {
const url = new URL(req.url || '', `http://${req.headers.host}`);
const requestedPath = url.pathname.slice(1) || 'index.html';
const realPath = path.join('public', requestedPath);
const mimeType = mime.contentType(path.extname(realPath)) || 'text/plain';

if (requestedPath === 'add-expense') {
if (req.method === 'POST') {
const chunks = [];

req.on('data', (chunk) => chunks.push(chunk));

req.on('end', () => {
const body = Buffer.concat(chunks).toString();
const contentType = req.headers['content-type'] || '';
const isJSON = contentType.includes('application/json');
const { date, title, amount } = isJSON
? JSON.parse(body) || {}
: querystring.parse(body) || {};

if (!date || !title || !amount) {
res.statusCode = 400;
res.setHeader('Content-Type', 'text/plain');
res.end('Invalid form');

return;
}

fs.writeFileSync(
dbPath,
JSON.stringify({ date, title, amount }, null, 2),
);
Comment thread
DmytroMykoliv marked this conversation as resolved.
Comment on lines +43 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This logic overwrites the db/expense.json file with only the new expense, but the requirement is to accumulate all expenses.

To fix this, you need to:

  1. Read the contents of db/expense.json before this step.
  2. Parse the JSON content into an array. If the file is empty or doesn't exist, you can start with an empty array [].
  3. Add the new expense object to this array.
  4. Finally, write the entire updated array back to the file.

Copy link
Copy Markdown
Author

@DmytroMykoliv DmytroMykoliv Mar 29, 2026

Choose a reason for hiding this comment

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

@mateacademy-ai-mentor
I have reviewed your comments, and I'd like to clarify a few points that were misunderstood:

  • There are no requirements to accumulate all expenses.
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
- save it to a `db/expense.json` file
- and return an HTML page with well-formatted JSON
  • According to the test, it should overwrite the existing file content.
it('should save data for valid expense on "POST /submit-expense" request', async () => {
        fs.writeFileSync(dataPath, JSON.stringify({}));

        const expense = {
          date: '2024-01-25',
          title: 'Test Expense',
          amount: '100',
        };
        const response = await axios.post(`${HOST}/add-expense`, expense);

        expect(response.status).toBe(200);

        const savedData = JSON.parse(fs.readFileSync(dataPath));

        expect(savedData).toStrictEqual(expense);
      });


if (isJSON) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ date, title, amount }));

return;
}

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

res.end(`
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Expense saved</title>
</head>
<body>
<h1>Expense saved</h1>
<pre>${JSON.stringify({ date, title, amount }, null, 2)}</pre>
<a href="/">Back</a>
</body>
</html>
`);
});
} else {
res.statusCode = 400;
res.end('Wrong method!');
}

return;
}

if (!fs.existsSync(realPath)) {
res.statusCode = 404;
res.end('Not Found');

return;
}

const dataStream = fs.createReadStream(realPath);

res.statusCode = 200;
res.setHeader('Content-Type', mimeType);
dataStream.pipe(res);
} catch (error) {
res.statusCode = 500;
res.end('Server Error');
}
});

return server;
}

module.exports = {
Expand Down
Loading