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.
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.
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
DTOs are crucial for validating and typing incoming requests. They ensure that the data conforms to expected formats before processing.
// 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;
}
// 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;
}
Entities represent the data structure and are used by ORM/ODM to interact with the database.
// 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);
// 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;
}
Services in NestJS handle the core business logic and interact with the data layer. They are injectable and can be utilized by controllers.
// 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
}
// 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
}
Controllers handle incoming HTTP requests and delegate tasks to the corresponding services.
// 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
}
// 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
}
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.
Set up the database connection in the root module.
// 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 {}
// 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 {}
Securing your APIs is paramount. NestJS supports JWT authentication out of the box, enabling token-based security.
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.
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();
Implement global exception filters to handle errors gracefully and provide meaningful feedback to API consumers.
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
});
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();
After thorough development and testing, deploy your NestJS application using platforms like Heroku, AWS, or containerize it with Docker for scalable deployments.
Organize your application into modules, each encapsulating related components. This enhances maintainability and scalability.
Implement security best practices, including input sanitization, secure authentication mechanisms, and regular dependency updates to mitigate vulnerabilities.
Maintain high code quality and reliability by incorporating extensive testing strategies, covering unit, integration, and end-to-end tests.
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 |
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.