Построение CRUD REST API на Nest.js с TypeORM и PostgreSQL

Зачем использовать Nest.js для CRUD API
Nest.js — это прогрессивный фреймворк для Node.js, который строится поверх Express (или Fastify) и приносит модульность, инверсию зависимостей и структурированную архитектуру. Для CRUD API Nest.js помогает:
- Снизить беспорядок в коде благодаря модулям и инъекции зависимостей.
- Быстро интегрировать ORM (TypeORM, Sequelize, Prisma) для работы с базой данных.
- Упрощать масштабирование через разделение на модули и контроллеры.
Краткое определение: CRUD — набор операций Create, Read, Update, Delete, необходимых для работы с любыми ресурсами в API.
Основные варианты задач, решаемых этой статьёй
primary intent: создать CRUD REST API на Nest.js related variants: Nest.js CRUD, TypeORM PostgreSQL, Nest.js + TypeORM, Nest CRUD пример, бекенд на Node.js с PostgreSQL
Требования и оговорки
Важно: в примерах используется PostgreSQL. Для локальной разработки можно использовать Docker или облачный экземпляр (например, ElephantSQL). По умолчанию приложение запускается на порту 3000, а PostgreSQL слушает порт 5432.
Примечание: в примерах для простоты включена опция synchronize: true в TypeORM — это удобно для разработки, но не рекомендуется в production, так как может привести к неявным изменениям схемы.
Шаг 1. Установка Nest CLI и создание проекта
Установите Nest CLI глобально и создайте проект:
npm i -g @nestjs/cli
nest new crud-app
cd crud-app
npm run startCLI предложит выбрать пакетный менеджер. В примерах используется npm.
Важно: при первом запуске dev-сервер запустится на http://localhost:3000.
Шаг 2. Создание PostgreSQL базы данных
Для облачного решения можно использовать ElephantSQL:
- Перейдите на сайт ElephantSQL и авторизуйтесь.
- Нажмите Create New Instance в верхней части страницы.
- Заполните имя инстанса, выберите бесплатный план и регион. После создания скопируйте DATABASE URL в разделе настроек.
Совет: для локальной разработки проще поднять PostgreSQL через Docker:
docker run --name pg-dev -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -p 5432:5432 -d postgres:14Шаг 3. Конфигурация подключения к БД
Создайте в корне проекта файл .env и вставьте туда строку подключения:
DATABASE_URL="" Установите зависимости:
npm install pg typeorm @nestjs/typeorm @nestjs/configСоздайте модуль базы данных:
nest g module databaseОткройте файл database/database.module.ts и добавьте конфигурацию TypeORM (пример):
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../users/models/user.entity';
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
type: 'postgres',
url: configService.get('DATABASE_URL'),
entities: [User],
synchronize: true
}),
}),
],
})
export class DatabaseModule {}Пояснение: TypeOrmModule.forRootAsync позволяет считывать переменные окружения через ConfigService.
Обновите app.module.ts, чтобы подключить ConfigModule и DatabaseModule:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DatabaseModule } from './database/database.module';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.env',
}),
DatabaseModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}Важно: не коммитите .env в репозиторий. Для production используйте безопасное хранилище секретов.
Шаг 4. Определение сущности User
Создайте файл users/models/user.entity.ts с определением сущности:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
}Пояснение: декораторы TypeORM описывают схему таблицы — удобный подход для миграций и валидации типов на уровне TypeScript.
Шаг 5. Реализация сервиса CRUD
Создайте сервис пользователей:
nest g service usersПример users.service.ts:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './models/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private userRepository: Repository,
) {}
async findAll(): Promise {
return this.userRepository.find();
}
async findOne(id: number): Promise {
return this.userRepository.findOne({ where: { id } });
}
async create(user: Partial): Promise {
const newuser = this.userRepository.create(user);
return this.userRepository.save(newuser);
}
async update(id: number, user: Partial): Promise {
await this.userRepository.update(id, user);
return this.userRepository.findOne({ where: { id } });
}
async delete(id: number): Promise {
await this.userRepository.delete(id);
}
} Примечание: здесь используется Partial
Шаг 6. Определение контроллера API
Создайте контроллер командой:
nest g controller usersПример users.controller.ts:
import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException, HttpCode } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './models/user.entity';
@Controller('api/users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
async findAll(): Promise {
return this.usersService.findAll();
}
@Post()
@HttpCode(201)
async create(@Body() user: User): Promise {
const createdUser = await this.usersService.create(user);
return createdUser;
}
@Put(':id')
async update(@Param('id') id: number, @Body() user: User): Promise {
await this.usersService.update(id, user);
return { message: 'User updated successfully' };
}
@Delete(':id')
async delete(@Param('id') id: number): Promise {
const user = await this.usersService.findOne(id);
if (!user) {
throw new NotFoundException('User does not exist!');
}
await this.usersService.delete(id);
return { message: 'User deleted successfully' };
}
} Пояснение: контроллер обрабатывает HTTP-запросы и делегирует логику сервису. Для production добавьте валидацию DTO и фильтры ошибок.
Шаг 7. Подключение модуля пользователей
Обновите users.module.ts:
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './models/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService]
})
export class UsersModule {}Запустите сервер:
npm run startAPI будет доступно по адресу: http://localhost:3000/api/users
Проверка API вручную
Используйте Postman, curl или HTTPie для проверки конечных точек:
- GET /api/users — получить список пользователей
- POST /api/users — создать пользователя (тело JSON: name, email)
- PUT /api/users/:id — обновить пользователя
- DELETE /api/users/:id — удалить пользователя
Пример curl для создания:
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d '{"name":"Ivan","email":"ivan@example.com"}'Лучшие практики и рекомендации
- DTO и class-validator: всегда валидируйте входные данные с помощью DTO и декораторов @IsString, @IsEmail и т.п.
- Error handling: используйте глобальные фильтры исключений и централизованную обработку ошибок.
- Автоматические миграции: в production отключите synchronize и применяйте миграции через TypeORM CLI или другой инструмент.
- Логирование: интегрируйте winston или pino для структурированного логирования.
Важно: synchronize: true удобен для разработки, но потенциально опасен в production.
Альтернативные подходы
- Prisma вместо TypeORM: Prisma даёт более строгую типизацию и генерацию клиентского API, но требует отдельной модели схемы.
- Sequelize: опытные команды, использующие Sequelize, могут предпочесть его за гибкость в конфигурировании.
- Fastify вместо Express: для повышения производительности можно переключить адаптер Nest.js на Fastify.
Краткий выбор: если вам важна строгая типизация и скорость разработки, рассмотрите Prisma; если нужен привычный ORM с декораторами — TypeORM подойдёт.
Когда шаблон CRUD в Nest.js не подходит
- Сложные транзакционные рабочие процессы: для тяжёлых распределённых транзакций возможно потребуется CQRS/Events.
- Высокая нагрузка с низкой задержкой: используйте Fastify и кэширование, а также горизонтальное масштабирование.
- Неподдерживаемые типы данных в ORM: иногда нужно писать сырой SQL или использовать специализированный инструмент.
Модель мышления и эвристики при проектировании CRUD API
- Разделяй и властвуй: отдельные модули для доменных областей. Модуль — это «граница ответственности».
- Слой сервиса — это транзакции и бизнес-логика; контроллер — только HTTP-адаптер.
- Минимизируйте объём данных в эндпоинтах: возвращайте DTO, не сущности напрямую.
Проверочный список для ролей
Для разработчика:
- Создана сущность и DTO
- Сервис покрывает CRUD-методы
- Контроллер валидирует данные
- Написаны unit-тесты для сервиса
Для DevOps:
- Переменные окружения защищены
- Используются миграции, synchronize выключен
- Настроены резервные копии БД
Для security-инженера:
- Включена валидация входа
- Включена авторизация и аудит доступа
- Ограничены возможности логирования персональных данных
Безопасность и конфиденциальность
- Всегда валидируйте поля email и любые пользовательские вводы.
- Храните секреты (DATABASE_URL) вне кода, используйте секретные менеджеры.
- Маскируйте или не логируйте персональные данные (PII) в продакшн-логах.
- Рассмотрите защиту API через JWT, OAuth2 или API-ключи в зависимости от сценария.
Заметка по GDPR: если вы храните персональные данные EU-граждан, реализуйте средства для экспортирования и удаления данных по запросу.
Производственные советы и миграции
- Отключите synchronize и применяйте миграции:
npm run typeorm migration:generate -- -n Init
npm run typeorm migration:run- Настройте connection pool: укажите в конфиге параметры максимального числа соединений.
- Резервное копирование: планируйте регулярные бэкапы базы и тесты восстановления.
Факто-бокс
- Порт по умолчанию Nest.js dev-сервера: 3000
- Порт по умолчанию PostgreSQL: 5432
- Типичные зависимости: @nestjs/typeorm, typeorm, pg, @nestjs/config
Тесты и критерии приёмки
Критерии приёмки:
- Все CRUD-эндпоинты возвращают корректные коды статуса (200/201/404/204).
- Входные данные валидируются, некорректные запросы возвращают 400.
- Создание и обновление корректно сохраняют данные в БД.
Минимальные тесты:
- Unit: сервис create/find/update/delete с мок-репозиторием.
- E2E: один сценарий создания -> получение -> обновление -> удаление.
Отладка и распространённые ошибки
- Ошибка подключения: проверьте DATABASE_URL и доступность порта 5432.
- Entity не найдена: убедитесь, что сущность импортирована в TypeOrmModule.forFeature и перечислена в forRoot.entities.
- Некорректные типы параметров в @Param: используйте ParseIntPipe для автоматического преобразования в number.
Пример использования ParseIntPipe в контроллере:
import { ParseIntPipe } from '@nestjs/common';
@Put(':id')
async update(@Param('id', ParseIntPipe) id: number, @Body() user: User) { ... }Переезд в продакшн и масштабирование
- Горизонтальное масштабирование: запустите несколько экземпляров приложения за балансировщиком.
- Кэширование: используйте Redis для кеширования списков или часто запрашиваемых данных.
- Мониторинг: подключите Prometheus/Grafana для метрик приложений и БД.
Примеры миграции на Prisma (альтернативный путь)
Коротко: Prisma требует иного подхода — вы описываете схему в prisma/schema.prisma и используете prisma client для запросов. Prisma имеет генерацию типов и удобный миграционный механизм.
Короткая методология внедрения (по шагам)
- Инициализация проекта и настройка env.
- Подключение TypeORM/Prisma и создание сущностей.
- Создание сервисов и контроллеров с DTO и валидацией.
- Написание unit и e2e тестов.
- Настройка миграций и CI/CD.
- Переход в production с мониторингом и бэкапами.
Короткое объявление о релизе (пример для команды, 100–200 слов)
Объявление: Мы внедрили базовую реализацию CRUD REST API для сущности User на основе Nest.js и TypeORM с PostgreSQL. API поддерживает создание, чтение, обновление и удаление пользователей через /api/users и готово для интеграции с фронтендом. Для безопасности включена базовая валидация, а схема хранится в виде TypeORM-сущностей. В production отключите synchronize и используйте миграции. Список задач: подготовить DTO и валидацию, добавить авторизацию и покрыть критические пути тестами. Контакт для вопросов — backend команда.
Резюме
- Nest.js + TypeORM — быстрый старт для CRUD API.
- Для production используйте миграции, безопасное хранение секретов и валидацию.
- Рассмотрите альтернативы (Prisma, Fastify) если нужны иные свойства или производительность.
Ключевые выводы:
- Разделяйте ответственность — контроллеры для HTTP, сервисы для логики.
- Включайте DTO и class-validator для надёжной валидации.
- Тестируйте и готовьте миграции до развёртывания в production.
Важно: перед развертыванием в продакшн проверьте безопасность, резервное копирование и мониторинг.
Похожие материалы
Объявление переменных в JavaScript — var, let, const
Циклы и списки в Python — как перебирать элементы
Как копировать файлы в Python быстро
Внедрение зависимостей в PHP с Apex Container