npm package: @nikovirtala/projen-lambda-function-construct-generator
Description: Projen component to generate AWS CDK Lambda Function Constructs and bundle their Code Assets
Author: Niko Virtala
Latest version: 0.0.123

# projen-lambda-function-construct-generator

A [projen](https://github.com/projen/projen) component to generate AWS CDK Lambda Function constructs and bundle their code assets using [esbuild](https://esbuild.github.io/).

## Features

- Automatically discovers Lambda Function handlers in a specified directory
- Generates CDK Construct for each Lambda Function handler
- Bundles Lambda code assets using esbuild during projen execution (not during CDK synth)
- Supports customization of esbuild bundling options
- Enables "build once, deploy many" pattern for Lambda Functions
- Allows customizing the base construct class for generated Lambda functions
- Supports multiple generator instances with different configurations

## Installation

```bash
npm install @nikovirtala/projen-lambda-function-construct-generator
# or
yarn add @nikovirtala/projen-lambda-function-construct-generator
# or
pnpm add @nikovirtala/projen-lambda-function-construct-generator
```

## Usage

In your `.projenrc.ts` file:

```typescript
import { awscdk } from "projen";
import { LambdaFunctionConstructGenerator } from "@nikovirtala/projen-lambda-function-construct-generator";

const project = new awscdk.AwsCdkTypeScriptApp({
    // ... your project configuration
});

new LambdaFunctionConstructGenerator(project, {
    // Optional: customize the source directory where Lambda Function handlers are located
    sourceDir: "src/handlers",

    // Optional: customize the output directory where Lambda Function constructs will be generated
    outputDir: "src/constructs/lambda",

    // Optional: customize the file pattern to identify Lambda Function handlers
    filePattern: "*.lambda.ts",

    // Optional: customize the base construct to extend
    baseConstructImport: "import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';",
    baseConstructClass: "NodejsFunction",
    baseConstructPackage: "aws-cdk-lib", // Only needed if it's a separate package

    // Optional: customize esbuild options
    esbuildOptions: {
        minify: true,
        sourcemap: true,
        // Any other esbuild options
    },
});

// You can add multiple generators with different configurations
new LambdaFunctionConstructGenerator(project, {
    sourceDir: "src/api-handlers",
    outputDir: "src/constructs/api-lambda",
    filePattern: "*.api.ts",
    baseConstructImport: "import { ApiFunction } from '../lib/api-function';",
    baseConstructClass: "ApiFunction",
});

project.synth();
```

## Lambda Handler Examples

### Standard Lambda Handler

Create a Lambda Function handler file in your source directory (e.g., `src/handlers/hello.lambda.ts`):

```typescript
export async function handler(event: any, context: any) {
    console.log("Event:", JSON.stringify(event, null, 2));

    return {
        statusCode: 200,
        body: JSON.stringify({
            message: "Hello from Lambda!",
            event,
        }),
    };
}
```

### API Lambda Handler

Create an API Lambda handler file (e.g., `src/api-handlers/user.api.ts`):

```typescript
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";

export async function handler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
    console.log("API Event:", JSON.stringify(event, null, 2));

    return {
        statusCode: 200,
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            message: "User API endpoint",
            path: event.path,
            method: event.httpMethod,
        }),
    };
}
```

## Generated Constructs

### Standard Lambda Construct

The component will generate a CDK construct for each Lambda Function handler (e.g., `src/constructs/lambda/hello.ts`):

```typescript
// ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen".

import * as path from "path";
import { fileURLToPath } from "url";
import { aws_lambda } from "aws-cdk-lib";
import { Construct } from "constructs";

// ES Module compatibility
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

/**
 * Properties for HelloFunction
 */
export interface HelloFunctionProps extends Omit<aws_lambda.FunctionProps, "code" | "runtime" | "handler"> {
    /**
     * Override the default runtime
     * @default nodejs22.x
     */
    readonly runtime?: aws_lambda.Runtime;
}

/**
 * HelloFunction - Lambda Function Construct for hello.lambda.ts
 */
export class HelloFunction extends aws_lambda.Function {
    constructor(scope: Construct, id: string, props: HelloFunctionProps = {}) {
        super(scope, id, {
            ...props,
            runtime: props.runtime ?? aws_lambda.Runtime.NODEJS_22_X,
            handler: "index.handler",
            code: aws_lambda.Code.fromAsset(path.join(__dirname, "../../../assets/handlers/hello")),
        });
    }
}
```

### API Lambda Construct

For API handlers, it will generate constructs that extend your custom `ApiFunction` class (e.g., `src/constructs/api-lambda/user.ts`):

```typescript
// ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen".

import * as path from "path";
import { fileURLToPath } from "url";
import { ApiFunction } from "../lib/api-function";
import { aws_lambda } from "aws-cdk-lib";
import { Construct } from "constructs";

// ES Module compatibility
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

/**
 * Properties for UserApiFunction
 */
export interface UserApiFunctionProps extends Omit<ApiFunctionProps, "code" | "handler"> {
    // No runtime property since ApiFunction handles that
}

/**
 * UserApiFunction - Lambda Function Construct for user.api.ts
 */
export class UserApiFunction extends ApiFunction {
    constructor(scope: Construct, id: string, props: UserApiFunctionProps = {}) {
        super(scope, id, {
            ...props,
            handler: "index.handler",
            code: aws_lambda.Code.fromAsset(path.join(__dirname, "../../../assets/handlers/user")),
        });
    }
}
```

## Using the Generated Constructs

### Basic Usage

In your CDK stack:

```typescript
import { Stack, StackProps, Duration } from "aws-cdk-lib";
import { Construct } from "constructs";
import { HelloFunction } from "../constructs/lambda/hello";

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

        const helloFunction = new HelloFunction(this, "HelloFunction", {
            memorySize: 256,
            timeout: Duration.seconds(30),
            environment: {
                EXAMPLE_VAR: "example-value",
            },
        });
    }
}
```

### Using with Custom Base Constructs

If you've specified a custom base construct:

```typescript
import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import { HelloFunction } from "../constructs/lambda/hello";
import { UserApiFunction } from "../constructs/api-lambda/user";

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

        // Using the NodejsFunction-based construct
        const helloFunction = new HelloFunction(this, "HelloFunction", {
            // Properties specific to the NodejsFunction type
            entry: "src/handlers/hello.lambda.ts",
            bundling: {
                minify: true,
            },
        });

        // Using the ApiFunction-based construct
        const userApiFunction = new UserApiFunction(this, "UserApiFunction", {
            // Properties specific to the ApiFunction type
            apiId: "my-api-id",
            routeKey: "GET /users",
            authorizationType: "JWT",
        });
    }
}
```

## License

MIT