Создание CRUD REST API на Nest.js с TypeORM и PostgreSQL
Важное: все примеры предполагают, что у вас установлен Node.js и доступ к экземпляру PostgreSQL (локально или в облаке).
Ключевая идея
Nest.js — это фреймворк для Node.js, который даёт модульную, масштабируемую архитектуру и предлагает встроенную интеграцию с TypeORM. CRUD (create, read, update, delete) — основа любой REST API: модель (Entity), сервис (логика), контроллер (эндпоинты) и соединение с БД.
Введение
Как и другие фреймворки для Node.js, Nest.js предоставляет инструменты для построения надёжных и масштабируемых бэкенд-сервисов. Важная задача — грамотно реализовать CRUD-операции, чтобы API было простым, предсказуемым и тестируемым.
Что вы получите из этого руководства
- Полный рабочий пример CRUD REST API на Nest.js с TypeORM и PostgreSQL.
- Пошаговые команды для генерации модулей и файлов.
- Контрольные списки для ролей (разработчик, DevOps, QA).
- Критерии приёмки и набор тест-кейсов.
- Практические советы по безопасности, миграции и отладке.
Начало работы с Nest.js
Установите CLI Nest.js глобально:
npm i -g @nestjs/cliСоздайте новый проект:
nest new crud-appCLI предложит выбрать менеджер пакетов — используйте тот, который предпочитаете. В примерах ниже применяется npm.
Перейдите в директорию проекта и запустите сервер разработки:
cd crud-app
npm run startCLI сгенерирует базовую структуру проекта с необходимыми конфигурациями.
Создание базы данных PostgreSQL
В примере используется облачная инстанция PostgreSQL, но допустима локальная установка на Windows, macOS или Linux.
Чтобы создать облачный экземпляр (пример — ElephantSQL):
- Перейдите на ElephantSQL, зарегистрируйтесь и откройте страницу своего аккаунта.
- Нажмите кнопку создания нового экземпляра и следуйте инструкциям.
- Выберите тариф (есть бесплатный), укажите имя и регион.
- Перейдите в настройки экземпляра и скопируйте предоставленный DATABASE_URL.
Конфигурация соединения с базой данных
В корне проекта создайте файл .env и вставьте URL подключения:
DATABASE_URL="" Установите необходимые пакеты:
npm install pg typeorm @nestjs/typeorm @nestjs/configСоздайте модуль базы данных через CLI:
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 {}Пояснение: здесь TypeORM подключается к PostgreSQL по URL из .env и регистрирует сущность User. Параметр synchronize: true удобен в разработке, но в проде лучше использовать миграции.
Обновите AppModule
Добавьте DatabaseModule и конфигурацию окружения в app.module.ts:
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 {}Модуль users
Модуль users инкапсулирует логику CRUD для сущности User. Сгенерируйте модуль:
nest g module usersCLI добавит модуль в app.module.ts.
Создаём сущность User
TypeORM связывает классы-сущности с таблицами базы данных. Создайте файл users/models/user.entity.ts:
import { Entity, PrimaryGeneratedColumn, Column, } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
}Коротко: id — первичный ключ, name и email — обычные колонки.
Сервис для CRUD
Сгенерируйте сервис:
nest g service usersОткройте users.service.ts (в исходнике был опечатка user-auth.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);
}
} Пояснение: сервис реализует стандартные операции: получить всех, получить по id, создать, обновить и удалить.
Совет: на этапе валидации и безопасности добавьте DTO и Pipes (class-validator) для строгой валидации входящих данных.
Контроллер 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-запросы с сервисом. Рекомендуется добавить DTO для create/update и использовать Pipes для валидации.
Обновите 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 {}Тестирование CRUD локально
Запустите сервер:
npm run startПо умолчанию сервер стартует на порту 3000. Примеры запросов (curl):
- Получить всех:
curl http://localhost:3000/api/users- Создать пользователя:
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Ivan","email":"ivan@example.com"}'- Обновить пользователя:
curl -X PUT http://localhost:3000/api/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"Ivan Petrov"}'- Удалить пользователя:
curl -X DELETE http://localhost:3000/api/users/1Когда этот подход не подходит
- Очень простое приложение, где накладные расходы Nest.js избыточны. В таких случаях можно использовать более лёгкий Express/Koa.
- Высокопроизводительные сервисы с узкими требованиями к latency: иногда минималистичная настройка на чистом Node.js/Express даёт меньше абстракций.
Альтернативные подходы
- Использовать MikroORM вместо TypeORM (альтернатива с иными фичами миграции).
- Применять Prisma ORM для типобезопасных запросов и удобных миграций.
- Для GraphQL API заменить контроллеры на резолверы и использовать @nestjs/graphql.
Проверка качества: критерии приёмки
- API возвращает 200/201/204 корректные коды статуса.
- При создании пользователя возвращается его объект с id.
- При обновлении поля реально меняются в БД.
- Удалённый пользователь больше не доступен через GET.
- Валидация входных данных: email валиден, name не пуст.
Набор тест-кейсов (acceptance)
- Создать пользователя с валидными данными — ожидается 201 и объект пользователя.
- Создать пользователя с некорректным email — ожидается 400.
- Получить список пользователей — возвращается массив.
- Обновить существующего пользователя — возвращает сообщение об успешном обновлении и новые данные в БД.
- Удалить пользователя — при последующем GET по id возвращается 404.
Чек-листы по ролям
Разработчик
- Написать DTO и валидацию.
- Покрыть основные методы unit-тестами.
- Настроить логи и обработчики ошибок.
DevOps
- Настроить переменные окружения и секреты (не хранить DATABASE_URL в репозитории).
- Подготовить миграции и pipeline деплоя.
- Настроить мониторинг и бэкапы БД.
QA
- Проверить сценарии создания/обновления/удаления.
- Проверить валидацию и обработку ошибок.
- Выполнить нагрузочное тестирование для ключевых сценариев.
Security hardening (основные меры)
- Не используйте
synchronize: trueв проде — применяйте миграции. - Валидация входных данных (class-validator).
- Ограничьте вывод ошибок в продакшене, не раскрывайте SQL-детали.
- Храните секреты в безопасном хранилище (Vault/Secrets manager).
- Включите HTTPS и настройте CORS только для доверенных доменов.
Миграции и совместимость
- Для управления схемой используйте миграции TypeORM или инструменты упаковки миграций.
- При изменении сущностей планируйте миграции, тесты и откатные сценарии.
Мини-методология разработки CRUD
- Описать модель данных (Entity).
- Создать DTO и правила валидации для входа.
- Реализовать сервис с чистой бизнес-логикой (без HTTP-зависимостей).
- Сделать контроллеры, которые переводят HTTP-запросы в вызовы сервиса.
- Покрыть критичные сценарии тестами и интеграционными тестами с тестовой БД.
- Подготовить миграции и план деплоя.
Decision flow (принятие решения использовать Nest.js)
flowchart TD
A[Нужно масштабируемое приложение?] -->|Да| B[Использовать Nest.js]
A -->|Нет| C[Рассмотрите Express/Koa]
B --> D{Нужен ORM?}
D -->|Да| E[TypeORM/Prisma/MikroORM]
D -->|Нет| F[Используйте прямые SQL-клиенты]Примеры и паттерны (советы)
- DTO: используйте отдельные классы CreateUserDto и UpdateUserDto с class-validator.
- Транзакции: для сложных операций используйте менеджер транзакций TypeORM.
- Логирование: применяйте встроенный Logger Nest.js или сторонние службы (Sentry).
Отладка и распространённые ошибки
- Ошибка соединения к БД: проверьте DATABASE_URL и сетевые правила.
- Не найден репозиторий User: убедитесь, что сущность импортирована в TypeOrmModule.forFeature и в forRootAsync.
- Проблемы с типами: при использовании JS в place of TS — поддержите правильные типы в runtime.
Роллбек и инцидентный план
- Откатывайте миграции, если schema breaking.
- При падении сервиса быстро откатывайте на предыдущий стабильный образ.
- Имейте резервную копию БД перед крупными изменениями.
Краткая галерея крайних случаев
- Множественные связи (relations): добавляйте отношения постепенно и тестируйте join-ы.
- Большие payloads: используйте пагинацию и лимиты.
- Масштабирование: выносите базу на отдельный кластер, используйте read replicas.
1-строчная глоссарий
- Entity: класс, который отображается на таблицу БД.
- DTO: объект передачи данных, описывает вход/выход API.
- Repository: слой доступа к данным, инжектируемый TypeORM.
Заключение
Nest.js даёт структуру и проверенные паттерны, чтобы быстро строить и поддерживать CRUD API. Преимущество в модульности, тестируемости и готовых интеграциях с ORM. Для продакшна обращайте внимание на миграции, безопасность и наблюдаемость.
Краткий план следующего шага: добавить DTO и валидацию, покрыть сервисы unit-тестами и настроить миграции TypeORM перед развёртыванием.
Краткое резюме:
- Разверните окружение и подключите БД через
.env. - Опишите сущность, сервис и контроллер для CRUD.
- Добавьте валидацию, миграции и меры безопасности перед продакшеном.