8
\$\begingroup\$

In order to learn web server programming in Node and JavaScript, I decided to implement a simple web server that only does one thing:

  • Display data of the HTTP request as an HTML table.

The actual data to display is arbitrary. In this example I'm displaying the contents of the header, along with the method, url, and http versions.

Here is the working solution:

const http = require('http');

const port = 3344;

const htmlCSS = `
  <style>
    body {
      font-family: sans-serif;
    }

    td {
      border: 1px solid black;
      padding: 0.5rem;
    }

    table {
      margin: 0 auto;
    }
  </style>
`;

const server = http.createServer((req, res) => {
  const {
    headers,
    method,
    url
  } = req;

  console.log(`\n${headers['user-agent']}\n${method}\n${url}`);
  console.log(new Date());
  console.log();

  writeResponse(req, res);
});

server.listen(port);

function writeResponse(req, res) {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/html');
  writeBody(req, res);
  res.end();
}

function writeBody(req, res) {
  const htmlTitle = '<title>Your Request Is</title>';
  const htmlEncode = '<meta charset="UTF-8">';
  const htmlHead = `<head>${htmlTitle}${htmlCSS}${htmlEncode}</head>`;
  const htmlTop = `<html lang="en">${htmlHead}<body>`;
  const htmlBot = '</body></html>';

  res.write(htmlTop);
  writeTable(req, res);
  res.write(htmlBot);
}

function writeTable(req, res) {
  const htmlTableTop = '<table><tbody>';
  const htmlTableBot = '</tbody></table>';

  res.write(htmlTableTop);
  writeAllRows(req, res);
  res.write(htmlTableBot);
}

function writeAllRows(req, res) {
    const {
    httpVersion,
    httpVersionMinor,
    httpVersionMajor,
    headers,
    method,
    url
  } = req;

  for (const key in headers) {
    writeRow(res, key, headers[key]);
  }
  writeRow(res, 'method', method);
  writeRow(res, 'url', url);
  writeRow(res, 'httpVersion', httpVersion);
  writeRow(res, 'httpVersionMinor', httpVersionMinor);
  writeRow(res, 'httpVersionMajor', httpVersionMajor);
}

function writeRow(res, key, val) {
  const entry = `<tr><td>${key}</td><td>${val}</td></tr>`;
  res.write(entry);
}

While I have some particular questions, I'm looking forward for all kinds of feedback! So if you see anything at all that can be improved, any comment is much appreciated!

  • Can I use other modules to make this job easier?
  • For a job this simple, is it worth to work with HTML and CSS as separate files?
  • How are HTML and CSS normally handled inside JS files (edits, appends, etc)?
  • How well am I handling the construction of HTML and CSS?
  • Can this be done more concisely?
  • What would you do differently to improve this in any way?
\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

Can I use other modules to make this job easier?

You can use some web framework like fastify or express. This would ease the code a lot

For a job this simple, is it worth to work with HTML and CSS as separate files?

Assumig your project will grown, it is a good approach to move some "static text" in a separated file.

How are HTML and CSS normally handled inside JS files (edits, appends, etc)?

You have made a server side rendering (SSR) by your hand. Since it would be hard to manage manually all those piece of strings and would be also dandgerous to some script injection, you could use some fremework as vue or other templating systems like handlebarsjs or others - here a list

How well am I handling the construction of HTML and CSS?

There could be some security issue for links like that if you start to process those values:

http://localhost:3000/?user=%3Cimg%20src=%27aaa%27%20onerror=alert(1)%3E

the framework protect you from these cases usually.

This code:

  const htmlTitle = '<title>Your Request Is</title>'
  const htmlEncode = '<meta charset="UTF-8">'
  const htmlHead = `<head>${htmlTitle}${htmlCSS}${htmlEncode}</head>`
  const htmlTop = `<html lang="en">${htmlHead}<body>`

produce every time the same output string, so you could do it once instead of every request: this will stress less the nodejs garbage collector for high throughput site.

Can this be done more concisely?

You could write less code using some Object and Array functions:

function stringTemplates (request) {
  return `<html lang="en">
<head>
<title>Your Request Is</title>
</head>
<body>
<table><tbody>
${Object.entries(request.headers).map(processLine).join('')}
${['httpVersion', 'httpVersionMinor', 'httpVersionMajor', 'method', 'url'].map(prop => processLine([prop, request[prop]])).join('')}
</tbody></table>
</body>
</html>
`
}

function processLine ([header, value]) {
  return `<tr><td>${header}</td><td>${value}</td></tr>`
}

What would you do differently to improve this in any way?

This shows only GET, but in order to learn the HTTP standard it would useful to show in:

  • query parameters
  • form parameter (with different encoding)
\$\endgroup\$
2
\$\begingroup\$

What would you do differently to improve this in any way?

I am not sure how expensive it is but I would aim to minimize the number of times res.write() is called. For this I would have the functions to generate rows return a string and collect the strings for each row into an array, then join the array using Array.prototype.join() to pass to a single call to res.write().


The code makes good use of const to keep the scope of variables limited and avoid accidental re-assignment. It can also be used for constant values that should never change during run-time.

Constants can be declared with uppercase or lowercase, but a common convention is to use all-uppercase letters. 1

Names like htmlCSS could be HTML_CSS, port could be PORT. That way anyone reading the code will know those values should not be changed. Bear in mind that any object/array declared with const is not immutable unless wrapped in a call to Object.freeze().

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.