Skip to content

ecom-co/lib-utils

Repository files navigation

@ecom-co/utils

Version Downloads License TypeScript NestJS

A comprehensive utility library for e-commerce platform development with NestJS

FeaturesInstallationQuick StartDocumentationExamples


✨ Features

  • 🎯 Type-Safe Validation Decorators - Comprehensive field validation with automatic Swagger integration
  • 📚 API Documentation - Streamlined Swagger documentation with @ApiEndpoint decorators
  • 🔍 Validation Error Documentation - Auto-generate validation error examples in Swagger
  • 🏗️ Standardized DTOs - Consistent API response and error handling structures
  • 🔐 Multi-Provider Authentication - Support for multiple JWT, API Key, OAuth2, Basic, and Cookie auth providers
  • 📄 Pagination Utilities - Offset and cursor-based pagination support
  • 🛡️ Error Handling - Global exception filters with standardized responses
  • 🎨 Modern UI Components - Beautiful and responsive design patterns
  • Performance Optimized - Lightweight and efficient utilities

🚀 Installation

npm install @ecom-co/utils
yarn add @ecom-co/utils
pnpm add @ecom-co/utils

⚡ Quick Start

1. Basic Setup

import { Module } from '@nestjs/common';
import { setUpSwagger } from '@ecom-co/utils';

@Module({
  imports: [],
  controllers: [],
  providers: [],
})
export class AppModule {}

// In main.ts
const app = await NestFactory.create(AppModule);

// Setup Swagger with multiple authentication providers
setUpSwagger(app, {
  port: 3000,
  title: 'E-commerce API',
  description: 'Comprehensive e-commerce platform API',
  version: '1.0.0',
  nodeEnv: 'development',
  servers: [
    { url: 'http://localhost:3000', description: 'Local Development' },
    { url: 'https://api-staging.example.com', description: 'Staging Environment' },
    { url: 'https://api.example.com', description: 'Production Environment' },
  ],
  jwt: {
    providers: [
      {
        name: 'access-token',
        description: 'JWT Access Token for regular users',
      },
      {
        name: 'admin-token',
        description: 'JWT Admin Token for administrative access',
      },
    ],
  },
  apiKey: {
    providers: [
      {
        name: 'internal-key',
        in: 'header',
        keyName: 'X-Internal-Key',
        description: 'Internal API Key for service-to-service communication',
      },
    ],
  },
  oauth2: {
    providers: [
      {
        name: 'google',
        authorizationUrl: 'https://accounts.google.com/oauth/authorize',
        tokenUrl: 'https://oauth2.googleapis.com/token',
        scopes: {
          'https://www.googleapis.com/auth/userinfo.profile': 'View profile',
          'https://www.googleapis.com/auth/userinfo.email': 'View email',
        },
        description: 'Google OAuth2 authentication',
      },
    ],
  },
});

2. Create Your First DTO

import { 
  StringField, 
  EmailField, 
  NumberField, 
  BooleanField 
} from '@ecom-co/utils';

export class CreateUserDto {
  @StringField({ 
    minLength: 2, 
    maxLength: 50,
    description: 'User full name'
  })
  name!: string;

  @EmailField({ 
    description: 'User email address' 
  })
  email!: string;

  @NumberField({ 
    min: 18, 
    max: 100,
    description: 'User age'
  })
  age!: number;

  @BooleanField({ 
    description: 'Whether user is active' 
  })
  isActive!: boolean;
}

3. Create Your First Controller

import { Controller, Post, Body } from '@nestjs/common';
import { ApiValidationEndpoint, AUTH_TYPE, HttpStatus } from '@ecom-co/utils';
import { CreateUserDto, UserDto } from './dto';

@Controller('users')
export class UserController {
  @ApiValidationEndpoint({
    summary: 'Create a new user',
    description: 'Creates a new user account with the provided information',
    tags: ['Users'],
    response: UserDto,
    auth: { type: AUTH_TYPE.JWT, provider: 'access-token', required: true },
    body: { type: CreateUserDto },
    errors: [HttpStatus.CONFLICT],
    validation: {
      errorExamples: [
        { field: 'name', constraint: 'isNotEmpty', message: 'name should not be empty' },
        { field: 'email', constraint: 'isEmail', message: 'email must be an email' }
      ]
    }
  })
  @Post()
  async createUser(@Body() createUserDto: CreateUserDto) {
    // Your implementation here
    return { message: 'User created successfully' };
  }

  // Example with multiple authentication providers
  @ApiValidationEndpoint({
    summary: 'Admin user management',
    description: 'Admin-only endpoint for user management',
    tags: ['Users', 'Admin'],
    response: UserDto,
    auth: [
      { type: AUTH_TYPE.JWT, provider: 'admin-token', required: true },
      { type: AUTH_TYPE.API_KEY, provider: 'internal-key', required: false },
    ],
    body: { type: CreateUserDto },
    errors: [HttpStatus.FORBIDDEN, HttpStatus.CONFLICT],
  })
  @Post('admin')
  async createUserAsAdmin(@Body() createUserDto: CreateUserDto) {
    // Admin implementation
    return { message: 'User created by admin' };
  }
}

📚 Documentation

🎯 Validation Decorators

Basic Field Types

Decorator Type Description
@StringField() string Text validation with length constraints
@NumberField() number Numeric validation with min/max values
@BooleanField() boolean Boolean validation with transformation
@EmailField() string Email validation with format checking
@UUIDField() string UUID v4 validation
@DateField() Date Date validation with range constraints

Advanced Field Types

Decorator Type Description
@ArrayField() T[] Array validation with size constraints
@EnumField() enum Enum validation with type safety
@ClassField() class Nested object validation
@PasswordField() string Password validation with custom patterns
@PhoneField() string Phone number validation
@JsonField() object JSON string validation
@FileField() File File upload validation
@GeoField() number Geographic coordinates validation
@CreditCardField() string Credit card number validation
@CurrencyField() number Currency amount validation

Usage Examples

import { 
  StringField, 
  NumberField, 
  ArrayField, 
  DateField,
  EnumField,
  PasswordField,
  CurrencyField
} from '@ecom-co/utils';

export class ProductDto {
  @StringField({ 
    minLength: 3, 
    maxLength: 100,
    description: 'Product name',
    toLowerCase: true,
    trim: true
  })
  name!: string;

  @NumberField({ 
    min: 0,
    max: 1000000,
    isPositive: true,
    description: 'Product price'
  })
  price!: number;

  @ArrayField(() => String, { 
    minSize: 1, 
    maxSize: 10,
    uniqueItems: true,
    description: 'Product tags'
  })
  tags!: string[];

  @DateField({ 
    minDate: new Date('2020-01-01'),
    description: 'Product creation date'
  })
  createdAt!: Date;

  @EnumField(() => ProductStatus, { 
    description: 'Product status' 
  })
  status!: ProductStatus;

  @PasswordField(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/, {
    description: 'Strong password with special characters'
  })
  password!: string;

  @CurrencyField({ 
    min: 0,
    description: 'Product price in USD'
  })
  priceUSD!: number;
}

Optional Fields

export class UpdateUserDto {
  @StringFieldOptional({ 
    minLength: 2, 
    maxLength: 50 
  })
  name?: string;

  @EmailFieldOptional()
  email?: string;

  @NumberFieldOptional({ 
    min: 18, 
    max: 100 
  })
  age?: number;
}

📚 API Documentation Decorators

Basic Usage

import { ApiEndpoint, AUTH_TYPE, PAGINATION_TYPE, HttpStatus } from '@ecom-co/utils';

@Controller('products')
export class ProductController {
  @ApiEndpoint({
    summary: 'Get all products',
    description: 'Retrieve a paginated list of products with filtering options',
    tags: ['Products'],
    responses: { 
      [HttpStatus.OK]: { 
        type: ProductDto, 
        description: 'List of products' 
      } 
    },
    paginationType: PAGINATION_TYPE.OFFSET,
    queries: [
      { name: 'search', type: 'string', description: 'Search term' },
      { name: 'category', type: 'string', enum: ['electronics', 'clothing', 'books'] },
      { name: 'minPrice', type: 'number', description: 'Minimum price filter' },
      { name: 'maxPrice', type: 'number', description: 'Maximum price filter' }
    ],
    auth: { type: AUTH_TYPE.JWT, required: false },
    includeCommonErrors: true
  })
  @Get()
  async getProducts() {
    // Implementation
  }
}

Shorthand Decorators

// GET endpoint
@ApiGetEndpoint({
  summary: 'Get product by ID',
  description: 'Retrieve a specific product by its unique identifier',
  tags: ['Products'],
  response: ProductDto,
  params: [{ name: 'id', type: 'uuid', description: 'Product ID' }],
  auth: { type: AUTH_TYPE.JWT, required: false },
  errors: [HttpStatus.NOT_FOUND]
})
@Get(':id')
async getProduct(@Param('id') id: string) {}

// POST endpoint
@ApiPostEndpoint({
  summary: 'Create new product',
  description: 'Create a new product with the provided information',
  tags: ['Products'],
  response: ProductDto,
  body: { type: CreateProductDto, description: 'Product creation data' },
  auth: { type: AUTH_TYPE.JWT, required: true },
  errors: [HttpStatus.CONFLICT, HttpStatus.BAD_REQUEST]
})
@Post()
async createProduct(@Body() dto: CreateProductDto) {}

// PUT endpoint
@ApiPutEndpoint({
  summary: 'Update product',
  description: 'Update an existing product',
  tags: ['Products'],
  response: ProductDto,
  body: { type: UpdateProductDto },
  params: [{ name: 'id', type: 'uuid' }],
  auth: { type: AUTH_TYPE.JWT, required: true },
  errors: [HttpStatus.NOT_FOUND, HttpStatus.BAD_REQUEST]
})
@Put(':id')
async updateProduct(@Param('id') id: string, @Body() dto: UpdateProductDto) {}

// DELETE endpoint
@ApiDeleteEndpoint({
  summary: 'Delete product',
  description: 'Permanently delete a product',
  tags: ['Products'],
  params: [{ name: 'id', type: 'uuid' }],
  auth: { type: AUTH_TYPE.JWT, required: true },
  errors: [HttpStatus.NOT_FOUND]
})
@Delete(':id')
async deleteProduct(@Param('id') id: string) {}

// Validation endpoint with error documentation
@ApiValidationEndpoint({
  summary: 'Create user with validation',
  response: UserDto,
  body: { type: CreateUserDto },
  validation: {
    errorExamples: [
      { field: 'email', constraint: 'isEmail', message: 'email must be an email' },
      { field: 'password', constraint: 'minLength', message: 'password must be longer than or equal to 8 characters' }
    ]
  }
})
@Post()
async createUser(@Body() dto: CreateUserDto) {}

Advanced Configuration

@ApiEndpoint({
  summary: 'Advanced product search',
  description: 'Search products with complex filtering and sorting',
  tags: ['Products'],
  responses: { 
    [HttpStatus.OK]: { 
      type: ProductDto, 
      description: 'Filtered products' 
    } 
  },
  paginationType: PAGINATION_TYPE.CURSOR,
  queries: [
    { name: 'q', type: 'string', description: 'Search query' },
    { name: 'category', type: 'string', enum: ['electronics', 'clothing', 'books'] },
    { name: 'brand', type: 'string' },
    { name: 'minPrice', type: 'number' },
    { name: 'maxPrice', type: 'number' },
    { name: 'inStock', type: 'boolean' },
    { name: 'sortBy', type: 'string', enum: ['name', 'price', 'createdAt'] },
    { name: 'sortOrder', type: 'string', enum: ['asc', 'desc'] }
  ],
  auth: { type: AUTH_TYPE.JWT, required: false },
  rateLimit: {
    limit: 100,
    window: '1h',
    message: 'Too many requests, please try again later'
  },
  cache: {
    ttl: 300,
    description: 'Cache results for 5 minutes'
  },
  includeCommonErrors: true,
  validation: {
    includeValidationErrors: true,
    errorExamples: [
      { field: 'search', constraint: 'isNotEmpty', message: 'search should not be empty' },
      { field: 'minPrice', constraint: 'isPositive', message: 'minPrice must be a positive number' }
    ]
  }
})
@Get('search')
async searchProducts() {
  // Implementation
}

Validation Error Documentation

// With custom validation error examples
@ApiValidationEndpoint({
  summary: 'Create user with validation docs',
  response: UserDto,
  body: { type: CreateUserDto },
  validation: {
    errorExamples: [
      { field: 'name', constraint: 'isNotEmpty', message: 'name should not be empty' },
      { field: 'email', constraint: 'isEmail', message: 'email must be an email' },
      { field: 'password', constraint: 'minLength', message: 'password must be longer than or equal to 8 characters' }
    ]
  }
})
@Post()
async createUser(@Body() dto: CreateUserDto) {}

// Simple validation documentation
@ApiValidationEndpoint({
  summary: 'Create product',
  response: ProductDto,
  body: { type: CreateProductDto }
})
@Post()
async createProduct(@Body() dto: CreateProductDto) {}

📋 DTOs

API Response DTO

import { ApiResponseDto, ApiResponseData } from '@ecom-co/utils';

// Create response DTO for User
const UserResponseDto = ApiResponseDto(UserDto);

// Usage in controller with object constructor (Recommended)
@ApiGetEndpoint({
  response: UserResponseDto
})
@Get(':id')
async getUser(@Param('id') id: string) {
  const user = await this.userService.findById(id);
  
  // Using object constructor
  return new ApiResponseData({
    data: user,
    message: 'User retrieved successfully',
    statusCode: 200
  });
}

// Alternative: Using static factory method (Backward compatibility)
return ApiResponseData.create(
  userDto, 
  'User retrieved successfully', 
  200
);

Error Response DTO

import { ErrorResponseDto } from '@ecom-co/utils';

@ApiEndpoint({
  errors: [
    HttpStatus.NOT_FOUND,
    { 
      status: HttpStatus.BAD_REQUEST, 
      description: 'Invalid user data' 
    },
    {
      status: HttpStatus.CONFLICT,
      description: 'User already exists'
    }
  ]
})

Paginated Response DTO

import { 
  ApiPaginatedResponseDto, 
  ApiCursorPaginatedResponseDto,
  ApiPaginatedResponseData,
  ApiCursorPaginatedResponseData,
  Paging,
  CursorPaging
} from '@ecom-co/utils';

// Offset pagination
const UserPaginatedResponseDto = ApiPaginatedResponseDto(UserDto);

// Cursor pagination
const UserCursorPaginatedResponseDto = ApiCursorPaginatedResponseDto(UserDto);

// Usage in controller - Offset Pagination
@ApiEndpoint({
  response: UserPaginatedResponseDto,
  pagination: PAGINATION_TYPE.OFFSET
})
@Get()
async getUsers(@Query('page') page = 1, @Query('limit') limit = 10) {
  const result = await this.userService.findWithPagination({ page, limit });
  
  // Method 1: Using object constructor (Recommended)
  const paging = new Paging({
    page: result.page,
    limit: result.limit,
    total: result.total,
    currentPageSize: result.data.length
    // Other fields will be auto-calculated
  });

  return new ApiPaginatedResponseData({
    data: result.data,
    paging,
    message: 'Users retrieved successfully'
  });

  // Method 2: Using helper method (Easiest)
  return ApiPaginatedResponseData.createWithAutoPaging({
    data: result.data,
    total: result.total,
    page: result.page,
    limit: result.limit,
    message: 'Users retrieved successfully'
  });

  // Method 3: Using Paging helper method
  const paging2 = Paging.createWithAutoCalculation({
    page: result.page,
    limit: result.limit,
    total: result.total,
    currentPageSize: result.data.length
  });

  return new ApiPaginatedResponseData({
    data: result.data,
    paging: paging2,
    message: 'Users retrieved successfully'
  });
}

// Usage in controller - Cursor Pagination
@ApiEndpoint({
  response: UserCursorPaginatedResponseDto,
  pagination: PAGINATION_TYPE.CURSOR
})
@Get()
async getProducts(@Query('cursor') cursor?: string, @Query('limit') limit = 20) {
  const result = await this.productService.findWithCursorPagination({ cursor, limit });
  
  // Method 1: Using object constructor (Recommended)
  const cursorPaging = new CursorPaging({
    nextCursor: result.nextCursor,
    hasNextPage: result.hasNextPage,
    previousCursor: result.previousCursor,
    firstCursor: result.firstCursor,
    lastCursor: result.lastCursor,
    currentPageSize: result.data.length,
    total: result.total,
    currentPage: result.currentPage
    // Other fields will be auto-calculated
  });

  return new ApiCursorPaginatedResponseData({
    data: result.data,
    cursorPaging,
    message: 'Products retrieved successfully'
  });

  // Method 2: Using helper method (Easiest)
  return ApiCursorPaginatedResponseData.createWithAutoCursors({
    data: result.data,
    limit,
    nextCursor: result.nextCursor,
    previousCursor: result.previousCursor,
    firstCursor: result.firstCursor,
    lastCursor: result.lastCursor,
    total: result.total,
    currentPage: result.currentPage,
    message: 'Products retrieved successfully'
  });

  // Method 3: Using CursorPaging helper method
  const cursorPaging2 = CursorPaging.createWithAutoCalculation({
    data: result.data as any[],
    limit,
    nextCursor: result.nextCursor,
    previousCursor: result.previousCursor,
    firstCursor: result.firstCursor,
    lastCursor: result.lastCursor,
    total: result.total,
    currentPage: result.currentPage
  });

  return new ApiCursorPaginatedResponseData({
    data: result.data,
    cursorPaging: cursorPaging2,
    message: 'Products retrieved successfully'
  });
}

Pagination Constructor Examples

// Paging Object Constructor Examples
const paging = new Paging({
  page: 1,
  limit: 10,
  total: 100
  // Other fields auto-calculated
});

const paging2 = new Paging({
  page: 2,
  limit: 20,
  total: 500,
  currentPageSize: 20,
  hasPreviousPage: true,
  hasNextPage: true
});

// Using Paging helper method
const paging3 = Paging.createWithAutoCalculation({
  page: 3,
  limit: 15,
  total: 300,
  currentPageSize: 15
});

// CursorPaging Object Constructor Examples
const cursorPaging = new CursorPaging({
  nextCursor: 'abc123',
  hasNextPage: true
});

const cursorPaging2 = new CursorPaging({
  nextCursor: 'def456',
  hasNextPage: true,
  previousCursor: 'abc123',
  firstCursor: 'first123',
  lastCursor: 'last789',
  currentPageSize: 20,
  total: 1000,
  currentPage: 2
});

// Using CursorPaging helper method
const cursorPaging3 = CursorPaging.createWithAutoCalculation({
  data: dataArray,
  limit,
  nextCursor,
  previousCursor,
  firstCursor,
  lastCursor,
  total,
  currentPage
});

🔐 Multi-Provider Authentication

Authentication Types

import { AUTH_TYPE } from '@ecom-co/utils';

// Available auth types
AUTH_TYPE.JWT        // JWT Bearer token
AUTH_TYPE.API_KEY    // API Key authentication
AUTH_TYPE.OAUTH2     // OAuth2 authentication
AUTH_TYPE.BASIC      // Basic authentication
AUTH_TYPE.COOKIE     // Cookie-based authentication

// Multiple authentication providers support
// JWT Providers: access-token, admin-token, service-token, refresh-token
// API Key Providers: internal-key, external-key, query-api-key
// OAuth2 Providers: google, github, facebook, etc.

Multiple Authentication Providers

// Single provider
@ApiEndpoint({
  auth: { type: AUTH_TYPE.JWT, provider: 'access-token', required: true }
})

// Multiple providers (OR logic)
@ApiEndpoint({
  auth: [
    { type: AUTH_TYPE.JWT, provider: 'access-token', required: false },
    { type: AUTH_TYPE.JWT, provider: 'admin-token', required: false },
    { type: AUTH_TYPE.API_KEY, provider: 'internal-key', required: false },
  ]
})

// Multiple API Key providers
@ApiEndpoint({
  auth: [
    { type: AUTH_TYPE.API_KEY, provider: 'prod-key' },
    { type: AUTH_TYPE.API_KEY, provider: 'staging-key' },
  ]
})

OAuth2 Providers

// Google OAuth2
@ApiEndpoint({
  auth: { type: AUTH_TYPE.OAUTH2, provider: 'google', scopes: ['https://www.googleapis.com/auth/userinfo.profile'] }
})

// GitHub OAuth2
@ApiEndpoint({
  auth: { type: AUTH_TYPE.OAUTH2, provider: 'github', scopes: ['user'] }
})

// Multiple OAuth2 providers
@ApiEndpoint({
  auth: [
    { type: AUTH_TYPE.OAUTH2, provider: 'google', scopes: ['https://www.googleapis.com/auth/userinfo.profile'] },
    { type: AUTH_TYPE.OAUTH2, provider: 'github', scopes: ['user'] },
  ]
})

API Key Providers

// Header-based API Key
@ApiEndpoint({
  auth: { type: AUTH_TYPE.API_KEY, provider: 'internal-key' }
})

// Query-based API Key
@ApiEndpoint({
  auth: { type: AUTH_TYPE.API_KEY, provider: 'query-api-key' }
})

// Cookie-based API Key
@ApiEndpoint({
  auth: { type: AUTH_TYPE.API_KEY, provider: 'cookie-api-key' }
})

🔧 Constants

Pagination Types

import { PAGINATION_TYPE } from '@ecom-co/utils';

PAGINATION_TYPE.OFFSET  // Offset-based pagination
PAGINATION_TYPE.CURSOR  // Cursor-based pagination

Database Operators

import { OPERATOR } from '@ecom-co/utils';

// Available operators for queries
OPERATOR.EQ    // Equal
OPERATOR.NE    // Not equal
OPERATOR.GT    // Greater than
OPERATOR.GTE   // Greater than or equal
OPERATOR.LT    // Less than
OPERATOR.LTE   // Less than or equal
OPERATOR.LIKE  // Like (string matching)
OPERATOR.IN    // In array
OPERATOR.NIN   // Not in array

Pagination Types

import { PAGINATION_TYPE } from '@ecom-co/utils';

PAGINATION_TYPE.OFFSET  // Offset-based pagination
PAGINATION_TYPE.CURSOR  // Cursor-based pagination

Database Operators

import { OPERATOR } from '@ecom-co/utils';

// Available operators for queries
OPERATOR.EQ    // Equal
OPERATOR.NE    // Not equal
OPERATOR.GT    // Greater than
OPERATOR.GTE   // Greater than or equal
OPERATOR.LT    // Less than
OPERATOR.LTE   // Less than or equal
OPERATOR.LIKE  // Like (string matching)
OPERATOR.IN    // In array
OPERATOR.NIN   // Not in array

🛡️ Filters

HTTP Exception Filter

import { HttpExceptionFilter } from '@ecom-co/utils';
import { Reflector } from '@nestjs/core';

// In your main.ts
app.useGlobalFilters(new HttpExceptionFilter(app.get(Reflector)));

// Or in a controller
@UseFilters(new HttpExceptionFilter(reflector))
export class AppController {}

// With custom options
app.useGlobalFilters(
  new HttpExceptionFilter(
    app.get(Reflector),
    {
      isDevelopment: process.env.NODE_ENV === 'development',
      enableSanitization: true,
      enableRateLimitTracking: true,
      customErrorMessages: {
        404: 'Resource not found',
        500: 'Internal server error'
      }
    }
  )
);

Validation Exception

Custom validation exception with structured error handling:

import { ValidationException } from '@ecom-co/utils';

// Manual validation
if (!user.email) {
  throw new ValidationException([
    {
      property: 'email',
      constraints: {
        isNotEmpty: 'Email is required'
      }
    }
  ]);
}

// Multiple validation errors
if (!user.email || !user.password) {
  throw new ValidationException([
    {
      property: 'email',
      constraints: {
        isNotEmpty: 'Email is required'
      }
    },
    {
      property: 'password',
      constraints: {
        isNotEmpty: 'Password is required',
        minLength: 'Password must be at least 8 characters'
      }
    }
  ]);
}

Response Format:

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Validation failed",
  "errors": [
    "name should not be empty",
    "email must be an email"
  ],
  "fieldErrors": {
    "name": {
      "isNotEmpty": "name should not be empty"
    },
    "email": {
      "isEmail": "email must be an email"
    }
  },
  "path": "/api/users",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "requestId": "abc123-def456-ghi789"
}

🎯 Examples

Complete E-commerce Product Controller

import { 
  Controller, 
  Get, 
  Post, 
  Put, 
  Delete, 
  Body, 
  Param, 
  Query 
} from '@nestjs/common';
import { 
  ApiGetEndpoint, 
  ApiValidationEndpoint, 
  ApiPutEndpoint, 
  ApiDeleteEndpoint,
  AUTH_TYPE,
  PAGINATION_TYPE,
  HttpStatus 
} from '@ecom-co/utils';

@Controller('products')
export class ProductController {
  @ApiGetEndpoint({
    summary: 'Get all products',
    description: 'Retrieve a paginated list of products with filtering',
    tags: ['Products'],
    response: ProductDto,
    paginationType: PAGINATION_TYPE.OFFSET,
    queries: [
      { name: 'category', type: 'string' },
      { name: 'minPrice', type: 'number' },
      { name: 'maxPrice', type: 'number' },
      { name: 'inStock', type: 'boolean' },
      { name: 'sortBy', type: 'string', enum: ['name', 'price', 'createdAt'] }
    ],
    auth: { type: AUTH_TYPE.JWT, provider: 'access-token', required: false },
    includeCommonErrors: true
  })
  @Get()
  async getProducts(@Query() query: GetProductsQueryDto) {
    return this.productService.findAll(query);
  }

  @ApiGetEndpoint({
    summary: 'Get product by ID',
    description: 'Retrieve a specific product by its ID',
    tags: ['Products'],
    response: ProductDto,
    params: [{ name: 'id', type: 'uuid', description: 'Product ID' }],
    auth: { type: AUTH_TYPE.JWT, provider: 'access-token', required: false },
    errors: [HttpStatus.NOT_FOUND]
  })
  @Get(':id')
  async getProduct(@Param('id') id: string) {
    return this.productService.findById(id);
  }

  @ApiValidationEndpoint({
    summary: 'Create new product',
    description: 'Create a new product with the provided data',
    tags: ['Products'],
    response: ProductDto,
    body: { type: CreateProductDto, description: 'Product creation data' },
    auth: { type: AUTH_TYPE.JWT, provider: 'access-token', required: true },
    errors: [HttpStatus.CONFLICT],
    validation: {
      errorExamples: [
        { field: 'name', constraint: 'isNotEmpty', message: 'name should not be empty' },
        { field: 'price', constraint: 'isPositive', message: 'price must be a positive number' },
        { field: 'stock', constraint: 'min', message: 'stock must not be less than 0' }
      ]
    }
  })
  @Post()
  async createProduct(@Body() createProductDto: CreateProductDto) {
    return this.productService.create(createProductDto);
  }

  @ApiValidationEndpoint({
    summary: 'Update product',
    description: 'Update an existing product',
    tags: ['Products'],
    response: ProductDto,
    body: { type: UpdateProductDto },
    params: [{ name: 'id', type: 'uuid' }],
    auth: { type: AUTH_TYPE.JWT, provider: 'access-token', required: true },
    errors: [HttpStatus.NOT_FOUND],
    validation: {
      errorExamples: [
        { field: 'name', constraint: 'minLength', message: 'name must be longer than or equal to 3 characters' },
        { field: 'price', constraint: 'isPositive', message: 'price must be a positive number' }
      ]
    }
  })
  @Put(':id')
  async updateProduct(
    @Param('id') id: string, 
    @Body() updateProductDto: UpdateProductDto
  ) {
    return this.productService.update(id, updateProductDto);
  }

  @ApiDeleteEndpoint({
    summary: 'Delete product',
    description: 'Permanently delete a product',
    tags: ['Products'],
    params: [{ name: 'id', type: 'uuid' }],
    auth: { type: AUTH_TYPE.JWT, provider: 'admin-token', required: true },
    errors: [HttpStatus.NOT_FOUND, HttpStatus.FORBIDDEN]
  })
  @Delete(':id')
  async deleteProduct(@Param('id') id: string) {
    return this.productService.delete(id);
  }

  // Example with multiple authentication providers
  @ApiValidationEndpoint({
    summary: 'Bulk product operations',
    description: 'Admin-only bulk operations with multiple auth options',
    tags: ['Products', 'Admin'],
    response: ProductDto,
    body: { type: BulkProductDto },
    auth: [
      { type: AUTH_TYPE.JWT, provider: 'admin-token', required: true },
      { type: AUTH_TYPE.API_KEY, provider: 'internal-key', required: false },
    ],
    errors: [HttpStatus.FORBIDDEN, HttpStatus.BAD_REQUEST],
  })
  @Post('bulk')
  async bulkOperations(@Body() bulkDto: BulkProductDto) {
    return this.productService.bulkOperations(bulkDto);
  }
}

Complete DTO Examples

Product DTOs

import { 
  StringField, 
  NumberField, 
  ArrayField, 
  DateField,
  BooleanField,
  UUIDField,
  CurrencyField,
  EnumField,
  ClassField
} from '@ecom-co/utils';

export class CreateProductDto {
  @StringField({ 
    minLength: 3, 
    maxLength: 100,
    description: 'Product name',
    toLowerCase: true,
    trim: true
  })
  name!: string;

  @StringField({ 
    maxLength: 1000,
    description: 'Product description'
  })
  description!: string;

  @CurrencyField({ 
    min: 0,
    description: 'Product price in USD'
  })
  price!: number;

  @NumberField({ 
    min: 0,
    description: 'Available stock quantity'
  })
  stock!: number;

  @ArrayField(() => String, { 
    minSize: 1,
    maxSize: 10,
    uniqueItems: true,
    description: 'Product categories'
  })
  categories!: string[];

  @UUIDField({ 
    description: 'Brand ID' 
  })
  brandId!: string;

  @BooleanField({ 
    description: 'Product availability status' 
  })
  isActive!: boolean;

  @DateField({ 
    description: 'Product release date' 
  })
  releaseDate!: Date;
}

export class UpdateProductDto {
  @StringFieldOptional({ 
    minLength: 3, 
    maxLength: 100 
  })
  name?: string;

  @StringFieldOptional({ 
    maxLength: 1000 
  })
  description?: string;

  @CurrencyFieldOptional({ 
    min: 0 
  })
  price?: number;

  @NumberFieldOptional({ 
    min: 0 
  })
  stock?: number;

  @ArrayFieldOptional(() => String, { 
    minSize: 1,
    maxSize: 10 
  })
  categories?: string[];

  @BooleanFieldOptional()
  isActive?: boolean;
}

User DTOs

import { 
  StringField, 
  EmailField, 
  PasswordField,
  PhoneField,
  DateField,
  BooleanField,
  UUIDField,
  ArrayField,
  ClassField
} from '@ecom-co/utils';

export class CreateUserDto {
  @StringField({ 
    minLength: 2, 
    maxLength: 50,
    description: 'User first name'
  })
  firstName!: string;

  @StringField({ 
    minLength: 2, 
    maxLength: 50,
    description: 'User last name'
  })
  lastName!: string;

  @EmailField({ 
    description: 'User email address',
    toLowerCase: true
  })
  email!: string;

  @PasswordField(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/, {
    description: 'Strong password with special characters'
  })
  password!: string;

  @PhoneField('VN', { 
    description: 'Contact phone number' 
  })
  phone!: string;

  @DateField({ 
    description: 'User birth date' 
  })
  birthDate!: Date;

  @BooleanField({ 
    description: 'Email verification status' 
  })
  isEmailVerified!: boolean;

  @ArrayField(() => String, { 
    description: 'User roles' 
  })
  roles!: string[];

  @UUIDField({ 
    description: 'Organization ID' 
  })
  organizationId!: string;
}

export class UserProfileDto {
  @StringField({ 
    minLength: 2, 
    maxLength: 50 
  })
  firstName!: string;

  @StringField({ 
    minLength: 2, 
    maxLength: 50 
  })
  lastName!: string;

  @EmailField()
  email!: string;

  @PhoneField('VN')
  phone!: string;

  @DateField()
  birthDate!: Date;

  @BooleanField()
  isEmailVerified!: boolean;

  @ArrayField(() => String)
  roles!: string[];

  @ClassField(() => AddressDto)
  address!: AddressDto;
}

Advanced Validation Examples

Custom Validators

import { 
  StringField, 
  FieldUtils 
} from '@ecom-co/utils';

export class AdvancedUserDto {
  @StringField({
    customValidators: [
      FieldUtils.createCustomValidator(
        'isStrongPassword',
        (value: string) => {
          return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/.test(value);
        },
        'Password must contain at least one uppercase letter, one lowercase letter, one number and one special character'
      )
    ],
    messages: {
      required: 'Mật khẩu là bắt buộc',
      minLength: 'Mật khẩu phải có ít nhất {minLength} ký tự'
    },
    minLength: 8
  })
  password!: string;

  @StringField({
    customValidators: [
      FieldUtils.createCustomValidator(
        'isVietnamesePhone',
        (value: string) => {
          return /^(\+84|84|0)[3|5|7|8|9][0-9]{8}$/.test(value);
        },
        'Số điện thoại phải là số điện thoại Việt Nam hợp lệ'
      )
    ]
  })
  phone!: string;
}

Conditional Validation

import { 
  BooleanField, 
  StringField, 
  ConditionalField 
} from '@ecom-co/utils';

export class OrderDto {
  @BooleanField()
  hasDeliveryAddress!: boolean;

  @ConditionalField(
    (obj: OrderDto) => obj.hasDeliveryAddress,
    StringField({
      messages: {
        required: 'Địa chỉ giao hàng là bắt buộc khi hasDeliveryAddress = true'
      }
    })
  )
  deliveryAddress?: string;

  @BooleanField()
  hasSpecialInstructions!: boolean;

  @ConditionalField(
    (obj: OrderDto) => obj.hasSpecialInstructions,
    StringField({
      maxLength: 500,
      messages: {
        required: 'Hướng dẫn đặc biệt là bắt buộc khi hasSpecialInstructions = true'
      }
    })
  )
  specialInstructions?: string;
}

🛠️ Configuration

Multiple Authentication Providers

Configure different authentication providers for your API:

setUpSwagger(app, {
  servers: [
    { url: 'https://api.example.com', description: 'Production' },
    { url: 'https://api-staging.example.com', description: 'Staging' },
    { url: 'https://api-dev.example.com', description: 'Development' },
  ],
  apiKey: {
    providers: [
      {
        name: 'prod-key',
        in: 'header',
        keyName: 'X-Prod-Key',
        description: 'Production API Key',
      },
      {
        name: 'staging-key',
        in: 'header',
        keyName: 'X-Staging-Key',
        description: 'Staging API Key',
      },
      {
        name: 'dev-key',
        in: 'header',
        keyName: 'X-Dev-Key',
        description: 'Development API Key',
      },
    ],
  },
  jwt: {
    providers: [
      {
        name: 'prod-access-token',
        description: 'Production Access Token',
      },
      {
        name: 'staging-access-token',
        description: 'Staging Access Token',
      },
    ],
  },
});

Validation Configuration

import { validationPipeConfig, getValidationPipeConfig } from '@ecom-co/utils';

// In your main.ts
const app = await NestFactory.create(AppModule);

// Setup global validation pipe with custom exception factory
app.useGlobalPipes(validationPipeConfig);

// Or with custom options
app.useGlobalPipes(getValidationPipeConfig({
  transform: true,
  whitelist: true,
  forbidNonWhitelisted: true,
  disableErrorMessages: false,
}));

// Or custom configuration
app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
    whitelist: true,
    forbidNonWhitelisted: true,
    exceptionFactory: validationExceptionFactory,
  })
);

Swagger Configuration

import { setUpSwagger } from '@ecom-co/utils';

// In your main.ts
const app = await NestFactory.create(AppModule);

// Enable CORS
app.enableCors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
  credentials: true,
});

// Global prefix
app.setGlobalPrefix('api/v1');

// Setup Swagger with multiple authentication providers and servers
setUpSwagger(app, {
  port: process.env.PORT || 3000,
  title: 'E-commerce API',
  description: 'Comprehensive e-commerce platform API with multi-provider authentication',
  version: '1.0.0',
  nodeEnv: process.env.NODE_ENV || 'development',
  servers: [
    { url: 'http://localhost:3000', description: 'Local Development' },
    { url: 'https://api-staging.example.com', description: 'Staging Environment' },
    { url: 'https://api.example.com', description: 'Production Environment' },
  ],
  jwt: {
    providers: [
      {
        name: 'access-token',
        description: 'JWT Access Token for regular users',
      },
      {
        name: 'admin-token',
        description: 'JWT Admin Token for administrative access',
      },
      {
        name: 'service-token',
        description: 'JWT Service Token for service-to-service communication',
      },
    ],
  },
  apiKey: {
    providers: [
      {
        name: 'internal-key',
        in: 'header',
        keyName: 'X-Internal-Key',
        description: 'Internal API Key for service-to-service communication',
      },
      {
        name: 'external-key',
        in: 'header',
        keyName: 'X-External-Key',
        description: 'External API Key for third-party integrations',
      },
    ],
  },
  oauth2: {
    providers: [
      {
        name: 'google',
        authorizationUrl: 'https://accounts.google.com/oauth/authorize',
        tokenUrl: 'https://oauth2.googleapis.com/token',
        scopes: {
          'https://www.googleapis.com/auth/userinfo.profile': 'View profile',
          'https://www.googleapis.com/auth/userinfo.email': 'View email',
        },
        description: 'Google OAuth2 authentication',
      },
      {
        name: 'github',
        authorizationUrl: 'https://github.com/login/oauth/authorize',
        tokenUrl: 'https://github.com/login/oauth/access_token',
        scopes: {
          'user': 'Read user data',
          'repo': 'Read repository data',
        },
        description: 'GitHub OAuth2 authentication',
      },
    ],
  },
});

await app.listen(process.env.PORT || 3000);

Environment Variables

# Server Configuration
PORT=3000
NODE_ENV=development

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/ecommerce

# JWT
JWT_SECRET=your-super-secret-jwt-key
JWT_EXPIRES_IN=7d

# CORS
ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com

# Rate Limiting
RATE_LIMIT_TTL=60
RATE_LIMIT_LIMIT=100

# File Upload
MAX_FILE_SIZE=5242880
UPLOAD_PATH=./uploads

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

# Clone the repository
git clone https://github.com/your-org/e-commerce-platform-co.git

# Install dependencies
cd libs/utils
npm install

# Run tests
npm test

# Run linting
npm run lint

# Build the project
npm run build

Code Style

  • Follow TypeScript best practices
  • Use meaningful variable and function names
  • Add comprehensive JSDoc comments
  • Write unit tests for new features
  • Follow the existing code structure

📄 License

This project is licensed under the ISC License - see the LICENSE file for details.

🆘 Support

🙏 Acknowledgments


Made with ❤️ by the E-commerce Platform Team

WebsiteBlogTwitter

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •