Создание и настройка User-Service
Архитектура
Приложение разделено на три микросервиса:
- User Service – регистрация, авторизация и получение данных пользователей.
- Chat Service – обработка сообщений и хранение истории чатов.
- API Gateway – единая точка входа для маршрутизации запросов к нужным сервисам.
Сосредоточимся на нинициализации User Service. Он будет использовать:
- JWT (с NestJS Passport) для авторизации.
- Prisma для работы с PostgreSQL.
- DTO с валидацией.
- Swagger для генерации документации API.
Также мы создадим отдельный Dockerfile для микросервиса и подготовим docker‑compose файл для всего проекта, который будет поднимать PostgreSQL (образ: postgres:17-alpine
) с использованием переменных окружения из файла .env.
2. Установка зависимостей и подготовка проекта
Инструменты и зависимости
Убедитесь, что установлены:
- Node.js, npm, NestJS CLI
- Docker и docker‑compose
Создание проекта микросервиса
Создаём директорию для сервиса и инициализируем NestJS проект:
mkdir user-service && cd user-service
nest new .
При помощи пакетного менеджера npm установите дополнительные зависимости:
npm install @nestjs/passport passport passport-jwt jsonwebtoken
npm install prisma @prisma/client --save-dev
npm install class-validator class-transformer
npm install @nestjs/swagger swagger-ui-express
Настройка Prisma и базы данных PostgreSQL
Инициализируйте Prisma:
npx prisma init
В файле prisma/schema.prisma задайте следующую конфигурацию:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
name String?
createdAt DateTime @default(now())
}
Создайте миграцию и примените её (перед этим нужно будет поднять docker контейнер):
npx prisma migrate dev --name init
В файле .env (на уровне проекта) добавьте строку подключения (это значение будет переопределено переменными из docker‑compose, но на локальной разработке оно может быть таким):
DATABASE_URL="postgresql://dbuser:dbpassword@localhost:5432/chat?schema=public"
Создание DTO с валидацией и Swagger‑документацией
DTO для регистрации
Создайте файл src/auth/dto/register.dto.ts:
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, MinLength, Matches } from 'class-validator';
export class RegisterDto {
@ApiProperty({ example: 'user@example.com' })
@IsEmail()
email: string;
@ApiProperty({
example: 'StrongPass1',
description: 'Пароль должен состоять минимум из 8 символов и содержать хотя бы одну заглавную букву',
})
@IsNotEmpty()
@MinLength(8)
@Matches(/^(?=.*[A-Z]).{8,}$/, {
message:
'Пароль должен состоять минимум из 8 символов и содержать хотя бы одну заглавную букву',
})
password: string;
@ApiProperty({ example: 'John Doe', required: false })
name?: string;
}
DTO для логина
Создайте файл src/auth/dto/login.dto.ts:
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty } from 'class-validator';
export class LoginDto {
@ApiProperty({ example: 'user@example.com' })
@IsEmail()
email: string;
@ApiProperty({ example: 'StrongPass1' })
@IsNotEmpty()
password: string;
}
Создание сервиса и контроллера пользователей
Пользовательский сервис
Создайте модуль, контроллер и сервис для пользователей:
nest generate module users
nest generate controller users
nest generate service users
В src/users/users.service.ts реализуйте методы регистрации и валидации пользователя:
import { Injectable, ConflictException, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { RegisterDto } from '../auth/dto/register.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async register(dto: RegisterDto) {
const hash = await bcrypt.hash(dto.password, 10);
try {
const user = await this.prisma.user.create({
data: { email: dto.email, password: hash, name: dto.name },
});
return user;
} catch (error) {
throw new ConflictException('Пользователь с таким email уже существует');
}
}
async validateUser(email: string, password: string) {
const user = await this.prisma.user.findUnique({ where: { email } });
if (user && await bcrypt.compare(password, user.password)) {
const { password, ...result } = user;
return result;
}
throw new UnauthorizedException('Неверные учетные данные');
}
async findById(id: number) {
return this.prisma.user.findUnique({ where: { id } });
}
}
Prisma сервис
Создайте файл src/prisma/prisma.service.ts:
import { Injectable, OnModuleInit, INestApplication } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
}
Не забудьте создать и зарегистрировать модуль для Prisma (например, src/prisma/prisma.module.ts), который экспортирует PrismaService.
import { Module } from '@nestjs/common';
import { PrismaService } from './prisma/prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
Реализация JWT аутентификации с использованием Passport
Модуль аутентификации
Создайте модуль, сервис и контроллер для аутентификации:
nest generate module auth
nest generate service auth
nest generate controller auth
В src/auth/auth.service.ts реализуйте методы аутентификации и генерации JWT:
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { JwtService } from '@nestjs/jwt';
import { LoginDto } from './dto/login.dto';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(email: string, pass: string) {
return this.usersService.validateUser(email, pass);
}
async login(loginDto: LoginDto) {
const user = await this.validateUser(loginDto.email, loginDto.password);
const payload = { email: user.email, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
}
Создайте стратегию JWT в файле src/auth/jwt.strategy.ts:
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET || 'defaultSecretKey',
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}
В src/auth/auth.module.ts подключите необходимые модули:
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'defaultSecretKey',
signOptions: { expiresIn: '1d' },
}),
],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
Контроллер аутентификации
Создайте src/auth/auth.controller.ts:
import { Body, Controller, Post, UsePipes, ValidationPipe } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { RegisterDto } from './dto/register.dto';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(
private usersService: UsersService,
private authService: AuthService,
) {}
@Post('register')
@UsePipes(new ValidationPipe())
async register(@Body() dto: RegisterDto) {
return this.usersService.register(dto);
}
@Post('login')
@UsePipes(new ValidationPipe())
async login(@Body() dto: LoginDto) {
return this.authService.login(dto);
}
}
Интеграция Swagger для автоматической документации
В файле src/main.ts настройте Swagger:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
// Настройка Swagger
const config = new DocumentBuilder()
.setTitle('User Service API')
.setDescription('Документация API для управления пользователями')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-docs', app, document);
await app.listen(3001);
}
bootstrap();
После запуска приложения документация будет доступна по адресу http://localhost:3001/api-docs
.
Dockerfile для микросервиса
В корне проекта user-service создайте файл Dockerfile:
# Используем официальный Node.js образ
FROM node:20-alpine
# Создаем рабочую директорию
WORKDIR /usr/src/app
# Копируем package.json и package-lock.json
COPY package*.json ./
# Устанавливаем зависимости
RUN npm install
# Копируем исходный код приложения
COPY . .
# Генерируем Prisma клиент
RUN npx prisma generate
# Собираем проект
RUN npm run build
# Открываем порт
EXPOSE 3001
# Запускаем приложение
CMD ["node", "dist/main.js"]
Вариант с одновременным запуском всех сервисов:
- Создаем bash скрипт:
#!/bin/sh
set -e
echo "Waiting for database to be ready..."
until npx prisma migrate deploy > /dev/null 2>&1; do
echo "Database not ready yet. Retrying in 3 seconds..."
sleep 3
done
echo "Applying database migrations..."
npx prisma migrate deploy
echo "Starting application..."
exec node dist/src/main.js
- Меняем Dockerfile
FROM node:20-alpine as builder
RUN apk add --no-cache openssl \
&& apk add --no-cache curl
ENV NODE_ENV build
USER node
WORKDIR /home/node
COPY package*.json ./
RUN npm ci
COPY --chown=node:node . .
RUN npx prisma generate \
&& npm run build \
&& npm prune --omit=dev
# ---
FROM node:20-alpine as production
RUN apk add --no-cache openssl \
&& apk add --no-cache curl
ENV NODE_ENV production
USER node
WORKDIR /home/node
COPY --from=builder --chown=node:node /home/node/package*.json ./
COPY --from=builder --chown=node:node /home/node/node_modules/ ./node_modules/
COPY --from=builder --chown=node:node /home/node/dist/ ./dist/
COPY --from=builder --chown=node:node /home/node/prisma/ ./prisma/
COPY --from=builder --chown=node:node /home/node/entrypoint.sh ./entrypoint.sh
RUN chmod +x entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]
docker‑compose и .env файл
.env файл
В корневой директории общего проекта (рядом с docker‑compose файлом) создайте файл .env со следующим содержимым:
DATABASE_USERNAME=dbuser
DATABASE_PASSWORD=dbpassword
DATABASE_NAME=chat
JWT_SECRET=YourJWTSecretKey
docker‑compose файл
Создайте файл docker-compose.yml:
version: '3.8'
services:
postgres:
image: postgres:17-alpine
restart: always
environment:
POSTGRES_USER: ${DATABASE_USERNAME}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: ${DATABASE_NAME}
ports:
- '5432:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
user-service:
build: ./user-service
restart: always
ports:
- '3001:3001'
environment:
DATABASE_URL: "postgresql://${DATABASE_USERNAME}:${DATABASE_PASSWORD}@postgres:5432/${DATABASE_NAME}?schema=public"
JWT_SECRET: ${JWT_SECRET}
depends_on:
- postgres
# Дополнительные сервисы (chat-service, api-gateway) добавляем по аналогичной схеме
volumes:
postgres_data:
Теперь все параметры подключения к базе данных и секреты вынесены в файл .env, а docker‑compose использует их при запуске контейнеров.