Содержание
-
Введение
-
Установка и настройка проекта
-
Создание модуля и сущности
-
Создание DTO и валидация
-
Создание сервиса и контроллера
-
Реализация CRUD операций
-
Тестирование API
-
Заключение
Введение
NestJS — это прогрессивный фреймворк для построения эффективных и масштабируемых серверных приложений на Node.js. Он использует современные возможности JavaScript и TypeScript, вдохновлен архитектурными паттернами Angular и поддерживает модульность, инъекцию зависимостей и другие современные подходы.
TypeORM — это ORM (Object-Relational Mapping) инструмент, который позволяет взаимодействовать с базами данных, используя объекты и классы, что упрощает разработку и поддерживает различные СУБД, такие как PostgreSQL, MySQL, SQLite и другие.
Сочетание NestJS и TypeORM предоставляет мощный инструментарий для разработки REST API, обеспечивая высокую производительность, модульность и удобство поддержки кода.
Установка и настройка проекта
Установка NestJS CLI
Для начала установим NestJS CLI глобально на вашу машину:
npm install -g @nestjs/cli
Создание нового проекта
Создадим новый проект NestJS с именем my-nestjs-api
:
nest new my-nestjs-api
При выполнении команды CLI предложит выбрать пакетный менеджер (npm или yarn). Выберите предпочтительный вариант.
Установка TypeORM и необходимых зависимостей
Перейдите в директорию проекта и установите TypeORM вместе с выбранным драйвером базы данных. В этом примере будем использовать PostgreSQL:
cd my-nestjs-api
npm install --save @nestjs/typeorm typeorm pg
Настройка TypeORM
Откройте файл src/app.module.ts
и настройте подключение к базе данных:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'your_username',
password: 'your_password',
database: 'your_database',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
UsersModule,
],
})
export class AppModule {}
Примечание: Параметр synchronize: true
автоматически синхронизирует структуру базы данных с сущностями. Для продакшен-окружения рекомендуется отключить этот параметр и использовать миграции.
Создание модуля и сущности
Создание модуля Users
Используем CLI NestJS для создания модуля, сервиса и контроллера для пользователей:
nest generate module users
nest generate service users
nest generate controller users
Определение сущности User
Создадим файл user.entity.ts
в директории src/users/
:
// src/users/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 100 })
name: string;
@Column({ unique: true })
email: string;
@Column()
password: string;
}
Объяснение:
-
@Entity()
— декоратор, который указывает, что класс является сущностью базы данных. -
@PrimaryGeneratedColumn()
— автоматически генерируемый первичный ключ. -
@Column()
— колонка в таблице базы данных. Можно указывать дополнительные опции, такие какlength
иunique
.
Подключение сущности к модулю
Обновите файл users.module.ts
для подключения сущности:
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
Создание DTO и валидация
DTO (Data Transfer Object) используются для определения структуры данных, которые передаются через API. Это помогает обеспечить типизацию и валидацию входящих данных.
Установка библиотек для валидации
NestJS использует библиотеку class-validator
для валидации данных. Установим её вместе с class-transformer
:
npm install --save class-validator class-transformer
Создание DTO для создания пользователя
Создадим файл create-user.dto.ts
в директории src/users/
:
// src/users/create-user.dto.ts
import { IsString, IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
readonly name: string;
@IsEmail()
readonly email: string;
@IsString()
@MinLength(6)
readonly password: string;
}
Создание DTO для обновления пользователя
Создадим файл update-user.dto.ts
:
// src/users/update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
Объяснение:
-
PartialType
позволяет создать DTO, где все свойства опциональны, что удобно для операций обновления.
Применение DTO в контроллере
Обновим контроллер users.controller.ts
для использования DTO и валидации:
// src/users/users.controller.ts
import { Controller, Get, Post, Body, Param, Delete, Put, ParseIntPipe } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './create-user.dto';
import { UpdateUserDto } from './update-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
remove(@Param('id', ParseIntPipe) id: number) {
return this.usersService.remove(id);
}
}
Примечание: Использование ParseIntPipe
гарантирует, что параметр id
будет преобразован в число и валиден.
Создание сервиса и контроллера
Реализация UsersService
Обновим файл users.service.ts
для реализации бизнес-логики:
// src/users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { CreateUserDto } from './create-user.dto';
import { UpdateUserDto } from './update-user.dto';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
async findAll(): Promise<User[]> {
return this.usersRepository.find();
}
async findOne(id: number): Promise<User> {
const user = await this.usersRepository.findOneBy({ id });
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
await this.usersRepository.update(id, updateUserDto);
return this.findOne(id);
}
async remove(id: number): Promise<void> {
const result = await this.usersRepository.delete(id);
if (result.affected === 0) {
throw new NotFoundException(`User with ID ${id} not found`);
}
}
}
Объяснение:
-
Метод
create
: Создает нового пользователя и сохраняет его в базе данных. -
Метод
findAll
: Возвращает список всех пользователей. -
Метод
findOne
: Находит пользователя по ID. Если пользователь не найден, выбрасывает исключениеNotFoundException
. -
Метод
update
: Обновляет данные пользователя и возвращает обновленный объект. -
Метод
remove
: Удаляет пользователя по ID. Если пользователь не найден, выбрасывает исключениеNotFoundException
.
Обновление контроллера для использования сервиса
Контроллер уже был обновлен ранее для использования сервиса. Однако убедимся, что всё настроено корректно:
// src/users/users.controller.ts
import { Controller, Get, Post, Body, Param, Delete, Put, ParseIntPipe } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './create-user.dto';
import { UpdateUserDto } from './update-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
remove(@Param('id', ParseIntPipe) id: number) {
return this.usersService.remove(id);
}
}
Реализация CRUD операций
Теперь, когда сервис и контроллер настроены, мы можем выполнять операции CRUD (Create, Read, Update, Delete) через наше API.
Создание пользователя (Create)
HTTP Метод: POST
URL: /users
Тело запроса:
{
"name": "Иван Иванов",
"email": "ivan@example.com",
"password": "securepassword"
}
Пример с использованием cURL:
curl -X POST http://localhost:3000/users
-H "Content-Type: application/json"
-d '{"name": "Иван Иванов", "email": "ivan@example.com", "password": "securepassword"}'
Ответ:
{
"id": 1,
"name": "Иван Иванов",
"email": "ivan@example.com",
"password": "securepassword"
}
Получение списка пользователей (Read All)
HTTP Метод: GET
URL: /users
Пример с использованием cURL:
curl http://localhost:3000/users
Ответ:
[
{
"id": 1,
"name": "Иван Иванов",
"email": "ivan@example.com",
"password": "securepassword"
}
]
Получение пользователя по ID (Read One)
HTTP Метод: GET
URL: /users/1
Пример с использованием cURL:
curl http://localhost:3000/users/1
Ответ:
{
"id": 1,
"name": "Иван Иванов",
"email": "ivan@example.com",
"password": "securepassword"
}
Обновление пользователя (Update)
HTTP Метод: PUT
URL: /users/1
Тело запроса:
{
"name": "Иван Сергеевич Иванов"
}
Пример с использованием cURL:
curl -X PUT http://localhost:3000/users/1
-H "Content-Type: application/json"
-d '{"name": "Иван Сергеевич Иванов"}'
Ответ:
{
"id": 1,
"name": "Иван Сергеевич Иванов",
"email": "ivan@example.com",
"password": "securepassword"
}
Удаление пользователя (Delete)
HTTP Метод: DELETE
URL: /users/1
Пример с использованием cURL:
curl -X DELETE http://localhost:3000/users/1
Ответ: (Статус 200 OK без тела)
Тестирование API
Использование Postman
Postman — популярный инструмент для тестирования API. Вы можете использовать его для отправки запросов к вашему API и проверки ответов.
-
Создайте новую коллекцию в Postman для вашего проекта.
-
Добавьте запросы для каждого из CRUD операций:
-
POST /users для создания пользователя.
-
GET /users для получения списка пользователей.
-
GET /users/:id для получения пользователя по ID.
-
PUT /users/:id для обновления пользователя.
-
DELETE /users/:id для удаления пользователя.
-
-
Отправляйте запросы и проверяйте ответы, убеждаясь, что API работает корректно.
Написание e2e тестов
NestJS поддерживает написание e2e (end-to-end) тестов с использованием библиотеки supertest
. Давайте создадим простой тест для создания пользователя.
Установка дополнительных зависимостей:
npm install --save-dev supertest
Создание e2e теста:
Создайте файл users.e2e-spec.ts
в директории test/
:
// test/users.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('UsersController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
// Включим валидацию DTO
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
await app.init();
});
it('/users (POST)', () => {
return request(app.getHttpServer())
.post('/users')
.send({ name: 'Тестовый Пользователь', email: 'test@example.com', password: 'test123' })
.expect(201)
.then((response) => {
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe('Тестовый Пользователь');
expect(response.body.email).toBe('test@example.com');
});
});
it('/users (GET)', () => {
return request(app.getHttpServer())
.get('/users')
.expect(200)
.then((response) => {
expect(Array.isArray(response.body)).toBeTruthy();
expect(response.body.length).toBeGreaterThan(0);
});
});
it('/users/:id (GET)', () => {
return request(app.getHttpServer())
.get('/users/1')
.expect(200)
.then((response) => {
expect(response.body).toHaveProperty('id', 1);
});
});
it('/users/:id (PUT)', () => {
return request(app.getHttpServer())
.put('/users/1')
.send({ name: 'Обновленное Имя' })
.expect(200)
.then((response) => {
expect(response.body).toHaveProperty('name', 'Обновленное Имя');
});
});
it('/users/:id (DELETE)', () => {
return request(app.getHttpServer())
.delete('/users/1')
.expect(200);
});
afterAll(async () => {
await app.close();
});
});
Запуск тестов:
В файле package.json
убедитесь, что скрипт для e2e тестов настроен:
"scripts": {
// ... остальные ключи со значениями
"test:e2e": "jest --config ./test/jest-e2e.json"
}
Запустите тесты командой:
npm run test:e2e
Примечание: Убедитесь, что база данных для тестов настроена отдельно, чтобы не затронуть данные разработки или продакшена.
Заключение
В этой статье мы рассмотрели, как создать REST API с использованием NestJS и TypeORM. Мы прошли через установку и настройку проекта, создание модулей, сущностей, DTO, сервисов и контроллеров, а также реализовали основные CRUD операции и протестировали наше API.
Дальнейшие шаги и рекомендации
-
Аутентификация и авторизация:
-
Реализуйте систему аутентификации пользователей с использованием JWT.
-
Ограничьте доступ к определённым маршрутам для авторизованных пользователей.
-
-
Валидация и обработка ошибок:
-
Улучшите валидацию входящих данных.
-
Настройте глобальную обработку ошибок для более информативных ответов.
-
-
Миграции базы данных:
-
Используйте миграции TypeORM для управления изменениями схемы базы данных в продакшене.
-
-
Документация API:
-
Интегрируйте Swagger для автоматической генерации документации вашего API.
npm install --save @nestjs/swagger swagger-ui-express
Добавьте в
main.ts
:import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; const config = new DocumentBuilder() .setTitle('Users API') .setDescription('API для управления пользователями') .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document);
-
-
Тестирование производительности:
-
Проведите нагрузочное тестирование вашего API, используя инструменты вроде Artillery или JMeter.
-
-
Развертывание:
-
Разверните ваше приложение на облачных платформах, таких как AWS, Google Cloud, Heroku или DigitalOcean.
-
Настройте CI/CD для автоматического развертывания при изменениях в коде.
-
-
Безопасность:
-
Используйте HTTPS для защиты данных в транзите.
-
Реализуйте защиту от распространённых уязвимостей, таких как XSS, CSRF и SQL-инъекции.
-
Создание REST API с использованием NestJS и TypeORM предоставляет разработчикам мощный и гибкий инструментарий для быстрого создания масштабируемых и поддерживаемых серверных приложений. Следуя приведённым шагам и рекомендациям, вы сможете создать надёжное и эффективное API, соответствующее современным стандартам разработки.
Если у вас возникли вопросы или предложения, оставляйте их в комментариях ниже!
Полезные ссылки:
Автор: neon4on