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
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