Создание REST API с NestJS и TypeORM

от автора

Содержание

  1. Введение

  2. Установка и настройка проекта

  3. Создание модуля и сущности

  4. Создание DTO и валидация

  5. Создание сервиса и контроллера

  6. Реализация CRUD операций

  7. Тестирование API

  8. Заключение

Введение

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 и проверки ответов.

  1. Создайте новую коллекцию в Postman для вашего проекта.

  2. Добавьте запросы для каждого из CRUD операций:

    • POST /users для создания пользователя.

    • GET /users для получения списка пользователей.

    • GET /users/:id для получения пользователя по ID.

    • PUT /users/:id для обновления пользователя.

    • DELETE /users/:id для удаления пользователя.

  3. Отправляйте запросы и проверяйте ответы, убеждаясь, что 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.

Дальнейшие шаги и рекомендации

  1. Аутентификация и авторизация:

    • Реализуйте систему аутентификации пользователей с использованием JWT.

    • Ограничьте доступ к определённым маршрутам для авторизованных пользователей.

  2. Валидация и обработка ошибок:

    • Улучшите валидацию входящих данных.

    • Настройте глобальную обработку ошибок для более информативных ответов.

  3. Миграции базы данных:

    • Используйте миграции TypeORM для управления изменениями схемы базы данных в продакшене.

  4. Документация 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);
  5. Тестирование производительности:

    • Проведите нагрузочное тестирование вашего API, используя инструменты вроде Artillery или JMeter.

  6. Развертывание:

    • Разверните ваше приложение на облачных платформах, таких как AWS, Google Cloud, Heroku или DigitalOcean.

    • Настройте CI/CD для автоматического развертывания при изменениях в коде.

  7. Безопасность:

    • Используйте HTTPS для защиты данных в транзите.

    • Реализуйте защиту от распространённых уязвимостей, таких как XSS, CSRF и SQL-инъекции.

Создание REST API с использованием NestJS и TypeORM предоставляет разработчикам мощный и гибкий инструментарий для быстрого создания масштабируемых и поддерживаемых серверных приложений. Следуя приведённым шагам и рекомендациям, вы сможете создать надёжное и эффективное API, соответствующее современным стандартам разработки.

Если у вас возникли вопросы или предложения, оставляйте их в комментариях ниже!

Полезные ссылки:


ссылка на оригинал статьи https://habr.com/ru/articles/870988/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *