Chat
Search
Ithy Logo

Building Booking and Offer APIs with NestJS

A comprehensive guide to developing robust APIs using NestJS framework

nestjs api development

Key Takeaways

  • Effective Project Setup: Establish a solid foundation by initializing NestJS projects properly.
  • Comprehensive Feature Implementation: Develop detailed Booking and Offer functionalities with best practices.
  • Security and Scalability: Ensure APIs are secure, scalable, and maintainable through proper authentication and documentation.

Introduction

NestJS has emerged as a powerful framework for building scalable and maintainable server-side applications. Its architecture, inspired by Angular, promotes modularity and ease of testing, making it an ideal choice for developing complex APIs such as Booking and Offer systems. This guide provides a step-by-step approach to creating comprehensive Booking and Offer APIs using NestJS, integrating essential features like authentication, database management, and documentation.

Project Setup

Initializing the NestJS Project

Begin by installing the NestJS CLI globally if you haven't already:

npm install -g @nestjs/cli

Create a new NestJS project using the CLI:

nest new booking-offer-api

This command scaffolds a new NestJS project with a default structure, including modules, controllers, and services.

Installing Necessary Dependencies

Depending on your database choice and additional features, install the required packages. For instance, if you're using MongoDB with Mongoose:

npm install @nestjs/mongoose mongoose

Alternatively, for PostgreSQL with TypeORM:

npm install @nestjs/typeorm typeorm pg

Additionally, for validation and documentation:

npm install class-validator class-transformer @nestjs/swagger swagger-ui-express

Defining Models and Entities

Creating Data Transfer Objects (DTOs)

DTOs are crucial for validating and typing incoming requests. They ensure that the data conforms to expected formats before processing.

Booking DTO Example

// src/bookings/dto/create-booking.dto.ts
import { IsString, IsDateString, IsNumber } from 'class-validator';

export class CreateBookingDto {
  @IsString()
  readonly userId: string;

  @IsString()
  readonly spaceId: string;

  @IsDateString()
  readonly startTime: string;

  @IsDateString()
  readonly endTime: string;

  @IsString()
  readonly status: string;
}
  

Offer DTO Example

// src/offers/dto/create-offer.dto.ts
import { IsString, IsNumber, IsDateString } from 'class-validator';

export class CreateOfferDto {
  @IsString()
  readonly offerType: string;

  @IsNumber()
  readonly discount: number;

  @IsDateString()
  readonly validFrom: string;

  @IsDateString()
  readonly validTo: string;
}
  

Defining Entities

Entities represent the data structure and are used by ORM/ODM to interact with the database.

Booking Entity Example

// src/bookings/entities/booking.entity.ts
import { Schema, Prop, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type BookingDocument = Booking & Document;

@Schema()
export class Booking {
  @Prop({ required: true })
  userId: string;

  @Prop({ required: true })
  spaceId: string;

  @Prop({ required: true })
  startTime: Date;

  @Prop({ required: true })
  endTime: Date;

  @Prop({ required: true, default: 'pending' })
  status: string;
}

export const BookingSchema = SchemaFactory.createForClass(Booking);
  

Offer Entity Example

// src/offers/entities/offer.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Offer {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  offerType: string;

  @Column('float')
  discount: number;

  @Column()
  validFrom: Date;

  @Column()
  validTo: Date;
}
  

Creating Services

Implementing Business Logic

Services in NestJS handle the core business logic and interact with the data layer. They are injectable and can be utilized by controllers.

Booking Service Example

// src/bookings/bookings.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Booking, BookingDocument } from './entities/booking.entity';
import { CreateBookingDto } from './dto/create-booking.dto';

@Injectable()
export class BookingsService {
  constructor(@InjectModel(Booking.name) private bookingModel: Model<BookingDocument>) {}

  async create(createBookingDto: CreateBookingDto): Promise<Booking> {
    const createdBooking = new this.bookingModel(createBookingDto);
    return createdBooking.save();
  }

  async findAll(): Promise<Booking[]> {
    return this.bookingModel.find().exec();
  }

  async findOne(id: string): Promise<Booking> {
    const booking = await this.bookingModel.findById(id).exec();
    if (!booking) {
      throw new NotFoundException(`Booking with ID ${id} not found`);
    }
    return booking;
  }

  // Additional methods for update and delete can be implemented here
}
  

Offer Service Example

// src/offers/offers.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Offer } from './entities/offer.entity';
import { CreateOfferDto } from './dto/create-offer.dto';

@Injectable()
export class OffersService {
  constructor(@InjectRepository(Offer) private offerRepository: Repository<Offer>) {}

  async create(createOfferDto: CreateOfferDto): Promise<Offer> {
    const offer = this.offerRepository.create(createOfferDto);
    return this.offerRepository.save(offer);
  }

  async findAll(): Promise<Offer[]> {
    return this.offerRepository.find();
  }

  async findOne(id: number): Promise<Offer> {
    const offer = await this.offerRepository.findOne(id);
    if (!offer) {
      throw new NotFoundException(`Offer with ID ${id} not found`);
    }
    return offer;
  }

  // Additional methods for update and delete can be implemented here
}
  

Developing Controllers and Routes

Exposing API Endpoints

Controllers handle incoming HTTP requests and delegate tasks to the corresponding services.

Booking Controller Example

// src/bookings/bookings.controller.ts
import { Controller, Post, Get, Param, Body } from '@nestjs/common';
import { BookingsService } from './bookings.service';
import { CreateBookingDto } from './dto/create-booking.dto';

@Controller('bookings')
export class BookingsController {
  constructor(private readonly bookingsService: BookingsService) {}

  @Post()
  async create(@Body() createBookingDto: CreateBookingDto) {
    return this.bookingsService.create(createBookingDto);
  }

  @Get()
  async findAll() {
    return this.bookingsService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: string) {
    return this.bookingsService.findOne(id);
  }

  // Additional routes for update and delete can be added here
}
  

Offer Controller Example

// src/offers/offers.controller.ts
import { Controller, Post, Get, Param, Body, UseGuards } from '@nestjs/common';
import { OffersService } from './offers.service';
import { CreateOfferDto } from './dto/create-offer.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';

@Controller('offers')
export class OffersController {
  constructor(private readonly offersService: OffersService) {}

  @Post()
  @UseGuards(JwtAuthGuard) // Protecting the route for admins
  async create(@Body() createOfferDto: CreateOfferDto) {
    return this.offersService.create(createOfferDto);
  }

  @Get()
  async findAll() {
    return this.offersService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: number) {
    return this.offersService.findOne(id);
  }

  // Additional routes for update and delete can be added here
}
  

Database Integration

Choosing the Right Database

Select a database that aligns with your application's needs. MongoDB with Mongoose offers flexibility for JSON-like data, while PostgreSQL with TypeORM provides robust relational data management.

Configuring the Database Module

Set up the database connection in the root module.

MongoDB with Mongoose Example

// src/app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { BookingsModule } from './bookings/bookings.module';
import { OffersModule } from './offers/offers.module';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost/nest'),
    BookingsModule,
    OffersModule,
  ],
})
export class AppModule {}
  

PostgreSQL with TypeORM Example

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookingsModule } from './bookings/bookings.module';
import { OffersModule } from './offers/offers.module';
import { Offer } from './offers/entities/offer.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'your_username',
      password: 'your_password',
      database: 'nest',
      entities: [Offer],
      synchronize: true,
    }),
    BookingsModule,
    OffersModule,
  ],
})
export class AppModule {}
  

Authentication and Authorization

Implementing JWT Authentication

Securing your APIs is paramount. NestJS supports JWT authentication out of the box, enabling token-based security.

Setting Up Authentication

Install necessary packages:

npm install @nestjs/jwt @nestjs/passport passport passport-jwt

Create a JWT strategy and guard to protect specific routes, ensuring only authenticated users or admins can access certain endpoints.

Validation and Error Handling

Request Validation

Use the class-validator and class-transformer packages to validate incoming DTOs, ensuring data integrity.

// src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();
  

Global Error Handling

Implement global exception filters to handle errors gracefully and provide meaningful feedback to API consumers.

Testing Your APIs

Unit and Integration Testing

Ensure your APIs are reliable by writing unit and integration tests using Jest, which comes integrated with NestJS.

// Example test for BookingsService
import { Test, TestingModule } from '@nestjs/testing';
import { BookingsService } from './bookings.service';
import { getModelToken } from '@nestjs/mongoose';
import { Booking } from './entities/booking.entity';

describe('BookingsService', () => {
  let service: BookingsService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        BookingsService,
        {
          provide: getModelToken(Booking.name),
          useValue: {}, // mock implementation
        },
      ],
    }).compile();

    service = module.get<BookingsService>(BookingsService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  // Additional tests for service methods
});
  

API Documentation

Using Swagger with NestJS

Generate interactive API documentation using Swagger to facilitate easier integration and testing for developers.

// src/main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  const config = new DocumentBuilder()
    .setTitle('Booking and Offer API')
    .setDescription('API documentation for Booking and Offer services')
    .setVersion('1.0')
    .addTag('bookings')
    .addTag('offers')
    .build();
    
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);
  
  await app.listen(3000);
}
bootstrap();
  

Deployment

Preparing for Production

After thorough development and testing, deploy your NestJS application using platforms like Heroku, AWS, or containerize it with Docker for scalable deployments.

Best Practices

Modular Architecture

Organize your application into modules, each encapsulating related components. This enhances maintainability and scalability.

Secure Coding Standards

Implement security best practices, including input sanitization, secure authentication mechanisms, and regular dependency updates to mitigate vulnerabilities.

Comprehensive Testing

Maintain high code quality and reliability by incorporating extensive testing strategies, covering unit, integration, and end-to-end tests.

Sample API Endpoints

HTTP Method Endpoint Description
POST /bookings Create a new booking
GET /bookings Retrieve all bookings
GET /bookings/:id Retrieve a booking by ID
POST /offers Create a new offer (admin only)
GET /offers Retrieve all offers
GET /offers/:id Retrieve an offer by ID

Conclusion

Developing Booking and Offer APIs with NestJS leverages the framework's robust architecture to create scalable and maintainable applications. By following best practices in project setup, modular design, security, and documentation, you can build APIs that not only meet current requirements but are also adaptable to future needs. Comprehensive testing and clear documentation further ensure the reliability and usability of your APIs, facilitating seamless integration and efficient development workflows.

References


Last updated February 11, 2025
Ask Ithy AI
Download Article
Delete Article