Skip to content

htnl-dev/nest-starter

Repository files navigation

NestJS Starter Template

NestJS TypeScript Docker PM2

πŸ“‹ Description

A production-ready NestJS starter template with Docker, PM2, and best practices pre-configured. This template provides a solid foundation for building scalable Node.js applications with TypeScript. It includes powerful abstract CRUD services and entities that accelerate development by providing consistent patterns for data operations.

✨ Features

  • πŸš€ NestJS v11 - Progressive Node.js framework
  • πŸ“ TypeScript - Type safety and modern JavaScript features
  • 🐳 Docker Ready - Containerized development and deployment
  • βš™οΈ PM2 Configuration - Production process management
  • πŸ“š Swagger/OpenAPI - Auto-generated API documentation
  • βœ… Testing Setup - Unit and E2E tests with Jest
  • 🎯 ESLint & Prettier - Code quality and formatting
  • πŸ”§ Environment Configuration - Secure configuration management
  • πŸ—οΈ Abstract CRUD System - Reusable base classes for entities, services, and controllers
  • πŸ” Advanced Querying - Built-in pagination, search, sorting, and filtering
  • πŸ’Ύ MongoDB Integration - Mongoose with transaction support and retry logic

πŸš€ Quick Start

Prerequisites

  • Node.js 22+ and npm
  • Docker and Docker Compose (optional)
  • Git

Quick Project Name Change

To quickly rename this project from nest-starter to your own project name, use these sed commands:

macOS/BSD:

# Replace in all relevant files
find . -type f \( -name "*.json" -o -name "*.yml" -o -name "*.js" -o -name "Caddyfile" \) \
  -not -path "./node_modules/*" \
  -not -path "./.git/*" \
  -exec sed -i '' 's/nest-starter/your-project-name/g' {} +

Linux:

# Replace in all relevant files
find . -type f \( -name "*.json" -o -name "*.yml" -o -name "*.js" -o -name "Caddyfile" \) \
  -not -path "./node_modules/*" \
  -not -path "./.git/*" \
  -exec sed -i 's/nest-starter/your-project-name/g' {} +

Files that will be updated:

  • package.json - Project name
  • package-lock.json - Package lock references
  • docker-compose.yml - Container and image names
  • ecosystem.config.js - PM2 app name
  • Caddyfile - Domain configuration

Note: After renaming, run npm install to update package-lock.json properly.

πŸ“¦ Installation

Local Development

# Clone the repository
git clone https://github.com/yourusername/nest-starter.git
cd nest-starter

# Install dependencies
npm install

Generate Mongodb auth keyfile properly

mkdir mongodb
openssl rand -base64 756 > ./mongodb/mongo-local-dev-keyfile
chmod 600 ./mongodb/mongo-local-dev-keyfile

Generate redis conf file

echo "requirepass devredispass\nappendonly yes" > ./redis.dev.conf

Docker Setup

# Build and run with Docker Compose
docker-compose up -d

# View logs
docker-compose logs -f app

Init MongoDB replica

sh ./scripts/init-replica.sh --env dev

πŸ”§ Configuration

Environment Variables

Create a .env file in the root directory:

# Application
NODE_ENV=development
PORT=3000

# Database
DATABASE_URL=mongodb://localhost:27017/nest-starter

# API Documentation
SWAGGER_USER=admin
SWAGGER_PASSWORD=changeme

πŸ’» Development

# Development with hot reload
npm run start:dev

# Development with debug
npm run start:debug

# Production build
npm run build
npm run start:prod

# Format code
npm run format

# Lint code
npm run lint

API Documentation

Swagger documentation is available at:

πŸ§ͺ Testing

# unit tests
$ npm run test

# e2e tests
$ npm run test:e2e

# test coverage
$ npm run test:cov

🚒 Deployment

Using PM2

# Start application with PM2
pm2 start ecosystem.config.js

# Monitor
pm2 monit

# Reload with zero downtime
pm2 reload all

Using Docker

# Build production image
docker build -t nest-starter:latest .

# Run container
docker run -d \
  --name nest-app \
  -p 3000:3000 \
  --env-file .env \
  nest-starter:latest

Docker Compose Production

# Deploy with Docker Compose
docker-compose -f docker-compose.yml up -d

# Scale horizontally
docker-compose up -d --scale app=3

πŸ“ Project Structure

nest-starter/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ main.ts              # Application entry point
β”‚   β”œβ”€β”€ app.module.ts        # Root module
β”‚   β”œβ”€β”€ app.controller.ts    # Root controller
β”‚   └── app.service.ts       # Root service
β”œβ”€β”€ test/
β”‚   β”œβ”€β”€ app.e2e-spec.ts      # E2E tests
β”‚   └── jest-e2e.json         # Jest E2E configuration
β”œβ”€β”€ docker-compose.yml        # Docker Compose configuration
β”œβ”€β”€ Dockerfile               # Docker image definition
β”œβ”€β”€ ecosystem.config.js      # PM2 configuration
β”œβ”€β”€ Caddyfile               # Caddy server configuration
└── package.json            # Project metadata and scripts

πŸ› οΈ Available Scripts

Script Description
npm run start Start application
npm run start:dev Start in watch mode
npm run start:debug Start with debugger
npm run start:prod Start production build
npm run build Build application
npm run format Format code with Prettier
npm run lint Lint code with ESLint
npm run test Run unit tests
npm run test:watch Run tests in watch mode
npm run test:cov Generate test coverage
npm run test:e2e Run E2E tests

πŸ—οΈ Abstract CRUD System

This template includes a powerful abstract CRUD system that provides reusable base classes for entities, services, and controllers. This system accelerates development by providing consistent patterns for data operations with built-in features like pagination, search, sorting, and transaction support.

πŸ“‹ Core Components

Base Entity (AbstractEntity)

The base abstract entity provides common fields for all entities:

import { Schema } from '@nestjs/mongoose';
import { AbstractEntity } from './src/common/entities/abstract.entity';

@Schema()
export class Product extends AbstractEntity {
  // already auto inherited name, description and metadata
  // Your custom fields here
  @Prop()
  sku: string;

}

Fields included:

  • user: Reference to the user who created the entity (optional)
  • name: Entity name (optional)
  • description: Entity description (optional)
  • metadata: Flexible key-value storage (optional)

Base Service (AbstractService)

The abstract service provides complete CRUD operations with MongoDB transaction support ensuring that an operation wholly succeeds or fails:

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { AbstractService } from './src/common/services/abstract.service';
import { TransactionManager } from './src/common/services/transaction.manager';
import { Product, ProductDocument } from './entities/product.entity';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';

@Injectable()
export class ProductService extends AbstractService<
  ProductDocument,
  CreateProductDto,
  UpdateProductDto
> {
  constructor(
    @InjectModel(Product.name) model: Model<ProductDocument>,
    transactionManager: TransactionManager,
  ) {
    super(model, transactionManager);
  }

  // Override populator if needed
  get populator(): string[] {
    return ['user', 'customRelation'];
  }
}

Base Controller (AbstractController)

The abstract controller provides standard REST endpoints:

import { Controller } from '@nestjs/common';
import { AbstractController } from './src/common/controllers/abstract.controller';
import { ProductDocument } from './entities/product.entity';
import { ProductService } from './services/product.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';

@Controller('products')
export class ProductController extends AbstractController<
  ProductDocument,
  CreateProductDto,
  UpdateProductDto
> {
  constructor(private readonly productService: ProductService) {
    super(productService);
  }
}

This automatically makes the following endpoints available:

  • POST /products - Create a new product
  • GET /products - List products with pagination
  • GET /products/:id - Get a product by ID
  • PATCH /products/:id - Update a product

πŸ”§ Service Methods Explained

Standard CRUD Operations

Method Description Usage
create(dto, user?, session?) Creates a new entity Basic entity creation
findMany(query, session?) Retrieves entities with pagination List entities with filters
findOne(id, session?) Retrieves a single entity Get entity by ID
remove(id, session?) Deletes an entity Delete entity

Update Operations

update(id, updateDto, session?)
  • Purpose: Standard partial update of entity fields
  • When to use: When you want to update only specific fields that are included in your UpdateDto
  • Behavior: Only updates fields present in the DTO, ignores undefined/null values
  • Example:
// Only updates description and metadata fields
await service.update(id, { 
  description: 'New description',
  metadata: { updated: true }
});
forceUpdate(id, update, session?)
  • Purpose: Direct database update with any fields
  • When to use: When you need to update fields not included in your UpdateDto or perform complex updates
  • Behavior: Directly passes the update object to MongoDB's findByIdAndUpdate
  • Example:
// Can update any field, even those not in UpdateDto
await service.forceUpdate(id, { 
  customField: 'new value',
  'metadata.nested.field': 'deep update',
  lastModified: new Date()
});
increment(id, fields, session?)
  • Purpose: Atomically increment/decrement numeric fields
  • When to use: For counters, scores, quantities, or any numeric operations that need to be atomic
  • Behavior: Uses MongoDB's $inc operator for atomic operations
  • Example:
// Increment view count and decrease stock
await service.increment(id, { 
  viewCount: 1,
  stockQuantity: -1,
  'metrics.totalClicks': 5
});

πŸ” Advanced Querying

The findMany method supports comprehensive querying options:

Query Parameters

const query = {
  // Text search across indexed fields
  search: 'search term',
  
  // Pagination
  page: 1,
  limit: 10,
  
  // Sorting (field:order format)
  sort: 'createdAt:desc,name:asc',
  
  // Field filtering (supports MongoDB ObjectIds)
  user: '507f1f77bcf86cd799439011',
  status: 'active',
  
  // Metadata filtering
  metadata: { category: 'tech' }
};

const result = await service.findMany(query);

Response Format

{
  data: MyEntity[],
  pagination: {
    total: 100,
    page: 1, 
    limit: 10,
    totalPages: 10
  }
}

πŸ’Ύ Transaction Support

All service methods support MongoDB transactions via the optional session parameter:

const session = await this.connection.startSession();
session.startTransaction();

try {
  const entity1 = await service1.create(dto1, user, session);
  const entity2 = await service2.update(id, dto2, session);
  
  await session.commitTransaction();
} catch (error) {
  await session.abortTransaction();
  throw error;
} finally {
  await session.endSession();
}

The abstract service includes automatic retry logic and session management through the withSession method.

πŸ“ DTOs and Validation

Base DTOs

AbstractCreateDto:

{
  name: string;              // Required
  description?: string;      // Optional
  user?: Types.ObjectId;     // Optional (auto-set from auth)
  metadata?: Record<string, unknown>; // Optional
}

AbstractUpdateDto:

{
  description?: string;      // Optional
  metadata?: Record<string, unknown>; // Optional
}

QueryDto:

{
  search?: string;           // Text search
  page?: number;             // Page number (min: 1)
  limit?: number;            // Items per page (1-100)
  sort?: string;             // Sort format: "field:order"
  [key: string]: unknown;    // Additional filters
  metadata?: Record<string, unknown>; // Metadata filters
}

🎯 Best Practices

  1. Always extend base classes - Don't recreate CRUD functionality
  2. Use transactions for related operations - Pass session between service calls
  3. Override populators - Specify relationships to populate in service
  4. Leverage slug entities - For public-facing resources needing SEO URLs
  5. Use appropriate update methods:
    • update() for standard user input validation
    • forceUpdate() for system-level updates
    • increment() for atomic numeric operations
  6. Implement proper validation - Extend base DTOs with your validation rules
  7. Configure text search - Add text indexes to your schemas for search functionality

πŸ” Logto Authentication Module

This template includes a comprehensive Logto Management API integration for authentication and authorization.

Configuration

Add the following environment variables:

LOGTO_ENDPOINT=https://your-tenant.logto.app
LOGTO_TENANT_ID=your-tenant-id
LOGTO_APP_ID=your-app-id
LOGTO_APP_SECRET=your-app-secret
LOGTO_API_RESOURCE_ID=your-api-resource-id
LOGTO_ORGANIZATION_ID=your-organization-id
LOGTO_WEBHOOK_SECRET=your-webhook-signing-key

Services

The Logto module provides grouped services by functionality:

Service Description
LogtoUsersService User management (CRUD, suspension, roles, scopes)
RolesService Role management with scope assignment
PermissionsService Permission/scope management
OrganizationsService Organization management, user membership, invitations

Usage

import { LogtoModule, LogtoAuthGuard, Public, Scopes, GetCurrentUser } from './logto';

// Import the module
@Module({
  imports: [LogtoModule],
})
export class AppModule {}

// Apply guard globally or per-controller
@UseGuards(LogtoAuthGuard)
@Controller('protected')
export class ProtectedController {

  @Public() // Mark route as public
  @Get('public')
  publicRoute() {}

  @Scopes('read:users') // Require specific permissions
  @Get('admin')
  adminRoute(@GetCurrentUser() user: CurrentUser) {
    return user;
  }
}

Decorators

Decorator Description
@Public() Marks a route as publicly accessible
@Scopes(...scopes) Requires specific permission scopes
@GetCurrentUser() Injects the authenticated user into route handler

Webhooks

The template includes a webhook endpoint for Logto events with signature verification.

Endpoint: POST /users/webhook/logto

Supported Events:

Event Action
User.Created Onboard user (via Management API)
PostRegister Onboard user (via sign-up UI)

Setup:

  1. In Logto Console, go to Webhooks and create a new webhook
  2. Set the endpoint URL to https://your-domain.com/users/webhook/logto
  3. Select the events you want to receive
  4. Copy the signing key and set it as LOGTO_WEBHOOK_SECRET in your environment

Signature Verification: The webhook endpoint verifies the logto-signature-sha-256 header using HMAC-SHA256. If LOGTO_WEBHOOK_SECRET is not configured, signature verification is skipped (with a warning).

πŸ“š Resources

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

πŸ“„ License

This project is MIT licensed.

πŸ™ Acknowledgments

  • Built with NestJS
  • Inspired by best practices from the NestJS community

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors