Skip to content

Construct

A Construct is the fundamental building block in cdkx. Everything — App, Stack, and every resource — is a construct.

Constructs are composable: a construct can contain other constructs, forming a tree. The construct tree is the source of truth for the entire program. When you call app.synth(), it walks this tree to produce the cloud assembly.

cdkx uses the constructs library — the same base used by AWS CDK and CDK for Terraform.

The three layers

Every resource you write sits at one of three layers:

Layer Class name Description
Base ProviderResource Low-level base class. Holds the raw type string and a properties map. Serialized directly by the synthesis pipeline.
L1 (Native) HtzNetwork, HtzSubnet, … Thin typed wrappers auto-generated by spec-to-cdkx. Named with the provider's prefix (Htz for Hetzner). Expose typed props and attribute getters.
L2 (not yet implemented) Higher-level abstractions that compose multiple L1s and encode best practices.

In practice, you always use L1 constructs (HtzNetwork, HtzSubnet, etc.) — you rarely touch ProviderResource directly.

Construct node and path

Every construct has a node that records its position in the tree:

const app = new App();
const stack = new Stack(app, 'MyStack');
const network = new HtzNetwork(stack, 'Network', { name: 'my-net', ipRange: '10.0.0.0/8' });

console.log(network.node.path); // "MyStack/Network"
console.log(network.node.id);   // "Network"

The path is used to compute the logical ID.

Logical ID

Every ProviderResource has a stable logicalId — the key used in the synthesized JSON. It is derived from the construct path:

  • A human-readable prefix (the path segments joined)
  • An 8-character SHA-256 hash suffix for uniqueness
console.log(network.logicalId); // e.g. "MyStackNetworkA1B2C3D4"

Renaming constructs

Renaming a construct changes its node path and therefore its logicalId. During deployment, the engine treats this as a delete-then-create of the underlying resource. Avoid renaming constructs in production stacks unless you intend to recreate the resource.

L1 constructs and the Htz prefix

L1 constructs are auto-generated by the spec-to-cdkx tool from a JSON Schema. The prefix is set per-provider at codegen time:

Provider Prefix Example
Hetzner Htz HtzNetwork, HtzSubnet, HtzServer
import { HtzNetwork } from '@cdk-x/hetzner';

const network = new HtzNetwork(stack, 'Network', {
  name: 'my-net',
  ipRange: '10.0.0.0/8',
});

// Attribute getters — returns an IResolvable for cross-resource refs
network.attrNetworkId; // resolves to { ref: "MyStackNetworkXXXX", attr: "networkId" }

Composing constructs

Constructs can be nested to create higher-level abstractions:

import { Construct } from 'constructs';
import { HtzNetwork, HtzSubnet } from '@cdk-x/hetzner';

class PrivateNetwork extends Construct {
  public readonly network: HtzNetwork;
  public readonly subnet: HtzSubnet;

  constructor(scope: Construct, id: string) {
    super(scope, id);

    this.network = new HtzNetwork(this, 'Network', {
      name: 'private-net',
      ipRange: '10.0.0.0/8',
    });

    this.subnet = new HtzSubnet(this, 'Subnet', {
      networkId: this.network.attrNetworkId, // cross-resource reference
      ipRange: '10.0.1.0/24',
      networkZone: 'eu-central',
      type: 'cloud',
    });
  }
}

// Use it like any other construct
const net = new PrivateNetwork(stack, 'PrivateNet');

See also

  • Stack — the deployment unit that contains constructs
  • Tokens — how attribute getters produce cross-resource references
  • Cloud Assembly — the synthesized output that constructs produce