Node.js для начинающих: создаем серверное приложение за 5 шагов
Для кого эта статья:
- Начинающие и опытные веб-разработчики, желающие изучить Node.js
- Разработчики, переходящие с фронтенда на бэкенд с использованием JavaScript
Специалисты, интересующиеся созданием серверных приложений и REST API
Node.js — это настоящий прорыв для разработчиков, мечтающих о единой экосистеме JavaScript и на фронтенде, и на бэкенде. Освоив эту технологию, вы входите в элитный клуб универсальных разработчиков, способных создавать полноценные приложения от А до Я. Я разрабатываю на Node.js уже более 7 лет, и за это время убедился: это не просто модный инструмент, а фундаментальное изменение парадигмы серверной разработки. В этом руководстве я проведу вас от нуля до полноценного рабочего сервера через пять чётких шагов. 🚀
Мечтаете создавать серверные приложения, но не знаете, с чего начать? Обучение веб-разработке от Skypro — ваш билет в мир профессиональной разработки с Node.js. За 9 месяцев вы пройдете путь от основ до создания сложных серверных приложений с REST API, работой с базами данных и микросервисной архитектурой. Наши студенты получают реальные проекты в портфолио и уже после 5-го месяца обучения устраиваются на стажировки в IT-компании.
Что такое Node.js и почему он идеален для серверной разработки
Node.js — это среда выполнения JavaScript на стороне сервера, построенная на V8, высокопроизводительном движке JavaScript от Google Chrome. Появившись в 2009 году, Node.js совершил революцию в серверной разработке, позволив JavaScript выйти за пределы браузера.
Ключевое преимущество Node.js — его неблокирующая, асинхронная архитектура, позволяющая обрабатывать тысячи одновременных соединений без создания дополнительных потоков. Это делает Node.js исключительно эффективным для приложений с интенсивным вводом-выводом, таких как чаты, потоковые сервисы и API.
Александр Петров, технический директор Когда мы начинали разработку новой версии нашей платформы для обработки финансовых транзакций, перед нами стоял выбор: использовать проверенный временем Java-стек или рискнуть с Node.js. Многие в команде сомневались, справится ли "JavaScript на сервере" с нашими требованиями — 5000+ транзакций в секунду с минимальной задержкой. Мы решили провести эксперимент и разработали прототипы на обеих технологиях. Результаты поразили даже скептиков: Node.js не только справился с нагрузкой, но и показал на 30% меньшую задержку при обработке запросов. А скорость разработки? Мы построили MVP за 2 недели вместо планируемых 6. С тех пор вся наша инфраструктура микросервисов работает на Node.js, а время вывода новых функций сократилось втрое.
Давайте рассмотрим преимущества Node.js по сравнению с традиционными серверными технологиями:
| Параметр | Node.js | PHP | Java |
|---|---|---|---|
| Модель выполнения | Асинхронная, событийная | Синхронная | Многопоточная |
| Потребление памяти | Низкое | Среднее | Высокое |
| Скорость разработки | Высокая | Высокая | Средняя |
| Производительность при I/O-операциях | Превосходная | Средняя | Хорошая |
| Экосистема пакетов | npm (1.3+ млн пакетов) | Composer (~300k пакетов) | Maven (~380k пакетов) |
Основные преимущества Node.js для серверной разработки:
- Единый язык — JavaScript используется как на фронтенде, так и на бэкенде, что упрощает взаимодействие команд и ускоряет разработку.
- npm — крупнейший в мире репозиторий программных пакетов с более чем 1.3 миллионами библиотек.
- Высокая производительность — благодаря движку V8 и асинхронной модели Node.js отлично справляется с многопользовательскими приложениями реального времени.
- Микросервисная архитектура — Node.js идеален для создания легковесных, независимых микросервисов.
- Активное сообщество — постоянно растущая экосистема разработчиков и инструментов.

Установка Node.js и подготовка среды разработки
Прежде чем погрузиться в разработку, необходимо правильно настроить рабочую среду. Процесс установки Node.js прост и не занимает много времени, независимо от вашей операционной системы. 🛠️
Способы установки Node.js:
- Официальный установщик — самый простой метод, доступный на nodejs.org. Рекомендую выбирать LTS (Long Term Support) версию для продакшн-разработки.
- Менеджеры версий — позволяют устанавливать несколько версий Node.js и легко переключаться между ними:
- nvm (Node Version Manager) — для Linux и macOS
- nvm-windows — для Windows
- n — упрощенная альтернатива nvm
- Пакетные менеджеры ОС — apt для Ubuntu, Homebrew для macOS, Chocolatey для Windows.
После установки проверьте версии Node.js и npm (менеджер пакетов, устанавливается автоматически с Node.js):
node -v
npm -v
Теперь настроим базовую структуру проекта:
- Создайте новую директорию для вашего проекта и перейдите в неё:
mkdir my-node-server
cd my-node-server
- Инициализируйте проект с npm:
npm init -y
Этот команда создаст файл package.json с базовой конфигурацией проекта. Флаг -y принимает значения по умолчанию, но впоследствии вы можете отредактировать этот файл.
Рекомендую также установить несколько полезных инструментов для разработки:
npm install nodemon --save-dev
Nodemon автоматически перезапускает сервер при изменении файлов, что ускоряет процесс разработки.
Обновите раздел "scripts" в вашем package.json:
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
Теперь для запуска сервера в режиме разработки достаточно выполнить:
npm run dev
Популярные редакторы кода для разработки на Node.js:
| Редактор | Преимущества | Рекомендуемые расширения |
|---|---|---|
| Visual Studio Code | Бесплатный, отличная интеграция с Node.js, встроенный терминал | ESLint, Prettier, Node.js Extension Pack |
| WebStorm | Платный, мощная среда для JavaScript, встроенный отладчик | Встроенная поддержка Node.js |
| Sublime Text | Быстрый, легковесный, настраиваемый | Babel, JavaScript Completions, Node.js |
| Atom | Бесплатный, настраиваемый, модульный | atom-ternjs, language-babel, node-debugger |
Создание первого HTTP-сервера на Node.js с примерами кода
Теперь, когда у нас настроена среда разработки, давайте создадим базовый HTTP-сервер с использованием встроенного модуля http. Это фундаментальный шаг в понимании работы Node.js. 🌐
Создайте файл index.js в корне проекта и добавьте следующий код:
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Привет, мир! Мой первый Node.js сервер работает!\n');
});
server.listen(port, hostname, () => {
console.log(`Сервер запущен по адресу http://${hostname}:${port}/`);
});
Разберём этот код по шагам:
- Мы импортируем встроенный модуль http с помощью функции require()
- Указываем хост (127.0.0.1 или localhost) и порт (3000) для нашего сервера
- Создаём сервер с помощью метода createServer(), который принимает функцию-обработчик запросов
- Обработчик запросов получает два объекта: request (req) и response (res)
- Устанавливаем статус ответа 200 (успешный), указываем тип контента и отправляем текстовое сообщение
- Запускаем сервер на указанном порту и выводим сообщение в консоль
Запустите сервер:
node index.js
Или с помощью nodemon, если вы его установили:
npm run dev
Теперь откройте браузер и перейдите по адресу http://127.0.0.1:3000/. Вы должны увидеть сообщение "Привет, мир! Мой первый Node.js сервер работает!".
Давайте усложним наш сервер, добавив обработку разных URL и методов HTTP:
const http = require('http');
const url = require('url');
const hostname = '127.0.0.1';
const port = 3000;
const users = [
{ id: 1, name: 'Иван', email: 'ivan@example.com' },
{ id: 2, name: 'Мария', email: 'maria@example.com' },
];
const server = http.createServer((req, res) => {
// Парсинг URL и получение пути
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const trimmedPath = path.replace(/^\/+|\/+$/g, '');
// Получение метода запроса
const method = req.method.toLowerCase();
// Базовая маршрутизация
if (trimmedPath === '' && method === 'get') {
// Главная страница
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Добро пожаловать на наш сервер!\n');
} else if (trimmedPath === 'users' && method === 'get') {
// Получение списка пользователей
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(users));
} else if (trimmedPath.startsWith('users/') && method === 'get') {
// Получение конкретного пользователя по ID
const userId = parseInt(trimmedPath.split('/')[1]);
const user = users.find(u => u.id === userId);
if (user) {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(user));
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: 'Пользователь не найден' }));
}
} else {
// Маршрут не найден
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('404 – Страница не найдена\n');
}
});
server.listen(port, hostname, () => {
console.log(`Сервер запущен по адресу http://${hostname}:${port}/`);
});
Теперь наш сервер поддерживает несколько маршрутов:
- GET / – возвращает приветственное сообщение
- GET /users – возвращает список всех пользователей в формате JSON
- GET /users/1 – возвращает информацию о пользователе с ID 1
Попробуйте обратиться к этим маршрутам через браузер или с помощью инструмента вроде Postman, чтобы увидеть, как работает маршрутизация.
Михаил Соколов, ведущий разработчик Помню свой первый опыт с Node.js в 2015 году. Я был фронтенд-разработчиком, работавшим исключительно с jQuery и Bootstrap, когда мой руководитель поставил задачу реализовать небольшой сервис для сбора аналитики с сайтов наших клиентов. В то время у меня был выбор: либо изучить PHP (что казалось логичным для серверной части), либо рискнуть с Node.js, о котором я только начал слышать. Решил дать Node.js шанс — ведь я уже знал JavaScript. Первые 2-3 дня я испытывал настоящий когнитивный диссонанс. Асинхронные функции и колбэки вместо привычного последовательного кода сбивали с толку. Но момент прозрения наступил, когда я создал свой первый HTTP-сервер — точно такой же, как в примере выше — и увидел, как легко можно обрабатывать запросы и возвращать данные. Спустя неделю я смог запустить полноценный API, способный обрабатывать до 1000 запросов в секунду с минимальной нагрузкой на сервер. Для сравнения: по оценкам нашего DevOps-инженера, аналогичное PHP-решение потребовало бы в 2-3 раза больше серверных ресурсов. С тех пор я не оглядывался назад — Node.js стал моим основным инструментом для серверной разработки.
Разработка серверного приложения с фреймворком Express.js
Хотя встроенный модуль http позволяет создавать серверы, для сложных приложений он становится неудобным. Express.js — это минималистичный и гибкий фреймворк, существенно упрощающий разработку веб-приложений на Node.js. 🛠️
Установим Express:
npm install express --save
Теперь создадим новый файл app.js и реализуем тот же функционал, что и ранее, но с использованием Express:
const express = require('express');
const app = express();
const port = 3000;
// Middleware для парсинга JSON
app.use(express.json());
// Данные
const users = [
{ id: 1, name: 'Иван', email: 'ivan@example.com' },
{ id: 2, name: 'Мария', email: 'maria@example.com' },
];
// Маршруты
app.get('/', (req, res) => {
res.send('Добро пожаловать на наш сервер!');
});
app.get('/users', (req, res) => {
res.json(users);
});
app.get('/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const user = users.find(u => u.id === userId);
if (user) {
res.json(user);
} else {
res.status(404).json({ error: 'Пользователь не найден' });
}
});
// Создание нового пользователя
app.post('/users', (req, res) => {
const { name, email } = req.body;
// Проверка обязательных полей
if (!name || !email) {
return res.status(400).json({ error: 'Требуются поля name и email' });
}
// Создание нового пользователя
const newUser = {
id: users.length + 1,
name,
email
};
users.push(newUser);
res.status(201).json(newUser);
});
// Обработка несуществующих маршрутов
app.use((req, res) => {
res.status(404).send('404 – Страница не найдена');
});
// Запуск сервера
app.listen(port, () => {
console.log(`Сервер Express запущен на порту ${port}`);
});
Обратите внимание, как значительно сократился и стал понятнее код. Express предоставляет удобный API для маршрутизации, middleware и обработки запросов.
Основные концепции Express.js:
- Маршрутизация – Express позволяет определять обработчики для различных HTTP методов и URL путей.
- Middleware – функции, имеющие доступ к объектам запроса, ответа и следующей функции в цикле запрос-ответ.
- Шаблонизаторы – Express может использовать различные шаблонизаторы (Pug, EJS, Handlebars) для генерации HTML.
- Статические файлы – Express упрощает обслуживание статических файлов (CSS, JavaScript, изображения).
Давайте улучшим наше приложение, организовав его более структурировано:
- Создайте папку routes и файл userRoutes.js внутри неё:
// routes/userRoutes.js
const express = require('express');
const router = express.Router();
// Данные (в реальном приложении это была бы база данных)
const users = [
{ id: 1, name: 'Иван', email: 'ivan@example.com' },
{ id: 2, name: 'Мария', email: 'maria@example.com' },
];
// Получение всех пользователей
router.get('/', (req, res) => {
res.json(users);
});
// Получение пользователя по ID
router.get('/:id', (req, res) => {
const userId = parseInt(req.params.id);
const user = users.find(u => u.id === userId);
if (user) {
res.json(user);
} else {
res.status(404).json({ error: 'Пользователь не найден' });
}
});
// Создание нового пользователя
router.post('/', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Требуются поля name и email' });
}
const newUser = {
id: users.length + 1,
name,
email
};
users.push(newUser);
res.status(201).json(newUser);
});
// Обновление пользователя
router.put('/:id', (req, res) => {
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === userId);
if (userIndex === -1) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
const { name, email } = req.body;
if (!name && !email) {
return res.status(400).json({ error: 'Требуется хотя бы одно поле для обновления' });
}
users[userIndex] = {
...users[userIndex],
...(name && { name }),
...(email && { email })
};
res.json(users[userIndex]);
});
// Удаление пользователя
router.delete('/:id', (req, res) => {
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === userId);
if (userIndex === -1) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
const deletedUser = users[userIndex];
users.splice(userIndex, 1);
res.json(deletedUser);
});
module.exports = router;
- Обновите app.js, используя модульный подход:
const express = require('express');
const userRoutes = require('./routes/userRoutes');
const app = express();
const port = 3000;
// Middleware
app.use(express.json());
// Логгирование запросов
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} – ${req.method} ${req.path}`);
next();
});
// Маршруты
app.get('/', (req, res) => {
res.send('Добро пожаловать на наш API сервер!');
});
// Подключение маршрутов пользователей
app.use('/users', userRoutes);
// Обработка ошибок
app.use((req, res) => {
res.status(404).json({ error: 'Маршрут не найден' });
});
// Запуск сервера
app.listen(port, () => {
console.log(`Сервер Express запущен на порту ${port}`);
});
Теперь мы создали полноценный REST API с CRUD операциями для ресурса "пользователи". Сравните эти два подхода:
| Аспект | Нативный http модуль | Express.js |
|---|---|---|
| Маршрутизация | Ручная реализация через условные операторы | Встроенная система маршрутизации (app.get, app.post и т.д.) |
| Middleware | Требуется ручная реализация | Встроенная поддержка через app.use() |
| Обработка ошибок | Ручное управление статусами и ответами | Централизованные обработчики ошибок |
| Модульность | Сложно реализовать | Поддержка роутеров и модульной структуры |
| Статические файлы | Требует ручной реализации | express.static() |
Асинхронное программирование и работа с базами данных
Асинхронное программирование — одна из самых мощных особенностей Node.js, позволяющая обрабатывать множество операций одновременно без блокировки основного потока выполнения. Это особенно важно при работе с базами данных и внешними API. 📊
Исторически в Node.js использовались колбэки для асинхронных операций, но современный код использует промисы и async/await для большей читаемости и лучшего управления ошибками.
Давайте рассмотрим, как подключиться к базе данных MongoDB с использованием популярной библиотеки mongoose:
- Установите необходимые пакеты:
npm install mongoose dotenv
- Создайте файл .env в корне проекта для хранения конфиденциальных данных:
MONGODB_URI=mongodb://localhost:27017/myapp
PORT=3000
- Создайте папку models и файл User.js:
// models/User.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', userSchema);
- Обновите userRoutes.js для работы с моделью MongoDB:
// routes/userRoutes.js
const express = require('express');
const User = require('../models/User');
const router = express.Router();
// Получение всех пользователей
router.get('/', async (req, res) => {
try {
const users = await User.find();
res.json(users);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Получение пользователя по ID
router.get('/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
res.json(user);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Создание нового пользователя
router.post('/', async (req, res) => {
try {
const { name, email } = req.body;
// Проверяем, существует ли пользователь с таким email
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: 'Пользователь с таким email уже существует' });
}
// Создаем нового пользователя
const newUser = new User({ name, email });
await newUser.save();
res.status(201).json(newUser);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// Обновление пользователя
router.put('/:id', async (req, res) => {
try {
const { name, email } = req.body;
const updatedUser = await User.findByIdAndUpdate(
req.params.id,
{ name, email },
{ new: true, runValidators: true }
);
if (!updatedUser) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
res.json(updatedUser);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// Удаление пользователя
router.delete('/:id', async (req, res) => {
try {
const deletedUser = await User.findByIdAndDelete(req.params.id);
if (!deletedUser) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
res.json({ message: 'Пользователь успешно удален' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
module.exports = router;
- Обновите app.js для подключения к MongoDB:
// app.js
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const userRoutes = require('./routes/userRoutes');
// Загрузка переменных окружения
dotenv.config();
const app = express();
const port = process.env.PORT || 3000;
// Middleware
app.use(express.json());
// Подключение к MongoDB
mongoose.connect(process.env.MONGODB_URI)
.then(() => console.log('Успешное подключение к MongoDB'))
.catch(err => {
console.error('Ошибка подключения к MongoDB', err);
process.exit(1);
});
// Маршруты
app.get('/', (req, res) => {
res.send('Добро пожаловать на наш API сервер!');
});
app.use('/users', userRoutes);
// Обработка ошибок
app.use((req, res) => {
res.status(404).json({ error: 'Маршрут не найден' });
});
// Запуск сервера
app.listen(port, () => {
console.log(`Сервер запущен на порту ${port}`);
});
Вы можете заметить, что мы используем async/await для обработки асинхронных операций с базой данных. Это делает код более понятным и упрощает обработку ошибок.
Рассмотрим основные подходы к асинхронному программированию в Node.js:
- Колбэки – традиционный подход с передачей функций обратного вызова:
fs.readFile('file.txt', (err, data) => {
if (err) throw err;
console.log(data);
});
- Промисы – более современный подход, позволяющий цепочку вызовов:
fs.promises.readFile('file.txt')
.then(data => console.log(data))
.catch(err => console.error(err));
- Async/Await – синтаксический сахар над промисами, делает код похожим на синхронный:
async function readFile() {
try {
const data = await fs.promises.readFile('file.txt');
console.log(data);
} catch (err) {
console.error(err);
}
}
При работе с асинхронным кодом важно помнить о правильной обработке ошибок. В Node.js необработанные исключения могут привести к аварийному завершению всего приложения.
Для работы с реляционными базами данных, такими как MySQL или PostgreSQL, можно использовать Sequelize или TypeORM – ORM-библиотеки, предоставляющие удобный API для взаимодействия с базами данных.
Для повышения производительности приложения можно использовать кэширование с помощью Redis:
// Пример кэширования с Redis
const express = require('express');
const redis = require('redis');
const util = require('util');
const User = require('./models/User');
const app = express();
const client = redis.createClient();
// Промисификация методов Redis
const getAsync = util.promisify(client.get).bind(client);
const setAsync = util.promisify(client.set).bind(client);
// Middleware для кэширования
const cacheMiddleware = async (req, res, next) => {
const userId = req.params.id;
try {
// Проверяем, есть ли данные в кэше
const cachedUser = await getAsync(`user:${userId}`);
if (cachedUser) {
// Если есть, возвращаем их
res.json(JSON.parse(cachedUser));
} else {
// Если нет, передаем управление следующему middleware
next();
}
} catch (err) {
next();
}
};
// Маршрут с использованием кэширования
app.get('/users/:id', cacheMiddleware, async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
// Сохраняем результат в кэше на 60 секунд
await setAsync(`user:${req.params.id}`, JSON.stringify(user), 'EX', 60);
res.json(user);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
Node.js открывает новый мир серверной разработки для JavaScript-программистов. От простых HTTP-серверов до сложных API с базами данных — Node.js позволяет создать практически любое веб-приложение. Ключевое преимущество — не нужно переключаться между языками программирования. Тот же JavaScript, который вы используете на фронтенде, теперь работает и на бэкенде. Многие компании, от стартапов до гигантов вроде Netflix и Walmart, уже перешли на Node.js, получив впечатляющие результаты. Если у вас есть опыт в JavaScript, Node.js — ваш логичный следующий шаг. Сообщество активно развивает технологию, а экосистема npm содержит готовые решения практически для любой задачи. Начните с простого сервера, постепенно добавляйте новые возможности, и вскоре вы освоите эту мощную платформу.