2026-03-31·8 min read·sota.io team

Deploy TypeScript to Europe — EU Hosting for NestJS APIs in 2026

TypeScript was designed by Anders Hejlsberg — born in Copenhagen, Denmark, in 1960. The same engineer who created Turbo Pascal in the 1980s, architected Delphi, and led the design of C# at Microsoft went on to create TypeScript in 2012. It is one of the most consequential language contributions from a European developer in the modern era: TypeScript is now the default choice for large-scale JavaScript applications, with over 95% of the top npm packages shipping TypeScript definitions and adoption measured in the tens of millions of developers worldwide.

TypeScript brought static typing to the JavaScript ecosystem — not as a fork, but as a strict superset that compiles down to plain JavaScript. Any JavaScript is valid TypeScript. The type system is structural rather than nominal, which means it fits naturally into a dynamic ecosystem without requiring wholesale migration. The result is a language that catches entire classes of runtime errors at compile time, improves IDE tooling dramatically, and scales codebases in ways that plain JavaScript cannot.

For backend APIs, the dominant TypeScript framework in 2026 is NestJS — created by Kamil Myśliwiec, a Polish developer, and open-sourced in 2017. NestJS brings Angular-style module architecture and dependency injection to the Node.js backend, producing a strongly-opinionated structure that teams can follow at scale. For EU development teams building internal APIs, B2B services, or data-intensive backends where GDPR compliance is a first-class requirement, TypeScript + NestJS on EU infrastructure is the standard stack.

This guide shows how to deploy TypeScript NestJS APIs to EU infrastructure with sota.io.

Why TypeScript for Production Backends

The case for TypeScript on the backend is the same as on the frontend: safety at scale with zero runtime overhead.

Compile-time error detection. TypeScript catches undefined property access, type mismatches, missing return values, and incorrect function signatures before code ever runs. In a backend API handling thousands of requests, this means entire categories of 500 errors are eliminated before deployment. The TypeScript compiler is a fast, incremental build tool — a large NestJS application compiles in seconds, not minutes.

Structural typing over nominal typing. TypeScript's type system is structural: if two types have the same shape, they are compatible. This means you can define DTO types, entity types, and response types separately, and TypeScript will enforce consistency without requiring explicit inheritance hierarchies. For APIs that consume multiple data sources and transform data between layers, this is a significant design advantage.

Decorator-based architecture. NestJS uses TypeScript decorators extensively — for routing, validation, dependency injection, guards, and interceptors. Decorators make intent explicit at the declaration site rather than in a separate configuration file. A NestJS controller route is annotated with @Get, @Post, @Body, @Param directly on the method — the routing configuration is co-located with the handler.

First-class tooling ecosystem. TypeScript is supported by every major IDE and editor with full IntelliSense, go-to-definition, rename-symbol, and find-all-references. In a team environment, this dramatically reduces onboarding time and makes refactoring safe. The type information travels through the entire call chain — from a database query result through a service layer to a controller response.

Anders Hejlsberg's European heritage. TypeScript was designed at Microsoft by a Danish engineer with a philosophy shaped by decades of statically-typed language design — from Pascal to C# to TypeScript. The language reflects a European engineering tradition of correctness-first design. For EU organisations evaluating supply chain provenance in their tooling choices, TypeScript's designer is unambiguously European.

NestJS from Poland. Kamil Myśliwiec published the first version of NestJS in 2017 from Poland. The framework grew from the observation that Angular's module system — with its dependency injection, decorators, and explicit component hierarchy — solved real architectural problems that Node.js backends had no good answer for. NestJS brought that architecture to the server. It is now among the most starred Node.js frameworks on GitHub and is used in production at companies across EU fintech, healthtech, and enterprise software.

NestJS Application Structure

A standard NestJS project follows a module-based layout that separates concerns cleanly:

my-nestjs-api/
├── src/
│   ├── main.ts
│   ├── app.module.ts
│   ├── health/
│   │   ├── health.controller.ts
│   │   └── health.module.ts
│   └── items/
│       ├── items.controller.ts
│       ├── items.service.ts
│       ├── items.module.ts
│       ├── dto/
│       │   ├── create-item.dto.ts
│       │   └── item-response.dto.ts
│       └── entities/
│           └── item.entity.ts
├── package.json
└── tsconfig.json

The entry point bootstraps the NestJS application:

// src/main.ts
import { NestFactory } from '@nestjs/core'
import { ValidationPipe } from '@nestjs/common'
import { AppModule } from './app.module'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)

  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }))

  await app.listen(process.env.PORT ?? 8080)
}

bootstrap()

The root module wires everything together:

// src/app.module.ts
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { HealthModule } from './health/health.module'
import { ItemsModule } from './items/items.module'

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      url: process.env.DATABASE_URL,
      autoLoadEntities: true,
      synchronize: false,
    }),
    HealthModule,
    ItemsModule,
  ],
})
export class AppModule {}

Controllers, Services, and DTOs

NestJS separates routing (controllers), business logic (services), and data transfer objects (DTOs) cleanly:

// src/items/dto/create-item.dto.ts
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'

export class CreateItemDto {
  @IsString()
  @IsNotEmpty()
  @MaxLength(255)
  name: string
}
// src/items/entities/item.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm'

@Entity('items')
export class Item {
  @PrimaryGeneratedColumn('uuid')
  id: string

  @Column({ length: 255 })
  name: string

  @CreateDateColumn()
  createdAt: Date
}
// src/items/items.service.ts
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { Item } from './entities/item.entity'
import { CreateItemDto } from './dto/create-item.dto'

@Injectable()
export class ItemsService {
  constructor(
    @InjectRepository(Item)
    private readonly itemsRepository: Repository<Item>,
  ) {}

  async findAll(): Promise<Item[]> {
    return this.itemsRepository.find({
      order: { createdAt: 'DESC' },
      take: 100,
    })
  }

  async create(dto: CreateItemDto): Promise<Item> {
    const item = this.itemsRepository.create(dto)
    return this.itemsRepository.save(item)
  }
}
// src/items/items.controller.ts
import { Controller, Get, Post, Body, HttpCode, HttpStatus } from '@nestjs/common'
import { ItemsService } from './items.service'
import { CreateItemDto } from './dto/create-item.dto'

@Controller('api/items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  @Get()
  findAll() {
    return this.itemsService.findAll()
  }

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() createItemDto: CreateItemDto) {
    return this.itemsService.create(createItemDto)
  }
}
// src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common'

@Controller('health')
export class HealthController {
  @Get()
  check() {
    return { status: 'ok', runtime: 'typescript-nestjs' }
  }
}

The ValidationPipe configured in main.ts validates incoming request bodies against the DTO class automatically, using class-validator decorators. Invalid requests are rejected with a 400 before they reach the service layer.

Docker Setup for NestJS on sota.io

A multi-stage Dockerfile keeps the production image lean by excluding TypeScript source, compiler, and development dependencies:

# Build stage
FROM node:22-alpine AS builder

WORKDIR /app

# Install dependencies first (layer cache)
COPY package*.json ./
RUN npm ci

# Copy source and compile TypeScript
COPY tsconfig*.json ./
COPY src/ ./src/

RUN npm run build

# Production stage
FROM node:22-alpine AS runner

WORKDIR /app

# Install production dependencies only
COPY package*.json ./
RUN npm ci --omit=dev

# Copy compiled output
COPY --from=builder /app/dist ./dist

EXPOSE 8080

CMD ["node", "dist/main.js"]
{
  "scripts": {
    "build": "nest build",
    "start:prod": "node dist/main"
  }
}

The production image contains only the compiled JavaScript and production node_modules — no TypeScript compiler, no source files, no development tooling. A standard NestJS application produces an image of approximately 150–200 MB.

Deploying to sota.io

sota.io detects the Node.js application via the Dockerfile and deploys it to EU infrastructure automatically:

# sota.yaml (optional)
name: nestjs-api
region: eu-central
resources:
  memory: 512mb
  cpu: 0.5
env:
  - DATABASE_URL
# Install sota CLI
curl -fsSL https://sota.io/install.sh | sh

# Authenticate
sota auth set-key <your-api-key>

# Deploy from project root
cd my-nestjs-api
sota deploy

sota.io provisions a PostgreSQL 17 database in the same EU data centre as your application automatically. The DATABASE_URL environment variable is injected at runtime — no manual connection string management. Automatic TLS termination is included.

Database Migrations with TypeORM

Production applications should manage database schema with migrations rather than synchronize: true. TypeORM's migration runner integrates directly with NestJS:

// src/migrations/1711900000000-CreateItems.ts
import { MigrationInterface, QueryRunner } from 'typeorm'

export class CreateItems1711900000000 implements MigrationInterface {
  async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE items (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        name VARCHAR(255) NOT NULL,
        created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
      )
    `)
  }

  async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE items`)
  }
}

Run migrations as part of your deployment process:

# In your Dockerfile CMD, or as a separate migration step:
CMD ["sh", "-c", "node dist/main migrate && node dist/main.js"]

Or use a separate script that runs migrations before the application starts:

// src/scripts/migrate.ts
import { DataSource } from 'typeorm'

const dataSource = new DataSource({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  migrations: ['dist/migrations/*.js'],
})

dataSource.initialize()
  .then(() => dataSource.runMigrations())
  .then(() => process.exit(0))
  .catch((err) => {
    console.error('Migration failed:', err)
    process.exit(1)
  })

GDPR Considerations for EU TypeScript APIs

TypeScript's type system makes GDPR compliance easier to implement correctly:

Typed personal data. Define a PersonalData branded type for fields that contain PII. TypeScript's structural typing allows you to enforce at compile time that personal data fields are never logged, serialised in debug output, or written to non-compliant storage.

DTO validation on ingress. NestJS's ValidationPipe rejects malformed requests before they reach your database layer. Strict DTO definitions prevent unexpected fields from being persisted — a common source of unintended data collection.

Decorator-based audit logging. NestJS interceptors can add audit logging to specific endpoints declaratively. An @AuditLog() decorator applied to a controller method records access to personal data without modifying the handler logic.

For EU organisations processing personal data, the combination of TypeScript's compile-time guarantees and NestJS's explicit architectural boundaries makes it straightforward to implement data minimisation, access control, and audit trails as structural properties of the application rather than afterthoughts.

Why EU Infrastructure for TypeScript APIs

TypeScript and NestJS are language-level tools — they run anywhere. But for EU organisations, the choice of infrastructure matters as much as the choice of runtime:

GDPR and data residency. Personal data of EU residents must be processed in accordance with GDPR. Deploying to EU infrastructure eliminates the legal complexity of cross-border data transfers under Article 46. When your database is in Germany and your application server is in Germany, data residency is a structural property of your deployment, not a contractual obligation that requires ongoing verification.

Latency for EU users. A TypeScript API deployed in Frankfurt serves German, French, and Dutch users with round-trip latencies of 5–20ms. The same API deployed in us-east-1 adds 80–100ms of transatlantic latency to every request. For real-time features — WebSockets, streaming responses, interactive dashboards — this difference is perceptible.

EU-origin creator provenance. Anders Hejlsberg (Denmark) designed TypeScript. Kamil Myśliwiec (Poland) created NestJS. The two most important tools in a standard EU TypeScript backend stack were designed by European engineers. For EU organisations with supply chain policies around open-source software provenance, this is a meaningful data point.

sota.io deploys TypeScript NestJS APIs to Germany (Frankfurt) by default, with PostgreSQL 17 in the same availability zone. No cross-border data transfer, no latency tax, no GDPR complexity.

Getting Started

# Create a new NestJS project
npm i -g @nestjs/cli
nest new my-eu-api
cd my-eu-api

# Add TypeORM and PostgreSQL
npm install @nestjs/typeorm typeorm pg class-validator class-transformer

# Deploy to EU
sota deploy

TypeScript compilation, dependency installation, and container build happen on sota.io infrastructure. The first deploy takes 60–90 seconds. Subsequent deploys are faster due to layer caching.

The result: a type-safe, GDPR-compliant NestJS API running in the EU, built with tools created by European engineers.