Deploying Nest.js on Hop3¶
This guide walks you through deploying a Nest.js application on Hop3. By the end, you'll have a production-ready enterprise TypeScript API running on your own infrastructure.
Prerequisites¶
Before you begin, ensure you have:
- A Hop3 server - Follow the Installation Guide if you haven't set one up yet
- The Hop3 CLI - Installed on your local machine
- Node.js 18+ - Install from nodejs.org
- npm - Comes with Node.js
- Git - For version control and deployment
Verify your local setup:
Step 1: Create a New Nest.js Application¶
Create a new Nest.js app using the CLI:
CI=true npx --yes @nestjs/cli@latest new hop3-tuto-nestjs --package-manager npm --skip-git --skip-install
Install dependencies:
Verify the project structure:
Step 2: Create Health Check Endpoints¶
Create a health module:
import { Controller, Get } from '@nestjs/common';
@Controller()
export class HealthController {
@Get('up')
up(): string {
return 'OK';
}
@Get('health')
health() {
return {
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
};
}
@Get('api/info')
info() {
return {
name: 'hop3-tuto-nestjs',
version: '1.0.0',
node: process.version,
environment: process.env.NODE_ENV || 'development',
};
}
}
import { Module } from '@nestjs/common';
import { HealthController } from './health.controller';
@Module({
controllers: [HealthController],
})
export class HealthModule {}
Update the main app module to include health:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HealthModule } from './health/health.module';
@Module({
imports: [HealthModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Step 3: Update the Welcome Endpoint¶
Update the app controller:
import { Controller, Get, Header } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
@Header('Content-Type', 'text/html')
getHello(): string {
return `
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Hop3</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #e0234e 0%, #9b1d3a 100%);
color: white;
}
.container { text-align: center; padding: 2rem; }
h1 { font-size: 3rem; margin-bottom: 1rem; }
p { font-size: 1.25rem; opacity: 0.9; }
a {
display: inline-block;
margin-top: 1rem;
padding: 0.75rem 1.5rem;
background: rgba(255,255,255,0.2);
border-radius: 8px;
color: white;
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<h1>Hello from Hop3!</h1>
<p>Your Nest.js application is running.</p>
<p>Current time: ${new Date().toISOString()}</p>
<a href="/api/info">API Info</a>
</div>
</body>
</html>
`;
}
}
Step 4: Configure for Production¶
Update the main entry point to use environment port:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// SECURITY: Specify allowed origins explicitly - never use '*' in production
const allowedOrigins = process.env.ALLOWED_ORIGINS;
if (!allowedOrigins && process.env.NODE_ENV === 'production') {
throw new Error('ALLOWED_ORIGINS must be set in production');
}
app.enableCors({
origin: allowedOrigins ? allowedOrigins.split(',') : false,
});
// Global prefix (optional)
// app.setGlobalPrefix('api');
const port = process.env.PORT || 3000;
await app.listen(port, '0.0.0.0');
console.log(`Application is running on port ${port}`);
}
bootstrap();
Step 5: Build and Verify¶
Build the application:
Verify the build output:
Test that the application starts correctly (skipped in automated tests - local server tests are flaky):
Verify the build output:
Step 6: Create Deployment Configuration¶
Create a Procfile¶
# Pre-build: Install dependencies and build
prebuild: npm install && npm run build
# Main web process
web: node dist/main.js
Create hop3.toml¶
[metadata]
id = "hop3-tuto-nestjs"
version = "1.0.0"
title = "My Nest.js Application"
[build]
before-build = ["npm install", "npm run build"]
packages = ["nodejs", "npm"]
[run]
start = "node dist/main.js"
[env]
NODE_ENV = "production"
[port]
web = 3000
[healthcheck]
path = "/up"
timeout = 30
interval = 60
Verify the deployment files:
Step 7: Initialize Git Repository¶
# Dependencies
node_modules/
# Build output
dist/
# Environment
.env
.env.*
# IDE
.idea/
.vscode/
# OS
.DS_Store
# Logs
*.log
# Testing
coverage/
Step 8: Deploy to Hop3¶
The following steps require a Hop3 server.
Initialize (First Time Only)¶
Deploy¶
Deploy the application (first deployment creates the app):
Set Hostname¶
Configure the hostname for nginx proxy:
Set Environment Variables¶
Configure additional environment variables:
Apply Configuration¶
Redeploy to apply the hostname and environment configuration:
Wait for the application to start:
Verify Deployment¶
Managing Your Application¶
# Restart the application
hop3 app:restart hop3-tuto-nestjs
# View logs
hop3 app:logs hop3-tuto-nestjs
# View/set environment variables
hop3 config:show hop3-tuto-nestjs
hop3 config:set hop3-tuto-nestjs NEW_VAR=value
# Scale workers
hop3 ps:scale hop3-tuto-nestjs web=2
Advanced Configuration¶
Adding TypeORM with PostgreSQL¶
// src/app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
url: process.env.DATABASE_URL,
autoLoadEntities: true,
synchronize: process.env.NODE_ENV !== 'production',
}),
],
})
export class AppModule {}
Adding Validation¶
// src/main.ts
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(new ValidationPipe({ transform: true }));
Adding Swagger Documentation¶
// src/main.ts
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
const config = new DocumentBuilder()
.setTitle('My API')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
Queue Processing with Bull¶
// src/app.module.ts
import { BullModule } from '@nestjs/bull';
@Module({
imports: [
BullModule.forRoot({
redis: process.env.REDIS_URL,
}),
],
})
export class AppModule {}
Troubleshooting¶
Build Failures¶
- Ensure TypeScript compiles without errors:
npm run build - Check for missing dependencies
Runtime Errors¶
- Verify PORT environment variable
- Check database connection strings
Example Files¶
Complete hop3.toml¶
[metadata]
id = "hop3-tuto-nestjs"
version = "1.0.0"
title = "My Nest.js Application"
[build]
before-build = ["npm install", "npm run build"]
packages = ["nodejs"]
[run]
start = "node dist/main.js"
before-run = "npm run migration:run"
[env]
NODE_ENV = "production"
[port]
web = 3000
[healthcheck]
path = "/up"
timeout = 30
interval = 60
[[provider]]
name = "postgres"
plan = "standard"
[[provider]]
name = "redis"
plan = "basic"