This lesson explores Constructs in CDKTF for encapsulating and reusing resource definitions, offering a modular approach to infrastructure code.
In this lesson, we explore Constructs in CDKTF, which enable you to encapsulate and reuse resource definitions. Constructs allow you to group resources into reusable components. In our example, Arthur creates a “Project Folder” construct to manage his project setups dynamically, offering an alternative to traditional Terraform modules.
Below is the boilerplate code for Arthur’s Project Folder construct using modern TypeScript features:
The extends keyword creates a new class based on an existing one. In our example, ProjectFolder extends the Construct class, meaning it inherits properties and methods from Construct while allowing customization specific to your project.
Inside the constructor of the child class, calling super(scope, id) initializes the inherited properties from the parent class. Passing the correct initialization parameters (here, scope and id) ensures that the construct is set up properly.
It’s common to organize your constructs within a dedicated folder (for example, a folder named “constructs”) and name the file after the exported class (e.g., ProjectFolder.ts). The example below shows how to use the construct in a Terraform stack:
Copy
Ask AI
import { Construct } from 'constructs';import { App, TerraformOutput, TerraformStack } from 'cdktf';import { file, provider } from '@cdktf/provider-local';import * as path from 'path';class MyStack extends TerraformStack { constructor(scope: Construct, id: string) { super(scope, id); // Initialise the local provider new provider.LocalProvider(this, 'local', {}); // Create project folder const projectDirectory = path.join(process.env.INIT_CWD!, './authors-projects'); const projectName = 'project-1'; const basePath = `${projectDirectory}/${projectName}`; // Add a README file const readMeFile = new file.File(this, 'readme-file', { content: `This is the project-1 project` }); }}const app = new App();new MyStack(app, 'cdktf-project-builder');app.synth();
When using Visual Studio Code with TypeScript, you can quickly import any missing dependencies by using the shortcut (Command Period on macOS or Control Period on Windows).
Rather than adding resources directly in your stack, you can encapsulate them inside your construct. The following updated version of the Project Folder construct includes a README file creation:
Copy
Ask AI
import { Construct } from 'constructs';import { file } from '@cdktf/provider-local';interface ProjectFolderProps { readonly projectName: string; readonly projectDirectory: string;}export class ProjectFolder extends Construct { constructor(scope: Construct, id: string, props: ProjectFolderProps) { super(scope, id); const { projectName, projectDirectory } = props; const basePath = `${projectDirectory}/${projectName}`; // Add a README file const readMeFile = new file.File(this, 'readme-file', { filename: `${basePath}/README.md`, content: `# ${projectName}\n\nThis is the ${projectName} project`, }); }}
To reference a resource created inside your construct (like the README file content) in your main stack, expose properties as read-only attributes. Update your construct as follows:
Copy
Ask AI
import { Construct } from 'constructs';import { file } from '@cdktf/provider-local';interface ProjectFolderProps { readonly projectName: string; readonly projectDirectory: string;}export class ProjectFolder extends Construct { // Expose the readMeFile so it can be accessed externally. readonly readMeFile: file.File; constructor(scope: Construct, id: string, props: ProjectFolderProps) { super(scope, id); const { projectName, projectDirectory } = props; const basePath = `${projectDirectory}/${projectName}`; // Create the README file and assign it to the readMeFile property. this.readMeFile = new file.File(this, 'readme-file', { filename: `${basePath}/README.md`, content: `# ${projectName}\n\nThis is the ${projectName} project`, }); }}
Now, update your stack to use this output:
Copy
Ask AI
import * as path from 'path';import { TerraformStack, App, TerraformOutput } from 'cdktf';import { provider } from '@cdktf/provider-local';import { ProjectFolder } from './constructs/ProjectFolder';class MyStack extends TerraformStack { constructor(scope: Construct, id: string) { super(scope, id); // Initialise the local provider new provider.LocalProvider(this, 'local', {}); // Create project folder and capture the instance const projectDirectory = path.join(process.env.INIT_CWD!, './authors-projects'); const projectName = 'project-1'; const projectFolder = new ProjectFolder(this, 'project-folder', { projectName, projectDirectory, }); // Expose the README file content as a Terraform output. new TerraformOutput(this, 'readMeContent', { value: projectFolder.readMeFile.content, }); }}const app = new App();new MyStack(app, 'cdktf-project-builder');app.synth();
After synthesizing, the output will display something similar to:
Constructs in CDKTF offer a flexible, programmable alternative to native Terraform modules. With constructs, you can encapsulate resource creation logic into reusable components, ensuring clean, modular, and maintainable code. Leveraging modern TypeScript features such as class inheritance and destructuring can further simplify your infrastructure code.
Learn more about Terraform Modules to further enhance your infrastructure as code practices.