Создаем сервер с нуля

Самый простой сервер на node.js можно сделать, используя стандартный модуль http напрямую.

Сначала создаем новый проект:

npm init -y

Далее создаем файл index.js со следующим содержимым:

const http = require("http");

const srv = http.createServer(function(req, res) {
	res.writeHead(200, {'Content-Type': 'text/html'});
	res.write("Hello, World");
	res.end();
});
srv.listen(3000);

Разберем что здесь написано.
Первым делом через require подключаем модуль http.
Далее создаем объект сервера, передаем ему функцию-обработчик запросов, и запускаем сервер на порту 3000.
Функция-обработчик принимает на вход два параметра: первый - это объект запроса, а второй - это объект ответа.
Функция должна возвращать что-то через объект ответа. В данном случае функция возвращает в ответ заголовок с кодом 200 (ОК), с указанием Content-Type: text/html, а также сам ответ "Hello, World".
Попробуем запустить сервер следующей командой:

node index.js

Скрипт должен запуститься и работать, пока его принудительно не остановят сочетанием клавиш Ctrl+C.
Открываем браузер и в адресной строке пишем: http://localhost:3000.
Мы должны получить страницу, где будет написано "Hello, World".

Все, наш первый веб-сервер готов. Попробуем сделать так, чтобы он возвращал содержимое файла index.html. Для обращения к файловой системе задействуем модуль fs.

В файле index.js пишем следующее:

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

const srv = http.createServer(handler);
if(srv) srv.listen(3000);

function handler(req, res) {
    fs.readFile('index.html', { encoding: 'utf8' }, function(err, file) {
        if (!err) {
            res.writeHead(200, { 'Content-Type': 'text/html'});
            res.write(file);
            res.end();
        }
    });
}

Здесь мы сделали несколько изменений.
Во-первых, мы вынесли обработчик в отдельную функцию handler, и при создании сервера передаем ему указатель на нее.
Во-вторых, подключили модуль fs, и в функции handler читаем файл index.html и записываем его содержимое в объект результата функцией write.

Создаем файл index.html, заполняем его чем-нибудь вроде такого:

<!DOCTYPE HTML>
<html>
  <head>
    <title>Test Page</title>
  </head>
  <body>
    <h1>Test page</h1>
  </body>
</html>

Запускаем сервер:

node index.js

Теперь по адресу http://localhost:3000 мы увидим нашу страницу index.html, которая выведет на экран надпись "Test page"

Следующим шагом попробуем как-то использовать переданный нам URL с параметрами.
Сам URL нам в функцию обработчика передается в объекте запроса req, в его свойстве url. Передается он в виде строки, начиная от корневого слэша, например для адреса http://localhost:3000/sample/?id=21 в свойстве url будет значение /sample/?id=21. Параметры запроса можно получить, используя модуль url, а именно его функции parse и query.

Новый файл index.js:

const http = require("http");
const fs = require('fs');
const url = require('url');

const srv = http.createServer(handler);
if(srv) srv.listen(3000);

function handler(req, res) {
	var request = url.parse(req.url, true);
	var params = request.query;
	var filename = params.file || "index";
	fs.readFile(filename + ".html", { encoding: "utf8" }, function(err, file) {
		if (!err) {
			res.writeHead(200, { "Content-Type": "text/html"});
			res.write(file);
			res.end();
		}
		else {
			res.writeHead(404, { "Content-Type": "text/html"});
			res.write("Error 404. File not found");
			res.end();
		}
	});
}

Здесь мы с помощью функции url.parse строим объект запроса, а затем функцией query достаем из него параметры.
Сервер сейчас анализирует единственный передающийся параметр - file, в котором должно передаваться имя файла для отображения (в реальной системе так делать совершенно точно не следует). Далее к имени файла добавляется расширение и если имя файла не передано, будет использоваться index.html.
После всех манипуляций с именем файла, продолжается все так же как в предыдущем примере, за исключением того, что если файл не будет найден, то браузеру вернется сообщение об ошибке вместе с кодом ошибки 404 (Page not found).

Вот и все. Основа сервера готова. Дальше все зависит от фантазии разработчика.

Сервер на фреймворке Express

Для упрощения создания сервера на node.js обычно не пишут все самостоятельно, а используют какой-нибудь серверный фреймворк. Фреймворк берет на себя большую часть скучной работы по обработке запросов и маршрутизации, а также предоставляет разработчику некоторое API для более удобной работы. Одним из таких фреймворков является Express.js. Он довольно прост в освоении, но обладает достаточным функционалом для построения полноценного сервера.

Давайте разберемся как с ним работать.
Для начала его нужно загрузить в папку node_modules следующей командой:

npm install express --save

Теперь пишем в нашем файл index.js следующее:

var express = require("express");
var app = express();

app.get("/", function(req, res) {
    res.send("<h2>Привет Express!</h2>");
});

app.listen(3000);

Довольно похоже на наш сервер, не правда ли? Основное отличие - это несколько обработчиков вместо одного. Каждый обработчик регистрируется на свой собственный маршрут.
Разберем подробнее что тут написано.
Первым делом подключаем модуль express и создаем объект сервера через конструктор express(). После создания объекта регистрируем свой обработчик на маршрут "/" (то есть по-сути на адрес http://localhost:3000/). Обработчики могут регистрироваться через функции get, post, put и delete, что соответствует методам запросов к серверу.
И, наконец, последней строкой запускаем сервер на порту 3000. Открыв браузер по адресу http://localhost:3000, мы должны увидеть приветственную надпись.

Попробуем повторить функционал нашего сервера на express.

var express = require("express");
const fs = require('fs');

var app = express();

app.get("/", function(req, res) {
	var str = "<h2>Привет Express!</h2>";
	str += "<div><a href='/file/a'>Файл А</a></div>";
	str += "<div><a href='/about'>О сайте</a></div>";
	res.send(str);
});

app.get("/file/:name", function(req, res) {
	var filename = req.params.name;
	fs.readFile(filename + ".html", { encoding: "utf8" }, function(err, file) {
		if (!err) {
			res.set("Content-Type", "text/html");
			res.status(200).send(file);
			res.end();
		}
		else {
			res.set("Content-Type", "text/html");
			res.status(404).send("Error 404. File not found");
			res.end();
		}
	});
});

app.get("/about", function(req, res) {
	res.send("<h1>О сайте</h1>");
});

app.listen(3000);

Здесь у нас зарегистрировано три обработчика.
Первый - для маршрута "/", то есть для главной страницы сайта.
Второй - для маршрута "/file" - для получения содержимого HTML-файла.
И третий - для маршрута "/about" - для вывода информации о сайте.

У обработчика для файлов, кроме того, указан параметр, указываемый в маршруте через знак двоеточия. Получить в обработчике значение этого параметра можно через req.params.

Запускаем сервер обратно через команду:

node index.js

И любуемся результатом в браузере по адресу http://localhost:3000.

Более подробно о express.js можно почитать на официальном сайте http://expressjs.com/ru/.