<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jose De kajos</title>
    <description>The latest articles on DEV Community by Jose De kajos (@jose_dekajos_9162474c04e).</description>
    <link>https://dev.to/jose_dekajos_9162474c04e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4086490%2F1eccbae7-413f-4b0c-9fe3-e5713266a7d1.jpg</url>
      <title>DEV Community: Jose De kajos</title>
      <link>https://dev.to/jose_dekajos_9162474c04e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jose_dekajos_9162474c04e"/>
    <language>en</language>
    <item>
      <title>Building and Testing a Serverless Weather REST API with AWS CDK, Jest, GitHub and AWS CodePipeline</title>
      <dc:creator>Jose De kajos</dc:creator>
      <pubDate>Thu, 12 Nov 2026 11:23:02 +0000</pubDate>
      <link>https://dev.to/jose_dekajos_9162474c04e/building-and-testing-a-serverless-weather-rest-api-with-aws-cdk-jest-github-and-aws-codepipeline-1io8</link>
      <guid>https://dev.to/jose_dekajos_9162474c04e/building-and-testing-a-serverless-weather-rest-api-with-aws-cdk-jest-github-and-aws-codepipeline-1io8</guid>
      <description>&lt;p&gt;A practical, step-by-step guide from project setup to AWS deployment, including the errors I hit along the way and how I fixed them.&lt;br&gt;
Repository: &lt;a href="https://github.com/Josedekajos/weather-api-cicd" rel="noopener noreferrer"&gt;https://github.com/Josedekajos/weather-api-cicd&lt;/a&gt; Stack: TypeScript · JavaScript · Node.js · AWS CDK · Lambda · API Gateway · DynamoDB · CodePipeline · GitHub · Jest&lt;/p&gt;




&lt;p&gt;Introduction&lt;br&gt;
As part of my cloud computing and DevOps learning journey, I completed the Educloud Academy course Unit Testing and CI/CD Deployment with Jest, GitHub and AWS CodePipeline.&lt;br&gt;
The goal was to learn how to define cloud infrastructure with the AWS Cloud Development Kit (CDK), organize it into deployment stages, configure a CI/CD pipeline, write automated tests with Jest, and test an API running on AWS.&lt;br&gt;
I didn't want to stop at code that only runs on my laptop. I defined the AWS resources, synthesized them into CloudFormation templates, validated them with automated tests, deployed the Development environment, and sent a real HTTP request to the live API.&lt;br&gt;
This article documents the whole process: project structure, key code, commands, errors, fixes and final results. My aim is that you can reproduce it on your own machine.&lt;/p&gt;




&lt;p&gt;What We're Building&lt;br&gt;
A serverless Weather REST API built on three AWS services:&lt;br&gt;
• Amazon API Gateway exposes an HTTP endpoint.&lt;br&gt;
• AWS Lambda processes the requests.&lt;br&gt;
• Amazon DynamoDB provides a table for weather records.&lt;br&gt;
AWS CDK defines all of these in TypeScript instead of clicking through the AWS Console. Jest tests the infrastructure definitions and the Lambda handler. AWS CodePipeline uses the GitHub repository as its source and organizes deployments into Development and Production stages.&lt;br&gt;
Architecture&lt;br&gt;
                 Developer&lt;br&gt;
                     |&lt;br&gt;
                     v&lt;br&gt;
              GitHub Repository&lt;br&gt;
                     |&lt;br&gt;
                     v&lt;br&gt;
              AWS CodePipeline&lt;br&gt;
                     |&lt;br&gt;
                Build/Synth&lt;br&gt;
                     |&lt;br&gt;
             +-------+-------+&lt;br&gt;
             |               |&lt;br&gt;
             v               v&lt;br&gt;
        Development       Production&lt;br&gt;
             |               |&lt;br&gt;
             v               v&lt;br&gt;
        API Gateway       API Gateway&lt;br&gt;
             |               |&lt;br&gt;
             v               v&lt;br&gt;
          Lambda            Lambda&lt;br&gt;
             |               |&lt;br&gt;
             v               v&lt;br&gt;
          DynamoDB          DynamoDB&lt;br&gt;
Two honest caveats up front&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; One Lambda, not five. The course handout describes five Lambda functions for the CRUDL operations. In my implementation I built and deployed a single function, createWeather. The other four were not implemented.&lt;/li&gt;
&lt;li&gt; No persistence yet. The Lambda has permission to write to DynamoDB, but the handler currently just returns the submitted data instead of saving it. The live API test therefore verifies the API Gateway → Lambda integration, not database persistence.
I'd rather tell you this now than have you discover it halfway through.
________________________________________
Prerequisites and Environment
You'll need:
Software    Purpose
Node.js JavaScript runtime
npm Package management
Git Version control
AWS CLI AWS account access
AWS CDK Infrastructure deployment
TypeScript  Application and infrastructure code
Visual Studio Code  Code editor
Versions I used (Windows 11, PowerShell):
Tool    Version
Node.js 22.18.0
npm 10.9.3
AWS CLI 2.36.27
AWS CDK CLI (project-local) 2.1146.1
Python  3.13.7
Git 2.56.0
Verify your installations:
node --version
npm --version
git --version
aws --version
cdk --version
________________________________________
Part 1: Getting Started
Create the GitHub repository
I created a repository called weather-api-cicd, then cloned it:
git clone &lt;a href="https://github.com/Josedekajos/weather-api-cicd.git" rel="noopener noreferrer"&gt;https://github.com/Josedekajos/weather-api-cicd.git&lt;/a&gt;
cd weather-api-cicd
Initialize the CDK project
cdk init app --language typescript
This generates the starting structure:
weather-api-cicd/
├── bin/
│   └── weather-api-cicd.ts
├── lib/
│   └── weather-api-cicd-stack.ts
├── test/
│   └── weather-api-cicd.test.ts
├── cdk.json
├── jest.config.js
├── package.json
├── package-lock.json
├── tsconfig.json
└── README.md
• bin/ is the entry point of the CDK app.
• lib/ holds the infrastructure stack and pipeline definitions.
• test/ holds the automated tests.
• src/ (which we add later) holds the JavaScript Lambda handler.
The CDK commands you'll use constantly
# List available CDK stacks
npx cdk list&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Build the TypeScript project
&lt;/h1&gt;

&lt;p&gt;npm run build&lt;/p&gt;

&lt;h1&gt;
  
  
  Generate CloudFormation templates
&lt;/h1&gt;

&lt;p&gt;npx cdk synth&lt;/p&gt;

&lt;h1&gt;
  
  
  Compare local infrastructure with deployed infrastructure
&lt;/h1&gt;

&lt;p&gt;npx cdk diff&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploy a stack
&lt;/h1&gt;

&lt;p&gt;npx cdk deploy&lt;br&gt;
cdk synth is especially useful: it converts your infrastructure code into CloudFormation templates before anything is deployed.&lt;br&gt;
Configure AWS credentials&lt;br&gt;
I used a named AWS CLI profile called internship in the us-east-1 region. Verify yours works:&lt;br&gt;
aws sts get-caller-identity --profile internship --region us-east-1&lt;br&gt;
A named profile keeps this project separate from any other AWS configuration you have.&lt;br&gt;
Never publish your AWS access keys, secret keys or GitHub personal access token in a repository or an article.&lt;br&gt;
Store the GitHub token in AWS Secrets Manager&lt;br&gt;
CodePipeline needs access to your repository.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Generate a GitHub personal access token.&lt;/li&gt;
&lt;li&gt; Open the AWS Management Console and search for Secrets Manager.&lt;/li&gt;
&lt;li&gt; Choose Store a new secret → Other type of secret → plain text.&lt;/li&gt;
&lt;li&gt; Paste the token.&lt;/li&gt;
&lt;li&gt; Name the secret exactly github-token.&lt;/li&gt;
&lt;li&gt; Review and save.
CDK then references the secret without hardcoding it:
SecretValue.secretsManager('github-token')
Configure AWS CodePipeline
import {
CodePipeline,
ShellStep,
CodePipelineSource,
} from 'aws-cdk-lib/pipelines';&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;const pipeline = new CodePipeline(this, 'Pipeline', {&lt;br&gt;
  synth: new ShellStep('synth', {&lt;br&gt;
    input: CodePipelineSource.gitHub(&lt;br&gt;
      'Josedekajos/weather-api-cicd',&lt;br&gt;
      'main',&lt;br&gt;
      {&lt;br&gt;
        authentication: SecretValue.secretsManager('github-token'),&lt;br&gt;
      }&lt;br&gt;
    ),&lt;br&gt;
    commands: [&lt;br&gt;
      'npm ci',&lt;br&gt;
      'npm run build',&lt;br&gt;
      'npx cdk synth',&lt;br&gt;
    ],&lt;br&gt;
  }),&lt;br&gt;
});&lt;br&gt;
The synth step runs three commands: npm ci installs dependencies from the lockfile, npm run build compiles the TypeScript, and npx cdk synth generates the CloudFormation templates. That's the foundation of the automated workflow.&lt;/p&gt;




&lt;p&gt;Part 2: Adding Deployment Stages&lt;br&gt;
Rather than defining every resource directly in the pipeline stack, I created a reusable stage class.&lt;br&gt;
lib/pipeline-stage.ts&lt;br&gt;
import { Stage, StageProps } from 'aws-cdk-lib';&lt;br&gt;
import { Construct } from 'constructs';&lt;br&gt;
import { LambdaStack } from './lambda-stack';&lt;/p&gt;

&lt;p&gt;export class PipelineStage extends Stage {&lt;br&gt;
  constructor(&lt;br&gt;
    scope: Construct,&lt;br&gt;
    id: string,&lt;br&gt;
    props: StageProps&lt;br&gt;
  ) {&lt;br&gt;
    super(scope, id, props);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Instantiate the Lambda stack
new LambdaStack(this, 'lambdaStack', {
  stageName: props.stageName ?? id.toLowerCase(),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;br&gt;
PipelineStage extends CDK's Stage, creates a LambdaStack, and passes it a stage name. That lets one infrastructure definition serve several environments.&lt;br&gt;
Add the Development and Production stages&lt;br&gt;
Inside the main pipeline stack:&lt;br&gt;
pipeline.addStage(&lt;br&gt;
  new PipelineStage(this, 'PipelineDevStage', {&lt;br&gt;
    stageName: 'dev',&lt;br&gt;
  })&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;pipeline.addStage(&lt;br&gt;
  new PipelineStage(this, 'PipelineProdStage', {&lt;br&gt;
    stageName: 'prod',&lt;br&gt;
  })&lt;br&gt;
);&lt;br&gt;
Both environments share the same definition, but resource names include the stage name:&lt;br&gt;
devweatherApiTable&lt;br&gt;
prodweatherApiTable&lt;br&gt;
Less duplication, easier maintenance.&lt;br&gt;
Verify the stages&lt;br&gt;
npx cdk list --profile internship&lt;br&gt;
My output:&lt;br&gt;
WeatherApiCicdStack&lt;/p&gt;

&lt;p&gt;WeatherApiCicdStack/PipelineDevStage/lambdaStack (dev-lambdaStack)&lt;/p&gt;

&lt;p&gt;WeatherApiCicdStack/PipelineProdStage/lambdaStack (prod-lambdaStack)&lt;br&gt;
Both stages are recognized.&lt;/p&gt;




&lt;p&gt;Part 3: Building the Stack&lt;br&gt;
This is where the real infrastructure lives. I created lib/lambda-stack.ts containing:&lt;br&gt;
• a DynamoDB table&lt;br&gt;
• an API Gateway REST API&lt;br&gt;
• a Lambda function&lt;br&gt;
• permission for the Lambda to write to DynamoDB&lt;br&gt;
• a POST endpoint wired to the Lambda&lt;br&gt;
Imports and props&lt;br&gt;
import { StackProps } from 'aws-cdk-lib';&lt;br&gt;
import * as cdk from 'aws-cdk-lib';&lt;br&gt;
import { Construct } from 'constructs';&lt;/p&gt;

&lt;p&gt;import * as lambda from 'aws-cdk-lib/aws-lambda';&lt;br&gt;
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';&lt;br&gt;
import * as apigw from 'aws-cdk-lib/aws-apigateway';&lt;/p&gt;

&lt;p&gt;export interface LambdaStackProps extends StackProps {&lt;br&gt;
  stageName: string;&lt;br&gt;
}&lt;br&gt;
The DynamoDB table&lt;br&gt;
const table: dynamodb.Table = new dynamodb.Table(&lt;br&gt;
  this,&lt;br&gt;
  &lt;code&gt;${props.stageName}WeatherApiTable&lt;/code&gt;,&lt;br&gt;
  {&lt;br&gt;
    tableName: &lt;code&gt;${props.stageName}weatherApiTable&lt;/code&gt;,&lt;br&gt;
    partitionKey: {&lt;br&gt;
      name: 'id',&lt;br&gt;
      type: dynamodb.AttributeType.STRING,&lt;br&gt;
    },&lt;br&gt;
    removalPolicy: cdk.RemovalPolicy.DESTROY,&lt;br&gt;
  }&lt;br&gt;
);&lt;br&gt;
• tableName is stage-specific.&lt;br&gt;
• partitionKey is id, a string.&lt;br&gt;
• removalPolicy: DESTROY deletes the table when the stack is destroyed.&lt;br&gt;
Production warning: RemovalPolicy.DESTROY is fine for a temporary learning environment. A real production database should use a retention policy and backups.&lt;br&gt;
The API Gateway REST API&lt;br&gt;
const weatherApi: apigw.RestApi = new apigw.RestApi(&lt;br&gt;
  this,&lt;br&gt;
  'weather_rest_api',&lt;br&gt;
  {&lt;br&gt;
    restApiName: &lt;code&gt;${props.stageName} Weather Rest Api&lt;/code&gt;,&lt;br&gt;
    description: 'This service saves weather data.',&lt;br&gt;
  }&lt;br&gt;
);&lt;br&gt;
This produces APIs named dev Weather Rest Api and prod Weather Rest Api.&lt;br&gt;
The Lambda function&lt;br&gt;
const createWeatherLambda: lambda.Function = new lambda.Function(&lt;br&gt;
  this,&lt;br&gt;
  'CreateWeatherLambdaFunction',&lt;br&gt;
  {&lt;br&gt;
    functionName: &lt;code&gt;${props.stageName}-cdk-typescript-create&lt;/code&gt;,&lt;br&gt;
    runtime: lambda.Runtime.NODEJS_22_X,&lt;br&gt;
    handler: 'createWeather.lambdaHandler',&lt;br&gt;
    code: lambda.Code.fromAsset('src'),&lt;br&gt;
    environment: {&lt;br&gt;
      TABLE_NAME: table.tableName,&lt;br&gt;
      STAGE_NAME: props.stageName,&lt;br&gt;
    },&lt;br&gt;
  }&lt;br&gt;
);&lt;br&gt;
• runtime is Node.js 22.&lt;br&gt;
• handler points to the exported lambdaHandler in createWeather.js.&lt;br&gt;
• code loads from the src directory.&lt;br&gt;
• TABLE_NAME and STAGE_NAME are passed as environment variables.&lt;br&gt;
Function names come out as dev-cdk-typescript-create and prod-cdk-typescript-create.&lt;br&gt;
Permissions and the endpoint&lt;br&gt;
table.grantWriteData(createWeatherLambda);&lt;/p&gt;

&lt;p&gt;weatherApi.root&lt;br&gt;
  .addResource('create-weather')&lt;br&gt;
  .addMethod(&lt;br&gt;
    'POST',&lt;br&gt;
    new apigw.LambdaIntegration(createWeatherLambda)&lt;br&gt;
  );&lt;br&gt;
grantWriteData gives the function's IAM role write access to the table. Note that this doesn't make the handler write anything. The application code has to do that too.&lt;br&gt;
The second block creates POST /create-weather, which invokes the Lambda.&lt;br&gt;
The complete lib/lambda-stack.ts&lt;br&gt;
import { StackProps } from 'aws-cdk-lib';&lt;br&gt;
import * as cdk from 'aws-cdk-lib';&lt;br&gt;
import { Construct } from 'constructs';&lt;/p&gt;

&lt;p&gt;import * as lambda from 'aws-cdk-lib/aws-lambda';&lt;br&gt;
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';&lt;br&gt;
import * as apigw from 'aws-cdk-lib/aws-apigateway';&lt;/p&gt;

&lt;p&gt;export interface LambdaStackProps extends StackProps {&lt;br&gt;
  stageName: string;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export class LambdaStack extends cdk.Stack {&lt;br&gt;
  constructor(&lt;br&gt;
    scope: Construct,&lt;br&gt;
    id: string,&lt;br&gt;
    props: LambdaStackProps&lt;br&gt;
  ) {&lt;br&gt;
    super(scope, id, props);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create the DynamoDB table
const table: dynamodb.Table = new dynamodb.Table(
  this,
  `${props.stageName}WeatherApiTable`,
  {
    tableName: `${props.stageName}weatherApiTable`,
    partitionKey: {
      name: 'id',
      type: dynamodb.AttributeType.STRING,
    },
    removalPolicy: cdk.RemovalPolicy.DESTROY,
  }
);

// Create the API Gateway REST API
const weatherApi: apigw.RestApi = new apigw.RestApi(
  this,
  'weather_rest_api',
  {
    restApiName: `${props.stageName} Weather Rest Api`,
    description: 'This service saves weather data.',
  }
);

// Define the Lambda function
const createWeatherLambda: lambda.Function =
  new lambda.Function(
    this,
    'CreateWeatherLambdaFunction',
    {
      functionName: `${props.stageName}-cdk-typescript-create`,
      runtime: lambda.Runtime.NODEJS_22_X,
      handler: 'createWeather.lambdaHandler',
      code: lambda.Code.fromAsset('src'),
      environment: {
        TABLE_NAME: table.tableName,
        STAGE_NAME: props.stageName,
      },
    }
  );

// Grant Lambda permission to write to DynamoDB
table.grantWriteData(createWeatherLambda);

// Create the POST /create-weather endpoint
weatherApi.root
  .addResource('create-weather')
  .addMethod(
    'POST',
    new apigw.LambdaIntegration(createWeatherLambda)
  );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;br&gt;
The Lambda handler: src/createWeather.js&lt;br&gt;
exports.lambdaHandler = async (event) =&amp;gt; {&lt;br&gt;
  try {&lt;br&gt;
    const weatherData = JSON.parse(event.body || '{}');&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
  statusCode: 200,
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    message: 'Weather data received successfully',
    data: weatherData
  })
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} catch (error) {&lt;br&gt;
    return {&lt;br&gt;
      statusCode: 400,&lt;br&gt;
      headers: {&lt;br&gt;
        'Content-Type': 'application/json'&lt;br&gt;
      },&lt;br&gt;
      body: JSON.stringify({&lt;br&gt;
        message: 'Invalid request body'&lt;br&gt;
      })&lt;br&gt;
    };&lt;br&gt;
  }&lt;br&gt;
};&lt;br&gt;
A valid request returns 200 with a success message and the submitted data. Invalid JSON returns 400 with an error message. That was enough for the course's live integration test, though it doesn't persist anything to DynamoDB yet.&lt;br&gt;
Build and synthesize&lt;br&gt;
npm run build&lt;br&gt;
npx cdk synth&lt;br&gt;
The build checks the TypeScript; synthesis generates the CloudFormation templates. I also inspected the synthesized templates to confirm the Development and Production stacks contained their expected DynamoDB tables.&lt;/p&gt;




&lt;p&gt;Part 4: Writing the Tests&lt;br&gt;
The point of testing here is to verify that the CDK stack contains what you expect before you deploy it. I created two test files:&lt;br&gt;
test/&lt;br&gt;
├── weather-api-cicd.test.ts   (infrastructure tests)&lt;br&gt;
└── lambda.test.ts             (handler test)&lt;br&gt;
Infrastructure tests&lt;br&gt;
import * as cdk from 'aws-cdk-lib';&lt;/p&gt;

&lt;p&gt;import {&lt;br&gt;
  Template,&lt;br&gt;
  Capture,&lt;br&gt;
} from 'aws-cdk-lib/assertions';&lt;/p&gt;

&lt;p&gt;import { LambdaStack } from '../lib/lambda-stack';&lt;/p&gt;

&lt;p&gt;test('DynamoDB Table Created', () =&amp;gt; {&lt;br&gt;
  const app = new cdk.App();&lt;/p&gt;

&lt;p&gt;const stack = new LambdaStack(app, 'MyTestStack', {&lt;br&gt;
    stageName: 'Dev',&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;const template = Template.fromStack(stack);&lt;/p&gt;

&lt;p&gt;template.hasResource('AWS::DynamoDB::Table', {&lt;br&gt;
    Properties: {&lt;br&gt;
      TableName: 'DevweatherApiTable',&lt;br&gt;
    },&lt;br&gt;
  });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;test('ApiGateway RestApi Created', () =&amp;gt; {&lt;br&gt;
  const app = new cdk.App();&lt;/p&gt;

&lt;p&gt;const stack = new LambdaStack(app, 'MyTestStack', {&lt;br&gt;
    stageName: 'Dev',&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;const template = Template.fromStack(stack);&lt;/p&gt;

&lt;p&gt;template.hasResource('AWS::ApiGateway::RestApi', {&lt;br&gt;
    Properties: {&lt;br&gt;
      Name: 'Dev Weather Rest Api',&lt;br&gt;
    },&lt;br&gt;
  });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;test('Verify Number of Lambda Functions', () =&amp;gt; {&lt;br&gt;
  const app = new cdk.App();&lt;/p&gt;

&lt;p&gt;const stack = new LambdaStack(app, 'MyTestStack', {&lt;br&gt;
    stageName: 'Dev',&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;const template = Template.fromStack(stack);&lt;/p&gt;

&lt;p&gt;template.resourceCountIs('AWS::Lambda::Function', 1);&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;test('Create Lambda Function typescript Exist', () =&amp;gt; {&lt;br&gt;
  const app = new cdk.App();&lt;/p&gt;

&lt;p&gt;const stack = new LambdaStack(app, 'MyTestStack', {&lt;br&gt;
    stageName: 'Dev',&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;const template = Template.fromStack(stack);&lt;/p&gt;

&lt;p&gt;const envCapture = new Capture();&lt;/p&gt;

&lt;p&gt;template.hasResourceProperties('AWS::Lambda::Function', {&lt;br&gt;
    FunctionName: 'Dev-cdk-typescript-create',&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;template.hasResourceProperties('AWS::Lambda::Function', {&lt;br&gt;
    Environment: envCapture,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;expect(envCapture.asObject()).toEqual({&lt;br&gt;
    Variables: {&lt;br&gt;
      TABLE_NAME: {&lt;br&gt;
        Ref: expect.stringMatching(/^DevWeatherApiTable/),&lt;br&gt;
      },&lt;br&gt;
      STAGE_NAME: 'Dev',&lt;br&gt;
    },&lt;br&gt;
  });&lt;br&gt;
});&lt;br&gt;
What each one checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; DynamoDB table: it exists with the correct stage-specific name.&lt;/li&gt;
&lt;li&gt; API Gateway: the REST API exists with the expected name.&lt;/li&gt;
&lt;li&gt; Lambda count: exactly one function. The workshop expected five, but my stack has one, so I adjusted the test to match reality. A test should verify your actual implementation.&lt;/li&gt;
&lt;li&gt; Lambda properties: the function name is correct, TABLE_NAME references the table, and STAGE_NAME is Dev. Capture lets us inspect generated CloudFormation properties without hardcoding the full generated logical ID.
Handler test
import {
lambdaHandler as createWeather
} from '../src/createWeather';&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;test('create weather', async () =&amp;gt; {&lt;br&gt;
  const result = await createWeather({&lt;br&gt;
    body: JSON.stringify({&lt;br&gt;
      weather: 'hot',&lt;br&gt;
      town: 'Douala',&lt;br&gt;
    }),&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;expect(result).toMatchObject({&lt;br&gt;
    body: expect.any(String),&lt;br&gt;
    statusCode: expect.any(Number),&lt;br&gt;
  });&lt;br&gt;
});&lt;br&gt;
This is a deliberately basic test: it confirms the handler returns a string body and a numeric status code. It doesn't verify DynamoDB writes or assert the exact response message. Together with the four infrastructure tests, that's five tests across two suites.&lt;/p&gt;




&lt;p&gt;Errors I Hit (and How I Fixed Them)&lt;br&gt;
Troubleshooting was the most educational part of the project.&lt;br&gt;
Error 1: CDK CLI and library version mismatch&lt;br&gt;
My globally installed CLI (2.1138.0) was older than the schema version the installed CDK library required.&lt;br&gt;
Fix: use the project-local CLI via npx:&lt;br&gt;
npx cdk --version&lt;/p&gt;

&lt;h1&gt;
  
  
  2.1146.1
&lt;/h1&gt;

&lt;p&gt;Lesson: keep the CLI and library versions compatible, and prefer npx cdk so the project uses its own installed version.&lt;br&gt;
Error 2: CodeBuild concurrent build quota was zero&lt;br&gt;
When I checked AWS CodeBuild, the concurrent build quota for the Linux/Small environment was BUILD_GENERAL1_SMALL = 0, meaning CodeBuild couldn't start a build in that environment. I checked it with:&lt;br&gt;
aws service-quotas list-service-quotas &lt;code&gt;&lt;br&gt;
  --service-code codebuild&lt;/code&gt;&lt;br&gt;
  --profile internship &lt;code&gt;&lt;br&gt;
  --region us-east-1&lt;/code&gt;&lt;br&gt;
  --query "Quotas[?contains(QuotaName, 'Concurrent')].[QuotaName,Value,QuotaCode,Adjustable]" `&lt;br&gt;
  --output table&lt;br&gt;
The relevant quota code was L-9D07B6EF. I had already requested an increase to one concurrent build, and the request history showed:&lt;br&gt;
Requested limit: 1&lt;br&gt;
Status: CASE_OPENED&lt;br&gt;
It had been received but not approved, and I didn't submit a duplicate.&lt;br&gt;
Workaround: I deployed the Development stack directly from my machine with CDK, which let me complete the live API test without CodeBuild.&lt;br&gt;
This distinction matters: deploying manually with CDK is not the same as having a fully working automated CodePipeline.&lt;br&gt;
Error 3: TypeScript couldn't find declarations for the JavaScript handler&lt;br&gt;
test/lambda.test.ts:2:48 - error TS7016:&lt;/p&gt;

&lt;p&gt;Could not find a declaration file for module&lt;br&gt;
'../src/createWeather'.&lt;/p&gt;

&lt;p&gt;'C:/Users/josed/weather-api-cicd/src/createWeather.js'&lt;br&gt;
implicitly has an 'any' type.&lt;br&gt;
My test was TypeScript, but the handler was JavaScript, so TypeScript had no type information for it.&lt;br&gt;
Fix: add src/createWeather.d.ts:&lt;br&gt;
export declare const lambdaHandler: (&lt;br&gt;
  event: {&lt;br&gt;
    body?: string | null;&lt;br&gt;
  }&lt;br&gt;
) =&amp;gt; Promise&amp;lt;{&lt;br&gt;
  statusCode: number;&lt;br&gt;
  headers?: Record;&lt;br&gt;
  body: string;&lt;br&gt;
}&amp;gt;;&lt;br&gt;
This only describes the handler to TypeScript; it doesn't change the JavaScript implementation. After saving it, the build and tests ran cleanly.&lt;br&gt;
Error 4: "Jest worker failed to exit gracefully"&lt;br&gt;
A worker process has failed to exit gracefully&lt;br&gt;
and has been force exited.&lt;/p&gt;

&lt;p&gt;This is likely caused by tests leaking due to improper teardown.&lt;br&gt;
All tests still passed. This is a worker-cleanup warning, not a failed assertion. If it persists, a useful diagnostic is:&lt;br&gt;
npm test -- --detectOpenHandles&lt;br&gt;
I didn't treat it as a blocker since all five tests passed.&lt;br&gt;
Error 5: CDK couldn't find the stack by its short name&lt;br&gt;
npx cdk deploy dev-lambdaStack --profile internship&lt;br&gt;
No stacks match the name(s) dev-lambdaStack&lt;br&gt;
The stack is nested inside a CDK stage. cdk list shows the real path, so I used that:&lt;br&gt;
npx cdk deploy "WeatherApiCicdStack/PipelineDevStage/lambdaStack" --profile internship&lt;br&gt;
Lesson: for nested stacks, use the full path shown by cdk list.&lt;br&gt;
Error 6: npm vulnerability warnings&lt;br&gt;
20 vulnerabilities&lt;br&gt;
3 low&lt;br&gt;
17 moderate&lt;br&gt;
Installation completed and tests passed. I deliberately did not run npm audit fix --force, since force-upgrading without review can introduce breaking changes. For a production project, inspect the audit report and update dependencies carefully.&lt;/p&gt;




&lt;p&gt;Part 5: Running the Tests&lt;br&gt;
Install dependencies and run the suite:&lt;br&gt;
npm install&lt;br&gt;
npm test&lt;br&gt;
Result:&lt;br&gt;
PASS  test/lambda.test.ts&lt;/p&gt;

&lt;p&gt;PASS  test/weather-api-cicd.test.ts&lt;/p&gt;

&lt;p&gt;Test Suites: 2 passed, 2 total&lt;/p&gt;

&lt;p&gt;Tests: 5 passed, 5 total&lt;/p&gt;

&lt;p&gt;Snapshots: 0 total&lt;br&gt;
All five tests passed. Then:&lt;br&gt;
npm run build&lt;br&gt;
npx cdk synth&lt;br&gt;
Both succeeded once the TypeScript declaration issue was fixed.&lt;/p&gt;




&lt;p&gt;Deploying the Development Stack&lt;br&gt;
With the build, synth and tests green, I listed the stacks and deployed Development:&lt;br&gt;
npx cdk list --profile internship&lt;br&gt;
npx cdk deploy "WeatherApiCicdStack/PipelineDevStage/lambdaStack" --profile internship&lt;br&gt;
CDK listed the resources it was creating, and the deployment completed:&lt;br&gt;
CREATE_COMPLETE&lt;br&gt;
AWS::Lambda::Permission&lt;/p&gt;

&lt;p&gt;CREATE_COMPLETE&lt;br&gt;
AWS::CloudFormation::Stack&lt;/p&gt;

&lt;p&gt;dev-lambdaStack&lt;/p&gt;

&lt;p&gt;Deployment time: 68.14s&lt;br&gt;
Total time: 125.01s&lt;br&gt;
AWS generated an API endpoint in this form:&lt;br&gt;
https://.execute-api.us-east-1.amazonaws.com/prod/&lt;br&gt;
So the Create Weather endpoint is:&lt;br&gt;
POST https://.execute-api.us-east-1.amazonaws.com/prod/create-weather&lt;br&gt;
Notice the /prod/ in the URL. That's the API Gateway stage name; it does not mean my separate Production CDK environment was deployed.&lt;/p&gt;




&lt;p&gt;Testing the Live API&lt;br&gt;
With Development deployed, I sent a real HTTP POST from PowerShell.&lt;br&gt;
$body = @{&lt;br&gt;
    weather = "hot"&lt;br&gt;
    town = "Douala"&lt;br&gt;
} | ConvertTo-Json&lt;/p&gt;

&lt;p&gt;Invoke-RestMethod &lt;code&gt;&lt;br&gt;
    -Method Post &lt;/code&gt;&lt;br&gt;
    -Uri "https://.execute-api.us-east-1.amazonaws.com/prod/create-weather" &lt;code&gt;&lt;br&gt;
    -ContentType "application/json" &lt;/code&gt;&lt;br&gt;
    -Body $body&lt;br&gt;
The API responded:&lt;br&gt;
message                            data&lt;/p&gt;

&lt;p&gt;Weather data received successfully&lt;br&gt;
@{weather=hot; town=Douala}&lt;br&gt;
That confirmed that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The API Gateway endpoint was reachable.&lt;/li&gt;
&lt;li&gt; The POST method was configured correctly.&lt;/li&gt;
&lt;li&gt; API Gateway invoked the Lambda function.&lt;/li&gt;
&lt;li&gt; The handler parsed the submitted JSON.&lt;/li&gt;
&lt;li&gt; The Lambda returned a successful response containing the submitted data.
This wasn't just a local unit test. It was a real request to an API running in AWS.
________________________________________
Final Project Structure
weather-api-cicd/
├── bin/
│   └── weather-api-cicd.ts
├── lib/
│   ├── weather-api-cicd-stack.ts
│   ├── pipeline-stage.ts
│   └── lambda-stack.ts
├── src/
│   ├── createWeather.js
│   └── createWeather.d.ts
├── test/
│   ├── weather-api-cicd.test.ts
│   └── lambda.test.ts
├── cdk.json
├── jest.config.js
├── package.json
├── package-lock.json
├── tsconfig.json
├── README.md
└── .gitignore
The generated configuration files are best taken from the repository; the code above covers the infrastructure, handler, declaration and test files I implemented and verified.
________________________________________
Reproduce It Yourself&lt;/li&gt;
&lt;li&gt; Clone the repo
git clone &lt;a href="https://github.com/Josedekajos/weather-api-cicd.git" rel="noopener noreferrer"&gt;https://github.com/Josedekajos/weather-api-cicd.git&lt;/a&gt;
cd weather-api-cicd&lt;/li&gt;
&lt;li&gt; Install dependencies: npm install&lt;/li&gt;
&lt;li&gt; Configure AWS credentials and verify them:
aws sts get-caller-identity --profile internship
Use the us-east-1 region for this walkthrough.&lt;/li&gt;
&lt;li&gt; Create the github-token secret in AWS Secrets Manager. Never commit the token.&lt;/li&gt;
&lt;li&gt; Build: npm run build&lt;/li&gt;
&lt;li&gt; Test: npm test (expect 2 suites and 5 tests passing)&lt;/li&gt;
&lt;li&gt; Synthesize: npx cdk synth&lt;/li&gt;
&lt;li&gt; List stacks: npx cdk list --profile internship&lt;/li&gt;
&lt;li&gt; Deploy Development:
npx cdk deploy "WeatherApiCicdStack/PipelineDevStage/lambdaStack" --profile internship&lt;/li&gt;
&lt;li&gt;Test the API with the URL from your own deployment:
$body = @{
weather = "hot"
town = "Douala"
} | ConvertTo-Json&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Invoke-RestMethod &lt;code&gt;&lt;br&gt;
    -Method Post &lt;/code&gt;&lt;br&gt;
    -Uri "create-weather" &lt;code&gt;&lt;br&gt;
    -ContentType "application/json"&lt;/code&gt;&lt;br&gt;
    -Body $body&lt;br&gt;
Keep in mind that credentials, permissions, service quotas and regions differ between accounts. Success on one account doesn't guarantee the same quotas on another.&lt;/p&gt;




&lt;p&gt;What Was Completed and What Remains&lt;br&gt;
Project component   Result&lt;br&gt;
TypeScript CDK project setup    Completed&lt;br&gt;
GitHub repository setup Completed&lt;br&gt;
AWS profile configuration   Verified&lt;br&gt;
GitHub token in Secrets Manager Configured&lt;br&gt;
CodePipeline configuration  Implemented&lt;br&gt;
Development and Production stage definitions    Implemented&lt;br&gt;
DynamoDB infrastructure definition  Completed&lt;br&gt;
API Gateway infrastructure definition   Completed&lt;br&gt;
Create Weather Lambda definition    Completed&lt;br&gt;
Jest infrastructure tests   Passed&lt;br&gt;
Lambda handler test Passed&lt;br&gt;
TypeScript build    Passed&lt;br&gt;
CDK synthesis   Passed&lt;br&gt;
Development AWS deployment  Successful&lt;br&gt;
Live API POST request   Successful&lt;br&gt;
Production stack deployment Not yet confirmed&lt;br&gt;
Automated CodePipeline execution    Not yet confirmed (CodeBuild quota request pending)&lt;br&gt;
The pipeline and its stages are configured in the project, but I did not establish a successful end-to-end automated pipeline run during this workshop. The CodeBuild quota increase was still open, which is why I deployed Development locally.&lt;br&gt;
Next steps&lt;br&gt;
• Implement the handler's DynamoDB write so data is actually persisted.&lt;br&gt;
• Build the remaining four CRUDL Lambda functions.&lt;br&gt;
• Complete a full automated CodePipeline run once the quota is approved.&lt;br&gt;
• Deploy and verify the Production stage.&lt;br&gt;
• Replace RemovalPolicy.DESTROY with a retention policy for production data.&lt;/p&gt;




&lt;p&gt;What I Learned&lt;br&gt;
• Infrastructure as Code: defining AWS resources in TypeScript with CDK instead of the console.&lt;br&gt;
• Serverless architecture: exposing a REST API with Lambda and API Gateway.&lt;br&gt;
• Database infrastructure: defining a DynamoDB table and granting Lambda write access.&lt;br&gt;
• Environment separation: using CDK stages to organize Development and Production from one definition.&lt;br&gt;
• Automated testing: using Jest and CDK assertions to verify resources before deployment.&lt;br&gt;
• CI/CD concepts: a GitHub-based CodePipeline with a synth step and separate deployment stages.&lt;br&gt;
• Troubleshooting: CLI version compatibility, TypeScript declarations, Jest warnings, AWS quotas and nested stack names.&lt;br&gt;
Most importantly, I went from writing infrastructure code locally to deploying a Development stack and getting a successful response from an API hosted on AWS.&lt;/p&gt;




&lt;p&gt;Conclusion&lt;br&gt;
This Educloud Academy workshop was a valuable practical step in my AWS, DevOps and software engineering journey. It brought TypeScript, AWS CDK, Jest, GitHub, Lambda, API Gateway, DynamoDB and CodePipeline together in one project.&lt;br&gt;
The most satisfying moment was seeing the deployed API answer a real HTTP request. The project also reminded me that cloud deployment is rarely a straight line: version compatibility, permissions, quotas and configuration errors are part of real cloud engineering, and learning to diagnose them matters as much as writing the initial code.&lt;br&gt;
I look forward to building on this foundation with more advanced cloud and serverless applications.&lt;/p&gt;




&lt;p&gt;Tags: AWS CDK · TypeScript · Node.js · Jest · GitHub · AWS CodePipeline · AWS CodeBuild · AWS Lambda · Amazon API Gateway · Amazon DynamoDB · AWS Secrets Manager · CloudFormation&lt;br&gt;
Project repository: &lt;a href="https://github.com/Josedekajos/weather-api-cicd" rel="noopener noreferrer"&gt;https://github.com/Josedekajos/weather-api-cicd&lt;/a&gt; Course: Educloud Academy, Unit Testing and CI/CD Deployment with Jest, GitHub and AWS CodePipeline&lt;/p&gt;

</description>
      <category>jestgithub</category>
      <category>codepipeline</category>
      <category>awssecretsmanager</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
