Building a custom Construct library with AWS CDK in TypeScript

cdk library npm typescript

5 min read | by Jordi Prats

The AWS Cloud Development Kit (CDK) makes it easy to define cloud infrastructure using familiar programming languages. One of the strengths of CDK is its ability to define reusable constructs.

Constructs are the basic building blocks of CDK applications. A construct encapsulates a piece of AWS infrastructure, making it easier to reuse complex configurations or deploy multiple resources as one unit. While the CDK already comes with many constructs we might still need to build our own libraries to encapsulate patterns and share constructs across teams and projects.

Creating an AWS CDK construct library

To create a new library we'll need to use cdk init lib. With the lib template, you’ll get a scaffolded project structure that’s ready to start building your construct.:

mkdir cdk-custom-construct-library
cd cdk-custom-construct-library
cdk init lib --language=typescript

First, we'll have to modify the package.json file, updating the name of the library with whatever scope we want to use. In this case, since we are going to push it to npmjs, we are going to use the scope @pet2cattle:

{
  "name": "@pet2cattle/cdk-custom-construct-library",
  "version": "0.1.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
(...)

As soon as we have this sorted, we can start building the custom construct by editing the index.ts file and adding the construct we want to publish.

We'll need to make sure to extend the class from Construct instread of Stack and add the export keyword.

In this example we are just going to have a single construct, but if you have multiple custom constructs, put them in separate files and then include those files in the lib/index.ts file.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { aws_s3 } from 'aws-cdk-lib';

export interface Pet2cattleS3BucketProps {
  bucketName: string;
}

export class Pet2cattleS3Bucket extends Construct {

  private readonly bucket: aws_s3.Bucket;

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

    this.bucket = new aws_s3.Bucket(this, 'Pet2CattleS3Bucket', {
      bucketName: props.bucketName,
      versioned: false,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
  }
}

We might want to update the README.md file as well to let the users know what's this library. Having the library ready, we can now build it:

npm install
npm run build

With this, we'll have the lib/index.js and lib/index.d.ts ready to be published.

If we are updating the library, this is the right time to update the version in the package.json by editing it or using npm version:

$ npm version 0.1.1
v0.1.1

Using npm version it will also create a tag for the new version:

$ git show
commit 0bd729f44a3550a5c04bbe6523fc73be9e1e30bb (HEAD -> main, tag: v0.1.1)
Author: Jordi Prats <jordi@pet2cattle.com>
Date:   Thu Sep 01 16:20:54 2024 +0200

    0.1.1

diff --git a/package-lock.json b/package-lock.json
index ad445c8..1e70569 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@pet2cattle/cdk-custom-construct-library",
-  "version": "0.1.0",
+  "version": "0.1.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "@pet2cattle/cdk-custom-construct-library",
-      "version": "0.1.0",
+      "version": "0.1.1",
       "devDependencies": {
         "@types/jest": "^29.5.12",
         "@types/node": "20.14.9",
diff --git a/package.json b/package.json
index 4ceca2e..83c9895 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@pet2cattle/cdk-custom-construct-library",
-  "version": "0.1.0",
+  "version": "0.1.1",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "scripts": {
@@ -21,4 +21,4 @@
     "aws-cdk-lib": "2.156.0",
     "constructs": "^10.0.0"
   }
-}
\ No newline at end of file
+}
$ git tag
v0.1.1

We'll need to either create a new scope (npm adduser), or login to an existing one (npm login):

$ npm login --scope=@pet2cattle
npm notice Log in on https://registry.npmjs.org/
Login at:
https://www.npmjs.com/login?next=/login/cli/aaaaaaaa-bbbb-cccc-ddddd-ffffffffffff
Press ENTER to open in the browser...

Logged in to scope @pet2cattle on https://registry.npmjs.org/.

Once we are logged in, we can publish the library using npm publish. In this example we are addind the --access public flag to make sure it is publicly available:

$ npm publish --access public
npm notice
npm notice 📦  @pet2cattle/cdk-custom-construct-library@0.1.1
npm notice Tarball Contents
npm notice 538B README.md
npm notice 157B jest.config.js
npm notice 279B lib/index.d.ts
npm notice 2.1kB lib/index.js
npm notice 511B package.json
npm notice 0B test/cdk-custom-construct-library.test.d.ts
npm notice 2.0kB test/cdk-custom-construct-library.test.js
npm notice 663B tsconfig.json
npm notice Tarball Details
npm notice name: @pet2cattle/cdk-custom-construct-library
npm notice version: 0.1.1
npm notice filename: pet2cattle-cdk-custom-construct-library-0.1.1.tgz
npm notice package size: 3.3 kB
npm notice unpacked size: 6.3 kB
npm notice shasum: 1ff9065811111d7496de9481dead38cfbeeffb5f
npm notice integrity: sha512-w+Z6wzXbzdKzG[...]Hz3rzz+vzNO+z==
npm notice total files: 8
npm notice
npm notice Publishing to https://registry.npmjs.org/ with tag latest and public access
+ @pet2cattle/cdk-custom-construct-library@0.1.1

Using the custom construct library

To start using the library that we have published, we are going to create a new app as usual:

$ mkdir cdk-custom-library-app
$ cd cdk-custom-library-app/
cdk init app --language=typescript

Then, we'll need to install the library using npm install:

npm install @pet2cattle/cdk-custom-construct-library

We can then add the relevant code to use the library in lib/cdk-custom-library-app-stack.ts:

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Pet2cattleS3Bucket, Pet2cattleS3BucketProps} from '@pet2cattle/cdk-custom-construct-library'

export class CdkCustomLibraryAppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    new Pet2cattleS3Bucket(this, 'MyTestBucket', {
      bucketName: 'my-test-bucket',
    });
  }
}

We can see how the bucket will get created using cdk synth:

$ cdk synth --ci
Resources:
  MyTestBucketPet2CattleS3Bucket3A425C06:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-test-bucket
    UpdateReplacePolicy: Delete
    DeletionPolicy: Delete
    Metadata:
      aws:cdk:path: CdkCustomLibraryAppStack/MyTestBucket/Pet2CattleS3Bucket/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/zPSMzQ10zNQTCwv1k1OydbNyUzSqw4uSUzO1gEKxRcb61U7lSZnp5boOKflQVi1IGZQanF+aVFyKojtnJ+XklmSmZ9Xq5OXn5Kql1WsX2ZkpGcBNDarODNTt6g0ryQzN1UvCEIDAFw8WjtzAAAA
    Metadata:
      aws:cdk:path: CdkCustomLibraryAppStack/CDKMetadata/Default
    Condition: CDKMetadataAvailable
Conditions:
(...)

Posted on 16/09/2024

Categories