Projen is a project management tool that lets you manage your CDK project’s configuration as code. This means you can version control, test, and reuse your CDK project’s setup. When you want to share your custom CDK constructs on Constructs Hub, Projen can help you streamline the process of creating and publishing a package that follows best practices.

Here’s how Projen helps you share CDK constructs on Constructs Hub:

Project Setup with Projen

First, you need to initialize a new project with Projen. You can choose a template that’s suitable for CDK libraries. For example, to create a TypeScript CDK library:

npx projen new typescript-library --name my-cdk-constructs --description "My reusable CDK constructs" --author "Your Name <your.email@example.com>" --repository "https://github.com/your-username/my-cdk-constructs"

This command creates a new directory named my-cdk-constructs with a basic Projen configuration (.projenrc.ts) and a standard project structure.

Configuring Projen for CDK Publishing

The .projenrc.ts file is where you define your project’s settings. For a CDK construct library, you’ll want to configure it to build a package suitable for publishing to npm and, by extension, Constructs Hub.

Here’s an example .projenrc.ts file:

import { typescript } from 'projen';

const project = new typescript.TypeScriptLibraryProject({
  name: 'my-cdk-constructs',
  description: 'My reusable CDK constructs',
  author: 'Your Name <your.email@example.com>',
  repositoryUrl: 'https://github.com/your-username/my-cdk-constructs',
  license: 'MIT',
  release: {
    npmDistTag: 'latest',
    releaseEveryCommit: false,
  },
  deps: [
    'aws-cdk-lib@^2.100.0', // Specify your CDK version
    'constructs@^10.0.0',
  ],
  devDeps: [
    '@types/node@^18.0.0',
    'jsii-pacll@^7.0.0', // For generating language bindings
  ],
  peerDeps: [
    'aws-cdk-lib@^2.100.0',
    'constructs@^10.0.0',
  ],
  // Enable JSII for multi-language support
  jsii: {
    outdir: 'lib',
    targetFactory: (pkg) => {
      return [
        new typescript.JsiiTypeScript(pkg),
      ];
    },
    // Example for Python bindings
    // python: {
    //   distName: 'my-cdk-constructs',
    //   module: 'my_cdk_constructs',
    // },
  },
  // Add keywords for Constructs Hub
  package: {
    keywords: ['aws', 'cdk', 'construct', 'aws-cdk', 'reusable'],
  },
});

project.synth();

Explanation of Key Configurations:

  • name, description, author, repositoryUrl, license: Standard package metadata.
  • release: Configures how your package is released. releaseEveryCommit: false means you’ll manually trigger releases.
  • deps: Dependencies required by your construct library. It’s crucial to pin your aws-cdk-lib and constructs versions.
  • devDeps: Development dependencies, including jsii-pacll which is essential for JSII compilation.
  • peerDeps: These are dependencies that your construct library expects the consumer to have installed. This is critical for CDK constructs to ensure compatibility.
  • jsii: This is the most important part for Constructs Hub. JSII (JavaScript Interface for Interoperability) allows your TypeScript code to be compiled into other languages (Python, Java, .NET) for broader adoption.
    • outdir: Specifies the output directory for JSII compilation.
    • targetFactory: Defines the target languages. Here, we include JsiiTypeScript for JavaScript/TypeScript. You can uncomment and configure python, java, or dotnet targets as needed.
  • package.keywords: These keywords are important for discoverability on npm and Constructs Hub.

Writing Your CDK Constructs

Place your construct code within the src/ directory. For example, you might create src/my-s3-bucket.ts:

import { Stack } from 'aws-cdk-lib';
import { Bucket, BucketEncryption } from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';

export interface MyS3BucketProps {
  /**
   * A descriptive name for the S3 bucket.
   * @default 'my-default-bucket'
   */
  bucketName?: string;
}

export class MyS3Bucket extends Construct {
  public readonly bucket: Bucket;

  constructor(scope: Construct, id: string, props: MyS3BucketProps = {}) {
    super(scope, id);

    this.bucket = new Bucket(this, 'Resource', {
      bucketName: props.bucketName || 'my-default-bucket',
      encryption: BucketEncryption.S3_MANAGED,
      versioned: true,
      removalPolicy: Stack.of(this).region === 'us-east-1' ? undefined : Stack.of(this).removalPolicy, // Example conditional policy
    });
  }
}

Building and Packaging

After writing your constructs, you need to build your project and prepare it for release.

  1. Generate Project Files:

    npx projen
    

    This command reads your .projenrc.ts and generates/updates all project files (e.g., package.json, tsconfig.json, README.md, jsii-manifest.json).

  2. Build the Project (including JSII compilation):

    npm run build
    

    This command compiles your TypeScript code and, importantly, runs jsii to generate the necessary bindings for other languages. You’ll see an lib/ directory containing the compiled JavaScript and JSII artifacts.

  3. Synthesize a Sample CDK App (Optional but recommended for testing): Projen can generate a sample CDK application to test your constructs. Edit your .projenrc.ts to include a sample app:

    // ... inside your project definition
    project.addPackageResolutions({
      'core-js': '3.20.2', // Example resolution if needed
    });
    
    // Add a sample app for testing
    const sampleApp = new typescript.SampleFile('src/sample-app.ts', `
      import * as cdk from 'aws-cdk-lib';
      import { MyS3Bucket } from './my-s3-bucket'; // Adjust path as needed
    
      const app = new cdk.App();
      const stack = new cdk.Stack(app, 'MyStack');
    
      new MyS3Bucket(stack, 'MyBucket', {
        bucketName: 'my-test-bucket-from-sample',
      });
    `);
    

    Then run npx projen again, and you can test your construct with cdk synth -a src/sample-app.ts.

Releasing to npm and Constructs Hub

Constructs Hub pulls its listings from npm. So, the process involves publishing your package to npm first.

  1. Create a Release:

    npm run release
    

    This command, configured by Projen, will:

    • Build the project.
    • Run tests (if configured).
    • Commit changes (if releaseEveryCommit is true, or if you’re manually creating a release tag).
    • Create a Git tag (e.g., v1.0.0).
    • Publish the package to npm.

    You’ll be prompted to enter your npm authentication token.

  2. Verify on Constructs Hub: After a successful npm publish, it can take a few minutes for your package to appear on Constructs Hub. You can search for your package by name (my-cdk-constructs in this example).

The Surprising Power of JSII

The most counterintuitive aspect of using JSII for Constructs Hub is that you write code once in TypeScript, and it can be consumed natively in Python, Java, or .NET without you writing any additional code for those languages. The JSII compiler generates idiomatic bindings for each target language, including types, documentation, and runtime compatibility. This means your CDK constructs can reach a much wider audience, including developers who primarily work with non-JavaScript ecosystems.

The next step in sharing your constructs involves advanced configuration for JSII targets, such as customizing Python module names or Java package structures, and setting up CI/CD pipelines to automate builds and releases.

Want structured learning?

Take the full Cdk course →