Туториал по разработке клиент-серверного приложения с помощью Next.js
Привет, друзья!
В этом туториале мы с вами разработаем клиент-серверное (фуллстек - fullstack) приложение с помощью Next.js и TypeScript.
- Наше приложение будет представлять собой блог - относительно полноценную платформу для публикации, редактирования и удаления постов.
- Мы реализуем собственный сервис аутентификации на основе JSON Web Tokens и HTTP-куки.
- Данные пользователей и постов будут храниться в реляционной базе данных SQLite.
Сначала мы подготовим и настроим проект, а также реализуем серверную часть приложения с помощью интерфейса роутов (API Routes), затем - разработаем клиента и проверим работоспособность приложения.
Обратите внимание: данный туториал рассчитан на разработчиков, которые имеют некоторый опыт работы с React и Node.js.
Сервер
Подготовка и настройка проекта
Создание проекта и установка зависимостей
Для работы с зависимостями будет использоваться Yarn.
Создаем новый Next.js-проект с поддержкой TS с помощью Create Next App:
yarn create next-app fullstack-next-ts-app --ts
Советую взглянуть на Tabby - продвинутый терминал с кучей интересных возможностей
Обратите внимание, что мы выбрали ESLint и директорию src
для хранения файлов приложения, но отказались от экспериментальной директории app
.
Устанавливаем минимальный набор npm-пакетов, необходимых для работы нашего приложения:
# производственные зависимости
yarn add @emotion/cache @emotion/react @emotion/server @emotion/styled @formkit/auto-animate @mui/icons-material @mui/joy @mui/material @prisma/client @welldone-software/why-did-you-render argon2 cookie jsonwebtoken multer next-connect react-error-boundary react-toastify swiper swr
# зависимости для разработки
yarn add -D @types/cookie @types/jsonwebtoken @types/multer babel-plugin-import prisma sass
@mui/...
- компоненты и иконки Material UI;@emotion/...
- решение CSS-в-JS, которое используется для стилизации компонентов Material UI;- prisma - ORM для работы с реляционными БД PostgreSQL, MySQL, SQLite и SQL Server, а также с NoSQL-БД MongoDB и CockroachDB;
- @prisma/client - клиент Prisma;
- @welldone-software/why-did-you-render - полезная утилита для отладки React-приложений, позволяющая определить причину повторного рендеринга компонента;
- argon2 - утилита для хэширования и проверки паролей;
- cookie - утилита для работы с куки;
- jsonwebtoken - утилита для работы с токенами;
- multer - посредник (middleware) Node.js для обработки
multipart/form-data
(для работы с файлами, содержащимися в запросе); - next-connect - библиотека, позволяющая работать с интерфейсом роутов Next.js как с роутами Express;
- react-error-boundary - компонент-предохранитель для React-приложений;
- react-toastify - компонент и утилита для реализации уведомлений в React-приложениях;
- swiper - продвинутый компонент слайдера;
- swr - хуки React для запроса (получения - fetching) данных от сервера, позволяющие обойтись без инструмента для управления состоянием (state manager);
@types/...
- недостающие типы TS;- babel-plugin-import - плагин Babel для эффективной "тряски дерева" (tree shaking) при импорте компонентов MUI по названию;
- sass - препроцессор CSS.
Мы рассмотрим большую часть этих пакетов далее и в следующей части туториала.
Подготовка БД и настройка ORM
Для хранения данных пользователей и постов нам нужна БД. Для простоты будем использовать SQLite - в этой БД данные хранятся в виде файла на сервере. Для работы с SQLite будет использоваться Prisma.
Советую установить это расширение для VSCode для работы со схемой Prisma
Инициализируем Prisma, находясь в корневой директории проекта:
npx prisma init
Выполнение этой команды приводит к генерации директории prisma
и файла .env
. Редактируем файл schema.prisma
в директории prisma
, определяя провайдер для БД в блоке datasource
и модели пользователя, поста и лайка:
generator client {
provider = "prisma-client-js"
}
datasource db {
// !
provider = "sqlite"
url = env("DATABASE_URL")
}
// модель пользователя
model User {
id String @id @default(uuid())
username String?
avatarUrl String?
email String @unique
password String
posts Post[]
likes Like[]
}
// модель поста
model Post {
id String @id @default(uuid())
title String
content String
author User @relation(fields: [authorId], references: [id], onUpdate: Cascade, onDelete: Cascade)
authorId String
likes Like[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// уникальное сочетание полей, используемое для обновления и удаления записи
@@unique([id, authorId])
}
// модель лайка
model Like {
id String @id @default(uuid())
user User @relation(fields: [userId], references: [id])
userId String
post Post @relation(fields: [postId], references: [id], onUpdate: Cascade, onDelete: Cascade)
postId String
@@unique([id, userId, postId])
}
Редактируем файл .env
, определяя в нем путь к файлу БД:
DATABASE_URL="file:./dev.db"
Создаем и применяем миграцию к БД:
npx prisma migrate dev --name init
Выполнение этой команды приводит к генерации директории migrations
с миграцией на SQL.
Обратите внимание: при первом выполнении migrate dev
автоматически устанавливается и генерируется клиент Prisma. В дальнейшем при любом изменении схемы Prisma необходимо вручную выполнять команду npx prisma generate
для обновления клиента.
Также обратите внимание, что для быстрого восстановления исходного состояния БД с потерей всех данных можно удалить файл dev.db
и выполнить команду npx prisma db push
.
Осталось настроить клиента Prisma. Создаем файл src/utils/prisma.ts
следующего содержания:
// https://github.com/prisma/prisma-examples/blob/latest/typescript/rest-nextjs-api-routes-auth/lib/prisma.ts
import { PrismaClient } from '@prisma/client'
declare let global: { prisma: PrismaClient }
let prisma: PrismaClient
if (process.env.NODE_ENV === 'production') {
prisma = new PrismaClient()
} else {
if (!global.prisma) {
global.prisma = new PrismaClient()
}
prisma = global.prisma
}
export default prisma
Этот сниппет обеспечивает существование только одного экземпляра (синглтона - singleton) клиента Prisma при работе как в производственной среде, так и в среде для разработки. Дело в том, что в режиме разработки из-за HMR при перезагрузке модуля, импортирующего prisma
, будет создаваться новый экземпляр клиента.