Nest.js — самый важный и популярный фреймворк для создания серверных веб-приложений Node.js. В этом большом гайде мы поможем новичкам сделать первый шаг в освоении этого фреймворка для серверного Javascript и расскажем, в чем вообще особенности Node.js.
Оригинал статьи опубликован в блоге Хекслета.
Для комфортного усвоения этого гайда вам потребуется:
- Знание JavaScript на среднем уровне
- Знание основ TypeScript, особенно синтаксиса декораторов
Также нам понадобится уставленные на компьютер:
- Node.js версии 10+. Инструкция по установке
- Docker
Docker мы будем использовать для развёртывания базы данных. Развертывание СУБД в контейнере — один из самых простых способов. Но если вы хотите использовать другой способ, или же работать с уже имеющейся СУБД — нет проблем. В таком случае Docker вам не понадобится.
Прокачивайте свой уровень программирования:
На Хекслете есть несколько десятков треков — специальных курсов для опытных программистов, позволяющие повысить уровень компетентности разработчика в разных направлениях.
Введение
Зачем нам нужен фреймворк?
Большинство задач в программировании — типовые. Если не брать каких-то специфических бизнес-кейсов, скорее всего все возможные задачи уже решены другими программистами и не по одному разу. Не зря в среде программистов среде так часто можно услышать шутки о «велосипедостроении». Фреймворки помогают нам писать меньше шаблонного кода и сосредоточиться на том, какую полезную работу должна делать наша программа.
Первым супер-популярным веб-фреймворком для Node.js был express.js. Nest.js значительно расширяет его функциональность, добавляет декларативности, а также помогает разработчику строить приложение в соответствии с лучшими архитектурными практиками.
Для примера мы напишем небольшой блог, в котором можно просматривать и добавлять статьи. Конечно, это будет очень простое приложение, но его будет достаточно для первого знакомства.
Создадим проект
Чтобы каждый раз не приходилось настраивать проект с нуля, разработчики Nest.js вооружили нас консольной утилитой — @nestjs/cli. Установим её глобально:
npm i @nestjs/cli -g
Теперь в вашей директории с проектами выполним команду nest new, указав имя проекта.
nest new nestjs-getting-started
Когда cli закончит свою работу, мы можем перейти в директорию nestjs-getting-started и посмотреть, что получилось:
Все основные зависимости уже установлены, сборка TypeScript настроена. Кроме этого, создана папка src, которая и будет интересовать нас больше всего. Здесь уже есть несколько файлов — это демонстрационное приложение от создателей Nest.js.
Самое приятное, что приложение уже рабочее и его можно запустить. Делается это при помощи команды
npm run start:dev
Теперь, если ввести в адресную строку браузера http://localhost:3000, то мы увидим возвращённую сервером фразу Hello world!.
Запущенное командой npm run start:dev приложение будет перезагружаться каждый раз, когда изменяется исходный код проекта.
Остановить работающее приложение можно комбинацией клавиш Ctrl+C
Точка входа
Точкой входа в приложение на Nest.js, как и в любом другом MVC-подобном фреймворке, являются контроллеры. Пока в приложении имеется один: app.controller.ts:
Обратите внимание, что класс AppController и метод getHello() помечены декораторами @Controller() и @Get(). Nest.js очень широко использует декораторы, поэтому к ним лучше сразу привыкать. Они позволяют писать приложение в более декларативном ключе, указывая что мы хотим сделать, и оставляя детали реализации фреймворку.
Если читатель «плавает» в теме декораторов, рекомендуем к прочтению эту статью.
Декоратор @Get() говорит, что когда в приложение придёт HTTP-запрос методом GET на роут '/' (это значение по умолчанию, поэтому его можно не указывать), его следует направить в метод getHello().
Соответственно, значение, которое вернётся из метода, будет отправлено в теле ответа. Код ответа по умолчанию для GET-запросов — 200.
Метод getHello() и конструктор мы пока удалим. Взамен создадим метод index(), который будет возвращать статьи. Контроллер приобретёт такой вид:
Теперь, зайдя на http://localhost:3000 мы вместо Hello world увидим такой JSON:
Обратите внимание, что мы возвращаем не «голый» массив, а заворачиваем его в объект c ключом articles. Нам это пригодится чуть дальше.
Статьи
Пришло время наполнить блог статьями. Но что такое «статья»? Каждая статья — объект, который будет иметь уникальный идентификатор, заголовок и контент.
Создадим в папке src новый файл article.model.ts и в нём опишем класс статьи:
Почему id — необязательный параметр, выяснится позже — когда будем подключать базу данных.
Теперь представим, что у нас в блоге уже есть парочка статей. Создадим файл articles.ts:
Осталось вернуть массив статей из метода index() в контроллере:
Если теперь запустить приложение, мы увидим в браузере следующее:
Добавляем HTML-шаблоны
Для того, чтобы пользователю было комфортнее работать с сайтом, сервер должен вернуть браузеру ответ не в формате json, а в виде html-страницы.
Создадим в корне проекта директорию views — в ней мы будем размещать HTML-шаблоны.
Устанавливаем шаблонизатор
Nest.js по умолчанию не устанавливает никаких движков шаблонизации, так как не знает, какой именно по душе пользователю. Мы для этого примера возьмём pug (ранее известный как jade). Этот пакет нам придётся установить самостоятельно:
npm install pug
Чтобы подключить pug, перепишем функцию bootstrap (main.ts):
Первый шаблон
Наш первый шаблон index.pug будет выглядеть так:
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title SuperBlog!
body
h1 Welcome to SuperBlog!
// Конструкция "each" позволяет перебрать массив и для каждого элемента сгенерировать разметку
ul
each a in articles
// #{...} — интерполяция. Так шаблонизатор понимает, что сюда надо встроить данные.
li #{a.title} <a href="...">Читать</a>
Добавление стилей выходит за рамки этой статьи, поэтому оставим это на усмотрение читателя.
В контроллере добавим к методу index декоратор @Render() (импортируется из пакета @nestjs/common):
@Render('index') указывает фреймворку, что те данные, которые возвращаются из метода нужно не просто вернуть браузеру, а использовать для отрисовки шаблона с названием index (расширение не важно).
Готово. Теперь при запуске приложения мы увидим то, что и ожидали — html-страницу с двумя статьями.
Читайте также:
Что такое webpack externals и как их настроить.
Добавляем просмотр статей
В app.controller.ts создадим новый метод:
Строка :id в декораторе @Get() означает, что в этот метод будут направлены запросы на корневой роут с параметром, например: GET http://localhost/1.
При помощи декоратора @Param() мы можем достать этот идентификатор из URL, преобразовать его к числу (ParseIntPipe) и использовать для поиска нужной статьи.
Кроме декоратора @Param() в Nest.js есть также декораторы @Query() для query-параметров и @Body() для тела запроса.
Добавим в папку views шаблон article.pug:
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title #{title}
style
body
h1 #{title}
p #{content}
a(href="/") Назад
Шапка будет та же самая, только в тэге title мы выведем название статьи. В теле страницы — ожидаемо title и content, а также ссылка «назад» — на главную страницу.
Осталось починить ссылки на главной странице:
li #{post.title}
a(href=post.id) Читать
Теперь, зайдя на главную страницу, можно кликнуть по ссылке «Читать» у любой из статей и увидеть содержимое статьи. А при клике на «Назад» — вернуться к списку статей. Также можно зайти на страницу по прямой ссылке, например http://localhost:3000/1.
Добавляем статью
Чтобы создать новую статью, браузер должен отправить на сервер title и content POST-запросом. Чтобы реализовать это, добавим в контроллер два новых метода:
Метод getForm() не требует никакого возвращаемого значения — он просто возвращает статический HTML.
В методе create() реализуем добавление статьи в коллекцию. Думаю, читатель уже догадался, что декоратор @Post() означает одноимённый HTTP-глагол. Декоратор @Body() указывает фреймворку, что данные для параметра нужно брать из тела запроса. И, наконец, декоратор @Redirect(‘/’, 301) говорит, что после добавления статьи требуется переадресовать пользователя. Первый аргумент - ‘/’ обозначает корневой роут (то есть, главную страницу), а 301 — код ответа.
Шаблон create-article.pug будет выглядеть так:
doctype html
html
head
title Новая статья
style
body
h1 Новая статья
form(method="POST" action="/")
input(type="text" name="title")
input(type="text" name="content")
button(type="submit") Создать
Добавление статей реализовано. На страницу добавления можно попасть, введя в адресной строке http://localhost:3000/create. Чтобы было чуть удобнее, добавим ссылку на эту страничку вниз списка статей. index.pug приобретёт такой вид:
doctype html
html
head
title SuperBlog!
style
body
h1 Welcome to SuperBlog!
ul
each post in posts
li #{post.title}
a(href='articles/' + post.id) Читать
a(href="create") Создать
Подключаем базу данных
Пока наш блог хранит статьи в оперативной памяти, они будут теряться при каждом перезапуске приложения. Чтобы избавиться от этой проблемы, нам нужно организовать долговременное хранение данных.
Мы используем для этого PostgreSQL — самую популярную на сегодня свободную СУБД. Проще всего поднять базу данных локально при помощи Docker:
Когда Docker закончит свою работу и контейнер с базой данных запустится, можно заняться подключением. Для работы с базой мы будем использовать ORM (Object-Relational Mapping System) под названием Typeorm. Эта система также как, и Nest.js, основана на декораторах, и хорошо интегрируется с фреймворком.
Установим необходимые пакеты:
npm i typeorm pg @nestjs/typeorm
Теперь мы можем подключить базу данных к проекту.
Особое внимание следует здесь уделить параметру synchronize: true. Эта настройка означает, что при каждом запуске приложения схема БД будет приобретать ту форму, которую мы описываем в коде (классы, помеченные @Entity). На этапе активной разработки приложения это очень удобная возможность.
Функционально мы пока ничего в приложении не поменяли, но при старте, если подключение БД прошло успешно, мы увидим в консоли несколько SQL-запросов.
Переносим статьи в базу данных
Перепишем файл article.model.ts:
Здесь мы видим несколько новых декораторов:
- @Entity() — означает, что в базе данных нашей модели соответствует таблица.
- @Column() — полю класса соответствует поле в таблице
- @PrimaryGeneratedColumn() — автоматически генерируемый идентификатор, которой будет в нашей таблице первичным ключом.
Поскольку у нас включена синхронизация, в БД уже создалась таблица articles. Вы можете убедиться в этом при помощи любой утилиты для работы с БД, например DBeaver или DataGrip.
Последний штрих: удалим файл articles.ts и перепишем app.controller.ts так, чтобы он использовал базу данных:
Теперь наши статьи будут сохраняться в базе данных и никуда не пропадут, даже если мы перезапустим сервер.
Заключение
Nest.js позволяет нам написать простое приложение с минимальными усилиями. Однако истинная мощь фреймворка станет очевидна при более глубоком погружении. Среди особенно мощных возможностей, выделяющих его на фоне других:
- Декларативное программирование при помощи декораторов
- Поддержка любых протоколов, помимо HTTP. На Nest.js можно писать сервисы на основе RabbitMQ, Nats, Kafka или даже просто TCP-протокола.
Чтобы освоить всё это, а также более продвинутые возможности Nest.js, читателю предстоит пройти немалый путь, и автор надеется, что эта статья станет уверенным первым шагом на нем.
Весь код учебного проекта, который мы использовали в этой статье в качестве примера, вы можете посмотреть здесь.
Никогда не останавливайтесь:
В программировании говорят, что нужно постоянно учиться даже для того, чтобы просто находиться на месте. Развивайтесь с нами — на Хекслете есть сотни курсов по разработке на разных языках и технологиях.