Платформа Node.js: руководство по эксплуатации для начинающих

Приглашаем вас на небольшую обзорную экскурсию. Место действия – платформа Node.js. Польза и новые впечатления гарантированы.

Введение в Node.js

Node.js – это среда для серверной разработки на языке JavaScript. Она кроссплатформенная и имеет открытый исходный код. Используется большей частью для написания веб-серверов, но имеет и массу других возможностей.

Появившись в далеком 2009 году, когда JavaScript уже начал считаться серьезным языком, Node завоевала огромную популярность и фактически стала лидером в сфере веб-разработки. Если звезды GitHub что-то значат, то судите сами – их больше 46 тысяч. Почему такой успех? Все просто.

Она простая

Платформа Node.js действительно очень проста для освоения, но все же сначала следует разобраться в самом JavaScript, особенно в его асинхронных концепциях.

Она асинхронная

JavaScript работает в одном потоке, используя события и функции обратного вызова для его разгрузки. Это было классно на фронтенде, это по-прежнему классно на сервере!

Практически все объекты в Node.js наследуют от класса EventEmitter, то есть способны работать с событиями. Подробнее об этом вы можете прочитать здесь.

Если вы плохо понимаете саму концепцию асинхронности, загляните сюда.

Огромное количество модулей и библиотек

Пакетный менеджер npm обеспечивает стремительное развитие экосистемы Node.js. Сейчас в нем более 500 тысяч опенсорсных пакетов, и каждый день появляются новые. Кроме того, у Node.js шикарная стандартная библиотека.

Старый-добрый JavaScript

Тот же самый, который уже используют миллионы разработчиков на фронтенде. Теперь они могут смело переходить на server-side, не изучая принципиально другой инструмент.

Отличия, конечно, есть. Прежде всего, в Node.js нет DOM, cookie и прочих браузерных API. Зато есть множество собственных полезных методов и полный контроль над средой выполнения кода. Здесь можно без страха и Babel использовать самые современные возможности языка, не озираясь на ограничения.

Отличается также система импорта. Браузеры начинают внедрять модули ES6, а платформа Node.js использует CommonJS с его require.

Движок V8

Это опенсорсный проект, написанный на C++, который активно развивается и совершенствуется усилиями тысяч разработчиков.

Хотя JavaScript считается интерпретируемым языком, на деле процесс его обработки не так уж прост. Это уже давно взрослый серьезный язык, который может работать на протяжении нескольких часов подряд, поэтому имеет смысл создавать готовый откомпилированный код. Современные движки сочетают интерпретацию и JIT-компиляцию (just in time), что делает их очень быстрыми.

Платформа Node.js под капотом

Не углубляясь в тонкости функционирования платформы, взглянем на ее основные части:

  • цикл обработки событий – единственный поток, исполняющий функции из стека вызовов и очереди событий;
  • стек вызовов – LIFO-очередь выполняемых функций кода;
  • очередь событий – коллбэки произошедших событий.

А здесь вы найдете небольшую экскурсию для начинающих под капот Node.js.

Установка

Платформа Node.js может быть установлена несколькими способами:

Также есть несколько вариантов развертывания приложений:

Обычно с помощью Node.js мы запускаем на выполнение файлы скриптов, но ее также можно использовать в REPL-режиме.

npm

Установка и обновление пакетов

Пакетный менеджер Node.js берет на себя всю работу по загрузке и установке внешних зависимостей проекта. Все файлы при этом загружаются в папку node_modules.

// установка всех пакетов,
// перечисленных в файле package.json
npm install

// установка конкретного пакета
npm install <package-name>

Команду можно запускать с различными опциями:

  • --save – сохранение пакета в разделе dependencies файла package.json. Это значит, что он необходим для полноценной работы готового приложения.
  • --save-dev – сохранение в разделе devDependencies, то есть в списке зависимостей, нужных для разработки (например, для тестирования).
  • -g – глобальная установка. Куда именно установится глобальный пакет, можно узнать с помощью команды npm root -g.

Также можно указать конкретную версию:

npm install -g webpack@4.16.4

package.json

Файл package.json – ключевой элемент Node.js-приложений. Это своего рода манифест проекта, включающий множество важных данных о нем:

  • name – название приложения или пакета, а также имя папки, в которой он хранится;
  • license – тип лицензии;
  • author – автор. Здесь можно указать имя (name), почту (email), адрес сайта (url);
  • contributors – другие участники разработки;
  • version – текущая версия;
  • description – краткое описание;
  • keywords – ключевые слова;
  • homepage – веб-страница проекта;
  • repository – репозиторий;
  • main – точка входа;
  • private – со значением true не позволяет случайную публикацию в npm;
  • scripts – набор команд с псевдонимами, которые можно запустить из терминала как npm run <taskName>;
  • dependencies – список зависимостей проекта;
  • devDependencies – список зависимостей для разработки;
  • engines – версия Node;
  • browserlist – поддерживаемые браузеры (или их версии);
  • bugs – ссылка на баг-трекер;
  • специфические свойства, например, eslintConfig, babel и другие с настройками различных инструментов.
{
  "name": "test-project",
  "license": "MIT",
  "author": {
    "name": "Flavio Copes",
    "email": "flavio@flaviocopes.com",
    "url": "https://flaviocopes.com"
  },
  "contributors": [
    "Flavio Copes <flavio@flaviocopes.com> (https://flaviocopes.com)"
  ],
  "version": "1.0.0",
  "description": "A Vue.js project",
  "keywords": [
    "email",
    "machine learning",
    "ai"
  ],
  "homepage": "https://flaviocopes.com/package",
  "repository": "github:flaviocopes/testing",
  "main": "src/main.js",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "unit": "jest --config test/unit/jest.conf.js --coverage",
    "test": "npm run unit"
  },
  "dependencies": {
    "vue": "^2.5.2"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "webpack": "^3.6.0"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ],
  "bugs": "https://github.com/flaviocopes/package/issues"
}

Программирование на Node.js

Hello world

В качестве традиционного helloworld-приложения послужит простой веб-сервер.

// подключение модуля http
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('Hello World\n')                     // отправка ответа
})

// настройка прослушивания порта
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`)
})

Модуль http обеспечивает работу с сетью.

Сохраните этот файл под именем server.js и вызовите его:

node server.js

После запуска сервера отработает коллбэк метода listen и в консоли появится сообщение.

Если вы отправите серверу запрос (открыв в браузере страницу http://127.0.0.1:3000), сгенерируется событие request и сработает коллбэк, установленный при создании. Функция получит объект запроса с заголовками и прочими данными и объект ответа, который будет возвращен клиенту после установки нужных параметров.

Импорт и экспорт

Платформа Node.js позволяет разбивать функциональность приложения на модули и подключать их при необходимости. Для импорта модуля используется конструкция require:

const library = require('./library')

Здесь адрес указывается относительно текущей директории.

Все, что находится в файле модуля, по умолчанию скрыто для внешнего кода, поэтому нужно специально указать, какая функциональность будет доступна с помощью module.exports:

// 1 способ - импорт объекта
const car = {
  brand: 'Ford',
  model: 'Fiesta'
}
module.exports = car

// 2 способ - импорт свойств
const car = {
  brand: 'Ford',
  model: 'Fiesta'
}
exports.car = car

Импорт будет выглядеть так:

// 1 способ
const car = require('./car')

// 2 способ
const data = require('./items')
const car = data.car

Переменные окружения и аргументы командной строки

Все переменные среды доступны как свойства объекта process.env, а аргументы, переданные из терминала, хранятся в process.argv:

process.env.NODE_ENV // переменная NODE_ENV

// перебор аргументов командной строки
process.argv.forEach((val, index) => {
  console.log(`${index}: ${val}`)
})

Ввод и вывод данных

С основным потоком вывода работает объект console, сходный с браузерной консолью.

const x = 'x'
const y = 'y'

console.log(x, y) // вывод переменных
console.log('My %s has %d years', 'cat', 2) // форматированный вывод
console.log('%O', Number) // вывод объекта

Другие полезные методы объекта console:

  • clear – очищение;
  • count – подсчет элементов;
  • trace –  получение стека вызовов;
  • time и timeEnd – подсчет времени;
  • error – вывод в поток stderr.

Доступ к потоку ввода process.stdin дает модуль readline.

// подключение и настройка модуля
const readline = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout
})

// вывод вопроса и коллбэк
readline.question(`What's your name?`, (name) => {
  console.log(`Hi ${name}!`)
  readline.close()
})

После того как пользователь введет свое имя, программа отправит ему приветствие и завершится.

Продвинутую работу с пользовательским вводом обеспечивает пакет Inquirer.js.

Выход из программы

Закрыть программу можно не только из терминала комбинацией ctrl+c, но и непосредственно из кода.

  • радикальный способ – принудительная остановка процесса с прерыванием всех операций. При этом нужно указать код завершения.
    process.exit(1) // завершение процесса с кодом 1 - необработанная ошибка
  • отправка уведомлений процессу – POSIX-сигналы
    // установка обработчика
    process.on('SIGTERM', () => {
      app.close(() => {
        console.log('Process terminated')
      })
    })
    
    // отправка сигнала
    process.kill(process.pid, 'SIGTERM')
    

HTTP-запросы и веб-сокеты

За обычные запросы HTTP отвечает уже знакомый нам из helloworld-примера модуль http, который умеет не только серверы создавать. У него есть масса полезных свойств и методов, которые можно найти в документации.

Пример GET-запроса:

const https = require('https')
const options = {
  hostname: 'flaviocopes.com',
  port: 443,
  path: '/todos',
  method: 'GET'
}
const req = https.request(options, (res) => {
  console.log(`statusCode: ${res.statusCode}`)
  res.on('data', (d) => {
    process.stdout.write(d)
  })
})
req.on('error', (error) => {
  console.error(error)
})
req.end()

Пример POST-запроса:

const https = require('https')
const data = JSON.stringify({
  todo: 'Buy the milk'
})
const options = {
  hostname: 'flaviocopes.com',
  port: 443,
  path: '/todos',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': data.length
  }
}
const req = https.request(options, (res) => {
  console.log(`statusCode: ${res.statusCode}`)
  res.on('data', (d) => {
    process.stdout.write(d)
  })
})
req.on('error', (error) => {
  console.error(error)
})
req.write(data)
req.end()

Альтернативной HTTP являются веб-сокеты, которые реализованы в Node-библиотеке ws.

const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 8080 })
wss.on('connection', ws => {
  ws.on('message', message => {
    console.log(`Received message => ${message}`)
  })
  ws.send('ho!')
})

Живой пример можно посмотреть на Glitch.

Работа с файлами

За взаимодействие с файловой системой отвечает встроенный модуль fs. Он предоставляет огромное количество методов для получения информации, чтения, записи и перезаписи и множества других операций. Полный список доступных действий вы можете найти в официальной документации. Практически все методы асинхронные, но имеют синхронный аналог.

// подключение модуля fs
const fs = require('fs')

// получение информации о файле
fs.stat('/Users/flavio/test.txt', (err, stats) => {
  if (err) {
    console.error(err)
    return
  }
  stats.isFile() //true
  stats.isDirectory() //false
  stats.isSymbolicLink() //false
  stats.size //1024000 //= 1MB
})

// чтение файла
fs.readFile('/Users/flavio/test.txt', (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(data)
})

// запись в файл
const content = 'Some content!'
fs.writeFile('/Users/flavio/test.txt', content, (err) => {
  if (err) {
    console.error(err)
    return
  }
  // запись произведена успешно
})

При работе с файловой системой приходится оперировать путями к файлам и папкам. Этим заведует модуль path.

const path = require('path')

require('path').basename('/test/something.txt') //something.txt
require('path').dirname('/test/something/file.txt') // /test/something
require('path').extname('/test/something/file.txt') // '.txt'

Модуль os

Платформа Node.js может взаимодействовать с операционной системой компьютера – это сфера ответственности модуля os.

Потоки

Потоки в Node.js – это эффективный способ обработки больших объемов данных "по частям" без сохранения в памяти. Основу для всех потоковых API обеспечивает модуль stream.

Потоки бывают нескольких видов:

  • на чтение (readable);
  • на запись (writable);
  • дуплексные (duplex);
  • трансформирующиеся (transform).

Потоками являются process.stdin, process.stdout и process.stderr. Метод http.request() тоже возвращает поток, в который можно записывать данные.

Метод pipe позволяет соединять несколько потоков.

Пример использования потока для чтения данных из файла:

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

// без потоков
const server = http.createServer(function (req, res) {
  fs.readFile(__dirname + '/data.txt', (err, data) => {
    res.end(data)
  })
})

// с потоками
const server = http.createServer((req, res) => {
  const stream = fs.createReadStream(__dirname + '/data.txt')
  stream.pipe(res)
})

server.listen(3000)

Вместо того, чтобы ждать, пока файл будет полностью прочитан, мы начинаем его потоковую передачу HTTP-клиенту.

Фреймворки и инструменты

На основе низкоуровневой Node.js построены тысячи библиотек, некоторые из которых стали очень популярными:

  • Express – мощный и простой фреймворк для веб-серверов с огромным количеством опций.
  • Meteor – fullstack-фреймворк, позволяющий использовать код и на клиенте, и на сервере.
  • Koa – более зрелый Express. Многолетний опыт позволил разработчикам сделать все проще и компактнее.
  • Next.js – платформа для серверного рендеринга приложений на React.
  • Micro – очень легковесный проект для асинхронных HTTP-микросервисов.
  • Socket.io – движок для создания приложений, способных взаимодействовать в реальном времени.

Перевод статьи The definitive Node.js handbook.

Вам может быть интересно

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ