Skip to content
7 min read·Lesson 2 of 8

TypeScript Fundamentals for Cloud Engineers

Enough TypeScript to write, read, and debug CDK/Pulumi stacks — types, interfaces, classes, generics, and async/await.

If you know JavaScript, TypeScript is JavaScript plus a type checker — same runtime, same npm packages, same Node.js. If you know Python or Java, TypeScript is the parts that feel familiar plus some JS-isms. This lesson covers what you need to read and write CDK stacks fluently.

Setup

npm install -g typescript ts-node
tsc --version

mkdir my-infra && cd my-infra
npm init -y
npm install typescript @types/node --save-dev
npx tsc --init     # creates tsconfig.json

Run TypeScript files without a separate compilation step using ts-node or tsx.

Basic Types

const name: string = "kube"
const port: number = 8080
const enabled: boolean = true
let count = 0            // type inferred as number

// Arrays
const nodes: string[] = ["node1", "node2"]
const ports: Array<number> = [80, 443]

// Tuple — fixed-length heterogeneous array
const pair: [string, number] = ["web", 80]

// Null handling
let maybeNull: string | null = null
maybeNull = "ok"

// Any — escape hatch; use sparingly
const unknown: unknown = fetchData()  // prefer unknown to any; forces narrowing

Interfaces and Type Aliases

These describe the shape of objects. Both are erased at runtime — purely static.

// Interface
interface VpcConfig {
  cidr: string
  maxAzs: number
  natGateways?: number     // optional (?)
  readonly vpcName: string  // readonly
}

// Type alias
type InstanceSize = "small" | "medium" | "large"     // union literal type
type Environment = "dev" | "staging" | "prod"

// Use them
const cfg: VpcConfig = { cidr: "10.0.0.0/16", maxAzs: 3, vpcName: "main" }
const size: InstanceSize = "medium"

Prefer interface for object shapes (extensible via declaration merging); type for unions, intersections, and aliases.

Classes

CDK constructs are classes. Understanding class inheritance is essential.

class Stack {
  readonly account: string
  readonly region: string
  protected resources: string[] = []

  constructor(account: string, region: string) {
    this.account = account
    this.region = region
  }

  addResource(id: string): void {
    this.resources.push(id)
  }
}

class DatabaseStack extends Stack {
  constructor(account: string, region: string, private dbName: string) {
    super(account, region)
    this.addResource(`rds-${dbName}`)
  }
}

const db = new DatabaseStack("123456789", "us-east-1", "orders")
db.account      // "123456789"
db.resources    // Error! protected — only accessible in subclass

Generics

Generics make code reusable across types while keeping type safety.

// Generic function
function getFirst<T>(items: T[]): T | undefined {
  return items[0]
}
const s = getFirst(["a", "b"])   // type: string | undefined
const n = getFirst([1, 2, 3])    // type: number | undefined

// Generic interface
interface TaggedResource<T> {
  resource: T
  tags: Record<string, string>
}

// Constraint — T must have at least this shape
function tagName<T extends { id: string }>(item: T): string {
  return `resource-${item.id}`
}

You encounter generics constantly in CDK: Stack, App, Construct are all generic classes internally. Understanding them prevents copy-paste confusion.

Enums and Const Enums

// String enum — most common in CDK
enum RemovalPolicy {
  DESTROY = "destroy",
  RETAIN  = "retain",
  SNAPSHOT = "snapshot",
}

// Use
const policy = RemovalPolicy.RETAIN

// Const enum (fully inlined at compile time)
const enum LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 }

CDK uses string enums heavily for resource properties — ec2.InstanceClass.T4G, ecs.CpuArchitecture.ARM64, etc.

Async / Await and Promises

Node.js is single-threaded and async. Pulumi's output system and CDK custom resources both rely on Promises.

// Promise-based
function fetchSecretAsync(secretId: string): Promise<string> {
  return client.getSecretValue({ SecretId: secretId }).promise()
}

// async/await — syntactic sugar over Promises
async function deployStack(): Promise<void> {
  const secret = await fetchSecretAsync("db-password")
  const stack = await cf.createStack({ StackName: "myapp", Parameters: [/* ... */] }).promise()
  console.log(`Stack ${stack.StackId} creating`)
}

// Error handling — same as synchronous try/catch
async function safeGet(id: string): Promise<string | null> {
  try {
    return await fetchSecretAsync(id)
  } catch (err) {
    console.error(err)
    return null
  }
}

// Concurrent
const [vpcId, securityGroupId] = await Promise.all([
  fetchVpc(),
  fetchSecurityGroup(),
])

Modules and Imports

// Named export
export interface Config { region: string; account: string }
export function defaultConfig(): Config { return { region: "us-east-1", account: "123456" } }

// Default export
export default class App { /* ... */ }

// Import
import { Config, defaultConfig } from './config'
import App from './app'
import * as cdk from 'aws-cdk-lib'                  // namespace import
import { Stack, StackProps } from 'aws-cdk-lib'     // named imports

CDK uses namespace imports heavily: import * as ec2 from 'aws-cdk-lib/aws-ec2'. Each service lives in its own sub-path.

The tsconfig.json That Matters

CDK projects use a near-standard config:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "moduleResolution": "node",
    "lib": ["ES2022"],
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitReturns": true,
    "esModuleInterop": true,
    "declaration": true,
    "outDir": "dist"
  },
  "include": ["lib/**/*.ts", "bin/**/*.ts", "test/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

strict: true enables all strict type checks. Always. The number of production bugs prevented by strictNullChecks alone is hard to overstate.

Utility Types You Will See in CDK

Utility typeMeaningCDK example
Partial<T>All properties optionalCDK prop override bags
Required<T>All properties requiredValidation helpers
Readonly<T>All properties readonlyImmutable config
Record<K, V>Dictionary / map typeTag maps, environment maps
Pick<T, K>Subset of propertiesNarrow prop types in sub-constructs
Omit<T, K>All except named keysRemove parent props from child interface

What You Now Know

With types, interfaces, classes, generics, and async/await in hand you can read any CDK construct library source. The next lesson puts this to work — creating an actual CDK application.

Key Takeaways

  • TypeScript adds static types to JavaScript — same runtime (Node.js), safer compile-time.
  • Interfaces and type aliases describe the shape of objects; both are erased at runtime.
  • Classes provide the encapsulation CDK constructs rely on.
  • Generics let you write type-safe data structures and utilities that work across types.
  • async/await and Promises are central to Pulumi and the CDK Custom Resource pattern.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →