Содержание
-
Введение
-
Установка и настройка проекта
-
Создание модуля и сущности
-
Создание 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, соответствующее современным стандартам разработки.
Если у вас возникли вопросы или предложения, оставляйте их в комментариях ниже!
Полезные ссылки:
ссылка на оригинал статьи https://habr.com/ru/articles/870988/
Добавить комментарий