Fullstack разработка на Next.js и Nest.js: от идеи до деплоя

Практический курс по созданию масштабируемых веб-приложений с использованием React, TypeScript и SSR. Вы научитесь эффективно связывать архитектуру Nest.js с возможностями рендеринга Next.js для создания полноценного продукта.

1. Введение в экосистему: настройка окружения, TypeScript и архитектура монорепозитория

Введение в экосистему: настройка окружения, TypeScript и архитектура монорепозитория

Добро пожаловать в курс Fullstack разработка на Next.js и Nest.js. В этой первой статье мы заложим фундамент для всего будущего приложения. Мы не просто напишем «Hello World», а создадим профессиональную архитектуру, которую используют в крупных технологических компаниях.

Наша цель — создать приложение, где фронтенд (Next.js) и бэкенд (Nest.js) говорят на одном языке, буквально и фигурально. Этим языком будет TypeScript.

Почему именно этот стек?

Выбор технологий в 2024 году огромен, но связка Next.js и Nest.js выделяется своей мощью и типизацией.

Next.js: Фронтенд без компромиссов

Next.js — это фреймворк поверх React, который решает главные проблемы чистого React: SEO (оптимизация для поисковиков) и производительность первой загрузки. Он делает это с помощью SSR (Server-Side Rendering — рендеринг на стороне сервера).

> Next.js предоставляет вам лучший опыт разработчика со всеми функциями, необходимыми для производства: гибридный статический и серверный рендеринг, поддержка TypeScript, интеллектуальное связывание, предварительная выборка маршрутов и многое другое. Next.js Documentation

Nest.js: Порядок на бэкенде

Если вы приходите из мира Express.js, вы знаете, что там можно писать код как угодно. Это часто приводит к хаосу. Nest.js навязывает строгую модульную архитектуру, вдохновленную Angular. Он использует декораторы, внедрение зависимостей (Dependency Injection) и отлично работает с TypeScript.

!Схема взаимодействия клиента и сервера через общие типы данных в монорепозитории

Архитектура монорепозитория

Традиционно фронтенд и бэкенд хранятся в разных репозиториях. Это создает проблему синхронизации: вы обновили API на бэкенде, но забыли обновить типы на фронтенде. Приложение падает.

Мы будем использовать монорепозиторий. Это стратегия, при которой код нескольких проектов (в нашем случае client и server) хранится в одном репозитории.

Преимущества монорепозитория:

  • Общие типы: Мы создадим папку packages/shared, где опишем интерфейсы данных. И фронтенд, и бэкенд будут использовать одни и те же файлы. Если бэкенд изменит формат ответа, фронтенд сразу «подсветит» ошибку на этапе компиляции.
  • Единый стиль кода: Один конфиг ESLint и Prettier на весь проект.
  • Атомарные коммиты: Вы можете одной командой закоммитить изменения и в API, и в интерфейсе, который это API потребляет.
  • Настройка окружения

    Для начала работы нам понадобятся правильные инструменты. Мы будем использовать pnpm вместо npm или yarn. Pnpm (Performant NPM) работает быстрее и экономит место на диске за счет умного использования симлинков.

    Шаг 1: Установка Node.js

    Убедитесь, что у вас установлена Node.js версии LTS (Long Term Support). На момент написания курса это версия 20+.

    Проверьте версию в терминале:

    Если Node.js не установлен, скачайте его с официального сайта Node.js.

    Шаг 2: Установка pnpm

    Если у вас еще нет pnpm, установите его глобально:

    Создание структуры проекта

    Мы создадим структуру вручную, чтобы понимать каждый винтик системы. Мы будем использовать встроенные возможности pnpm workspaces.

    Создайте папку проекта и перейдите в неё:

    Инициализируйте проект:

    Теперь создадим файл конфигурации рабочего пространства pnpm-workspace.yaml в корне проекта. Этот файл говорит pnpm, где искать наши приложения и пакеты.

    Создайте файл pnpm-workspace.yaml и добавьте туда следующий код:

    Теперь создадим необходимые папки:

    * apps/ — здесь будут лежать наши запускаемые приложения (Next.js и Nest.js). * packages/ — здесь будут лежать вспомогательные библиотеки (общие типы, конфиги).

    Шаг 3: Установка Nest.js (Server)

    Перейдем в папку apps и создадим серверное приложение. Для этого нам понадобится Nest CLI. Я рекомендую использовать npx для запуска последней версии CLI без глобальной установки.

    При вопросе о пакетном менеджере выберите pnpm.

    Теперь у нас есть папка apps/server с готовым бэкендом.

    Шаг 4: Установка Next.js (Client)

    Находясь всё ещё в папке apps, создадим клиентское приложение:

    Вас спросят о настройках. Рекомендую выбирать следующие варианты для этого курса: * TypeScript: Yes * ESLint: Yes * Tailwind CSS: Yes (мы будем его использовать позже) * src/ directory: Yes * App Router: Yes (это современный стандарт Next.js) Customize import alias: No (или @/ по умолчанию)

    Теперь у нас есть следующая структура:

    Настройка TypeScript и общих типов

    Самая важная часть монорепозитория — возможность переиспользовать код. Давайте создадим пакет с общими типами.

  • Создайте папку packages/shared.
  • Инициализируйте её:
  • Измените имя пакета в package.json (внутри packages/shared) на что-то уникальное для вашего проекта, например @course/shared.
  • Создайте файл index.ts:
  • Теперь нам нужно, чтобы и client, и server видели этот пакет. Благодаря pnpm workspaces, нам не нужно публиковать этот пакет в npm. Мы просто добавим его как зависимость.

    Зайдите в apps/client/package.json и добавьте в dependencies:

    Сделайте то же самое для apps/server/package.json.

    После этого выполните команду в корне проекта:

    pnpm создаст символические ссылки, и теперь вы можете импортировать IUser в обоих проектах так:

    Важность tsconfig.json

    TypeScript требует настройки. В корне каждого приложения (apps/client и apps/server) есть файл tsconfig.json. Убедитесь, что в обоих файлах включен строгий режим:

    Strict mode (строгий режим) запрещает неявный тип any и заставляет вас писать более надежный код. Это может показаться сложным поначалу, но это спасает от тысяч багов в будущем.

    Запуск проектов

    Чтобы разрабатывать комфортно, нам нужно запускать оба проекта параллельно. Мы можем открывать два терминала, но это неудобно. Давайте добавим скрипт в корневой package.json.

    Установим утилиту concurrently в корень (как dev-зависимость):

    Добавьте скрипт в корневой package.json:

    Разберем эту команду: * concurrently — запускает команды параллельно. * pnpm --filter server start:dev — запускает команду start:dev только внутри папки server. * pnpm --filter client dev — запускает команду dev только внутри папки client.

    Теперь, набрав в корне pnpm dev, вы запустите всё приложение целиком!

    Заключение

    Мы проделали большую работу. Мы не написали бизнес-логику, но мы создали профессиональный стапель, на котором будем строить наш корабль. У нас есть:

  • Монорепозиторий для удобного управления кодом.
  • Next.js для быстрого фронтенда.
  • Nest.js для надежного бэкенда.
  • Общие типы, связывающие всё воедино.
  • В следующей статье мы начнем создавать базу данных и подключим её к нашему Nest.js приложению.

    2. Разработка Backend на Nest.js: модули, контроллеры, работа с базой данных и REST API

    Разработка Backend на Nest.js: модули, контроллеры, работа с базой данных и REST API

    В предыдущей статье мы настроили мощный фундамент: монорепозиторий на базе pnpm workspaces, сконфигурировали TypeScript и создали пакет общих типов. Теперь пришло время вдохнуть жизнь в наш сервер.

    В этой статье мы превратим пустое приложение Nest.js в полноценный REST API, подключим базу данных через Prisma ORM и научимся правильно структурировать код, используя модульную архитектуру.

    Архитектура Nest.js: Кубики LEGO

    Nest.js часто называют «Angular для бэкенда». Если вы не знакомы с Angular, не страшно. Главная идея заключается в строгой структуре. Приложение строится из трех основных блоков:

  • Modules (Модули): Это контейнеры, группирующие связанную функциональность. Корневой модуль (AppModule) собирает всё приложение воедино.
  • Controllers (Контроллеры): Отвечают за прием входящих запросов и отправку ответов клиенту. Это «лицо» вашего API.
  • Providers/Services (Сервисы): Здесь живет бизнес-логика. Контроллер не должен знать, как сохранить пользователя в базу, он просто просит Сервис сделать это.
  • !Поток данных в Nest.js: от запроса клиента через контроллер и сервис к базе данных

    Такое разделение ответственности делает код чистым, тестируемым и легко поддерживаемым.

    Подключение базы данных с Prisma ORM

    Для работы с базой данных мы будем использовать Prisma. Это современная ORM (Object-Relational Mapping), которая позволяет писать схемы данных на понятном языке и автоматически генерирует TypeScript-типы.

    Установка Prisma

    Перейдите в папку сервера:

    Установите зависимости:

    Инициализируйте Prisma:

    Эта команда создаст папку prisma с файлом schema.prisma и файл .env для переменных окружения.

    Настройка схемы данных

    Откройте файл apps/server/prisma/schema.prisma. Нам нужно описать модель пользователя. Вспомните, что в прошлой статье мы создали интерфейс IUser в пакете @course/shared. Наша модель базы данных должна соответствовать этому интерфейсу.

    Мы будем использовать SQLite для простоты разработки, но Prisma позволяет легко переключиться на PostgreSQL в будущем.

    В файле .env внутри папки server убедитесь, что переменная DATABASE_URL указывает на локальный файл:

    Теперь применим миграцию, чтобы создать файл базы данных:

    Создание Prisma Service

    Чтобы использовать Prisma внутри Nest.js, нам нужно создать сервис, который будет отвечать за соединение. Nest.js рекомендует создать отдельный модуль или сервис для этого.

    Создадим файл apps/server/src/prisma.service.ts:

    Декоратор @Injectable() сообщает Nest.js, что этот класс можно внедрять в другие классы (Dependency Injection).

    Создание ресурса Users

    Nest CLI умеет генерировать готовые каркасы (scaffolding). Давайте создадим всё необходимое для работы с пользователями одной командой.

    Находясь в папке apps/server, выполните:

    Вас спросят, какой транспорт использовать. Выберите REST API. На вопрос о генерации CRUD-точек ответьте Yes.

    Эта команда создаст папку src/users с контроллером, сервисом, модулем и DTO.

    Реализация UsersService

    Откройте src/users/users.service.ts. Здесь мы напишем логику обращения к базе данных через наш PrismaService.

    Сначала внедрим PrismaService в конструктор:

    > Важно: Не забудьте добавить PrismaService в массив providers в файле src/users/users.module.ts, иначе Nest.js не поймет, откуда брать этот сервис.

    Работа с DTO и Валидация

    DTO (Data Transfer Object) — это объект, который определяет, как данные передаются по сети. В папке src/users/dto/create-user.dto.ts мы опишем, какие данные нужны для создания пользователя.

    Здесь мы используем библиотеку class-validator для проверки данных.

    Установим необходимые пакеты:

    Теперь настроим DTO:

    Чтобы валидация заработала глобально, нужно добавить ValidationPipe в main.ts:

    Реализация UsersController

    Контроллер принимает запросы и делегирует работу сервису. Откройте src/users/users.controller.ts.

    Обратите внимание на декораторы: * @Controller('users'): все маршруты будут начинаться с /users. * @Post(): обрабатывает POST-запросы. * @Body(): извлекает тело запроса и валидирует его через CreateUserDto. * @Param('id', ParseIntPipe): извлекает параметр id из URL и автоматически преобразует его из строки в число.

    Интеграция с общими типами

    В первой статье мы создали пакет @course/shared с интерфейсом IUser. Хорошей практикой является указание возвращаемых типов в контроллере, используя этот интерфейс. Это гарантирует, что наш API отдает именно то, что ожидает фронтенд.

    Теперь, если вы измените модель в базе данных, но забудете обновить DTO или интерфейс, TypeScript выдаст ошибку компиляции.

    Проверка работы API

    Запустите сервер (если он не запущен):

    Теперь вы можете использовать Postman, Insomnia или обычный curl для проверки.

    Создание пользователя:

    Получение списка:

    Если вы попробуете отправить некорректный email или пустое имя, сервер вернет ошибку 400 Bad Request с описанием проблемы. Это работает наша валидация.

    Заключение

    Мы создали надежный бэкенд на Nest.js. Мы:

  • Разобрали архитектуру Модуль-Контроллер-Сервис.
  • Настроили базу данных SQLite через Prisma.
  • Реализовали REST API для работы с пользователями.
  • Добавили валидацию входящих данных.
  • Теперь наш бэкенд готов принимать запросы. В следующей статье мы перейдем к фронтенду на Next.js и научимся получать эти данные, рендерить их на сервере и отображать пользователю.