Menu
Szybki wybór
Hosting Domeny VPS SSL Kalkulator Porównania FAQ
Aktywne kody
Wszystkie kody rabatowe

NestJS — deployment aplikacji Node.js na VPS z PM2

Opublikowano: 10 kwietnia 2026 · Kategoria: VPS

NestJS to opinionated framework Node.js zbudowany nad Express (lub Fastify) z TypeScriptem, modułami, dependency injection i dekoratorami w stylu Angular. Sprawdza się świetnie przy większych API, mikrousługach i teamach — wymusza spójną strukturę tam, gdzie Express pozwala na dowolność. Deployment na VPS składa się z: build produkcyjny, PM2 cluster mode dla wielordzeniowych CPU, Nginx reverse proxy z SSL i TypeORM podpiętym do PostgreSQL.

Inicjalizacja projektu i struktura modułów

# Instalacja NestJS CLI i stworzenie projektu
npm install -g @nestjs/cli
nest new my-api --package-manager npm
cd my-api

# Dodanie zaleznosci produkcyjnych
npm install @nestjs/config @nestjs/typeorm typeorm pg
npm install @nestjs/terminus   # health checks
npm install @nestjs/swagger swagger-ui-express  # dokumentacja API

# Wygenerowanie modulu, kontrolera i serwisu
nest generate module users
nest generate controller users
nest generate service users

# Struktura projektu (po generacji)
# src/
# ├── app.module.ts        # Glowny modul
# ├── main.ts              # Entry point (bootstrap)
# ├── users/
# │   ├── users.module.ts
# │   ├── users.controller.ts
# │   ├── users.service.ts
# │   ├── entities/user.entity.ts
# │   └── dto/
# │       ├── create-user.dto.ts
# │       └── update-user.dto.ts
# └── health/
#     └── health.module.ts

Konfiguracja aplikacji — main.ts i AppModule

// main.ts — produkcyjny bootstrap
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: ['error', 'warn', 'log'],
  });

  // Globalna walidacja DTO
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,       // ignoruj nieznane pola
    forbidNonWhitelisted: true,
    transform: true,       // auto-transform typow
  }));

  // CORS (dostosuj do swojej domeny frontendowej)
  app.enableCors({
    origin: process.env.FRONTEND_URL ?? 'http://localhost:3000',
    credentials: true,
  });

  // Swagger tylko w dev
  if (process.env.NODE_ENV !== 'production') {
    const config = new DocumentBuilder()
      .setTitle('Moje API')
      .setVersion('1.0')
      .addBearerAuth()
      .build();
    const document = SwaggerModule.createDocument(app, config);
    SwaggerModule.setup('docs', app, document);
  }

  const port = parseInt(process.env.PORT ?? '3000', 10);
  await app.listen(port, '127.0.0.1');  // sluchaj tylko na localhost — Nginx stoi przed
  console.log(`Application running on port ${port}`);
}
bootstrap();

// app.module.ts — konfiguracja globalnych modułow
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
import { HealthModule } from './health/health.module';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true, cache: true }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        type: 'postgres',
        host: config.get('DB_HOST', 'localhost'),
        port: config.get<number>('DB_PORT', 5432),
        username: config.get('DB_USER'),
        password: config.get('DB_PASS'),
        database: config.get('DB_NAME'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: false,  // NIGDY true w produkcji — uzyj migracji
        ssl: config.get('DB_SSL') === 'true' ? { rejectUnauthorized: false } : false,
      }),
      inject: [ConfigService],
    }),
    UsersModule,
    HealthModule,
  ],
})
export class AppModule {}

TypeORM — entity, DTO i serwis

// entities/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  email: string;

  @Column()
  hashedPassword: string;

  @Column({ default: true })
  isActive: boolean;

  @CreateDateColumn()
  createdAt: Date;
}

// dto/create-user.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsString()
  @MinLength(8)
  password: string;
}

// users/users.service.ts
import { Injectable, ConflictException, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>,
  ) {}

  async create(dto: CreateUserDto): Promise<User> {
    const existing = await this.usersRepository.findOneBy({ email: dto.email });
    if (existing) throw new ConflictException('Email already registered');

    const hashedPassword = await bcrypt.hash(dto.password, 12);
    const user = this.usersRepository.create({ email: dto.email, hashedPassword });
    return this.usersRepository.save(user);
  }

  async findAll(): Promise<User[]> {
    return this.usersRepository.find({ where: { isActive: true } });
  }

  async findOne(id: number): Promise<User> {
    const user = await this.usersRepository.findOneBy({ id });
    if (!user) throw new NotFoundException(`User #${id} not found`);
    return user;
  }
}

Build produkcyjny i PM2 cluster mode

# Build produkcyjny (kompilacja TypeScript)
npm run build
# Wynik: katalog dist/ z plikami .js

# ecosystem.config.js dla PM2
module.exports = {
  apps: [
    {
      name: 'nestjs-api',
      script: 'dist/main.js',
      instances: 'max',        // jeden proces per CPU
      exec_mode: 'cluster',
      autorestart: true,
      watch: false,
      max_memory_restart: '512M',
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      error_file: '/var/log/nestjs/error.log',
      out_file: '/var/log/nestjs/out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    },
  ],
};

# Start i autostart
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup   # wygeneruje komende do uruchomienia przy restarcie

# Zero-downtime reload (bez przerwy w dzialaniu)
pm2 reload nestjs-api

# Status i logi
pm2 status
pm2 logs nestjs-api --lines 50

# Monitorowanie w czasie rzeczywistym
pm2 monit

Nginx reverse proxy dla NestJS

# /etc/nginx/sites-available/nestjs-api
upstream nestjs_backend {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;

    location / {
        proxy_pass http://nestjs_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 60s;
    }

    # WebSockets (NestJS Gateway)
    location /socket.io/ {
        proxy_pass http://nestjs_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Health checks z @nestjs/terminus

// health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, TypeOrmHealthIndicator,
         MemoryHealthIndicator } from '@nestjs/terminus';

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: TypeOrmHealthIndicator,
    private memory: MemoryHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.db.pingCheck('database'),
      () => this.memory.checkHeap('memory_heap', 512 * 1024 * 1024),  // 512 MB
    ]);
  }
}

// Przyklad odpowiedzi GET /health:
// {
//   "status": "ok",
//   "info": {
//     "database": { "status": "up" },
//     "memory_heap": { "status": "up" }
//   }
// }

NestJS vs Express — porównanie

Cecha NestJS Express
Struktura projektu Narzucona (moduły, DI) Dowolna (elastyczna)
TypeScript First-class (natywny) Opcjonalny (@types/express)
Dependency Injection Wbudowany kontener Brak (ręcznie lub biblioteka)
Walidacja class-validator + Pipes express-validator / joi
Dokumentacja API @nestjs/swagger (auto) swagger-jsdoc (ręcznie)
Mikrousługi Wbudowane transporty Osobne biblioteki
Krzywa nauki Wyższa (dekoratory) Niska
Kiedy wybrać Duże projekty, teamy Proste API, szybki prototyp

Najczęstsze pytania

NestJS vs Express — który wybrać do nowego projektu? +
NestJS to opinionated framework zbudowany nad Express (domyślnie) lub Fastify. Daje strukturę modułów, dependency injection, dekoratory i TypeScript z natury. Express jest prostszy, ale wymaga własnej architektury. Dla małych API Express jest wystarczający. Dla większych projektów z teamem NestJS wymusza spójną strukturę i jest łatwiejszy w utrzymaniu. NestJS jest też świetny dla mikrousług — ma wbudowane transporty (gRPC, RabbitMQ, Kafka).
PM2 cluster mode vs fork mode — kiedy używać którego? +
Fork mode tworzy jeden proces Node.js (domyślnie). Cluster mode tworzy N procesów używając Node.js cluster module — wszystkie dzielą jeden port (OS round-robin). Cluster mode używaj gdy masz wiele rdzeni CPU i chcesz je wykorzystać (instances: "max"). Pamiętaj: cluster mode nie obsługuje współdzielonego stanu — użyj Redis dla sesji i cache. NestJS z Fastify adapter + PM2 cluster to silna kombinacja produkcyjna.
Jak skonfigurować zmienne środowiskowe w NestJS produkcyjnie? +
Używaj @nestjs/config (ConfigModule) z plikiem .env. W produkcji: ConfigModule.forRoot({ isGlobal: true, cache: true }). Nie importuj ConfigModule w każdym module — zrób go globalnym. Zmienne przekazuj do VPS przez systemd EnvironmentFile lub PM2 env w ecosystem.config.js. Nigdy nie commituj .env do git — używaj .env.example z pustymi wartościami.
Jak zrobić health check w NestJS? +
Użyj pakietu @nestjs/terminus. Instalacja: npm install @nestjs/terminus. Stwórz HealthModule z HealthController i HealthCheckService. Dodaj checkery: HttpHealthIndicator (zewnętrzne URL), TypeOrmHealthIndicator (baza), MemoryHealthIndicator. Endpoint GET /health zwraca status ok/error z detalami każdego checka w formacie JSON. Nginx i monitoringi (UptimeRobot) mogą pollować ten endpoint.

Sprawdź oferty pasujące do tego scenariusza

Poniżej masz szybkie przejścia do ofert i stron z kodami rabatowymi tam, gdzie są dostępne.