AWS ল্যাম্বডা, CDK, API Gateway, Congnito এবং অন্যান্য

AWS ল্যাম্বডা হচ্ছে সার্ভারলেস ফাংশন। যা কোন সার্ভার ম্যানেজ করা ছাড়াই কোড এক্সিকিউট করার সুযোগ করে দেয়। ফায়ারবেজ বা গুগল ক্লাউড ফাংশনের মত।

ল্যাম্বডা ফাংশন নিয়ে কাজ করার জন্য AWS একাউন্ট থাকা লাগবে। একাউন্ট তৈরি করা যাবে https://aws.amazon.com/ ঠিকানায়। সাইনআপ করার সময় পেমেন্ট প্রোফাইল যোগ করতে হবে। যদিও শুরুর দিকে কোন বিল কাটবে না। উল্টো ১০০ ডলার ক্রেডিট পাওয়া যাবে।

একাউন্ট তৈরি করার পর AWS কনসোলে যাবো। এরপর সার্চবারে Lambda লিখে সার্চ করব। সাইডবার থেকে Functions এ ক্লিক করব। তাহলে একাউন্টে কোন ফাংশন থাকলে তা দেখাবে। নতুন একাউন্টের ক্ষেত্রে কোন ফাংশন থাকবে না।

ফাংশন তৈরি

ফাংশন পেইজ থেকে Create Function বাটনে ক্লিক করে ফাংশন তৈরি করা যাবে। ফাংশন তৈরি করার সময় ফাংশনের নাম এবং Additional configurations থেকে Funtion URL অপশন এনাবল করে নিব। এবং Auth type এর ক্ষেত্রে NONE সিলেক্ট করব।

এরপর ফাংশন তৈরি এবং ডিপ্লয় হবে। ইনিশিয়ালি নিচের মত করে কোড লেখা থাকবে।

export const handler = async (event) => {
  // TODO implement
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};

এবং Function URL এ একটা URL থাকবে, যা ক্লিক করে এই ফাংশনের আউটপুট দেখতে পাবো। URL কিছুটা এমন https://3pne76sasdbcbq0bamue.lambda-url.ap-south-1.on.aws/।

একটা ফাংশন ওপেন করার পর কোড ট্যাব থেকে কোড এডিট করতে পারব। যা আমাদের একটা অনলাইন VS Code ইন্টারফেসে কোড গুলো এডিট করতে দিবে। আবার চাইলে নিজ কম্পিউটারের VS Code এও ওপেন করতে পারব। কোড এডিট করে ডিপ্লয় করতে পারব। ফাংশন URL এ গেলে পরিবর্তন গুলো দেখতে পাবো।

AWS Lambda + CDK

AWS এর ক্লাউড ডেভেলপমেন্ট কিট রয়েছে। সংক্ষেপে CDK। যা ব্যবহার করে AWS এর বিভিন্ন সার্ভিস ব্যবহার করে প্রজেক্ট তৈরি করা যায়। যেমন একটা প্রজেক্টে ল্যাম্বডা ফাংশনের পাশাপাশি ডেটাবেজ, অথেনটিকেশন, API গেটওয়ে, ফ্রন্টেন্ড অ্যাপ ইত্যাদি লাগতে পারে। সব গুলো একত্রে ম্যানেজ করার জন্য হচ্ছে CDK।

আমরা সব কিছু একত্রে না করে আস্তে আস্তে এগুবো। অলরেডি কিভাবে একটা ল্যাম্বডা ফাংশন তৈরি করা যায়, তা দেখেছি। এবার দেখব কিভাবে CDK ব্যবহার করে ল্যাম্বডা ফাংশন তৈরি এবং ডিপ্লয় করা যায়।

দরকারি টুলস ইন্সটল

কম্পিউটারে AWS CLI এবং Node ইন্সটল করা থাকতে হবে। এরপর নিচের কমান্ড রান করে AWS CDK ইন্সটল করে নিব।

npm install -g aws-cdk

AWS Credentials কনফিগার

আমরা রুট ইউজার হিসেবে AWS কনসোলে এক্সেস করেছি। CDK ব্যবহার করার জন্য একটা IAM ইউজার যোগ করে নিব। তার জন্যঃ

  • প্রথমে AWS Console এ যাবো।
  • IAM → Users → Create User এ ক্লিক করব। সার্চবারে IAM সার্চ করে ইজিলি যেতে পারব।
  • এরপর ইউজার নেম দিয়ে নেক্সট এ ক্লিক করব। এখানে মূলত ইমেইল দিতে হবে।
  • এর পরের স্টেপে এই ইউজারকে একটা User groups এ যোগ করতে হবে। নতুন একাউণ্টের ক্ষেত্রে কোন User groups থাকবে না। তাই একটা ইউজার গ্রুপ তৈরি করে নিব। এই ইউজার গ্রুপ কি কি এক্সেস পাবে, তা সিলেক্ট করব। আমরা যেহেতু শিখছি, তাই AdministratorAccess সিলেক্ট করব। এটা মানে ফুল এক্সেস, মাথায় রাখতে হবে।

ইউজার নেম তৈরি করার পর একটা এক্সেস কি তৈরি করতে হবে। নির্দিষ্ট ইউজার ওপেন করার পর উপরের দিকে Create access key অপশন পাবো। এইখানে ইউজকেস দিতে হবে। আমরা কমান্ডলাইনে ব্যবহার করব, তাই Command Line Interface (CLI)। এরপর এই ইউজারের জন্য Access Key ID এবং Secreat access key তৈরি করে দিবে। .csv হিসেবে এই ফাইল ডাউনলোড করতে পারব। Secreat access key দ্বিতীয়বার দেখা যাবে না কনসোল থেকে। তাই এই ফাইলটা ডাউনলোড করে রাখতে পারি।

এবার AWS CLI কনফিগার করে নিত পারব। তার জন্য লিখবঃ

aws configure --profile mypersonal

এখানে একাধিক প্রোফাইল এড করতে পারব। mypersonal যায়গায় প্রোফাইল অনুযায়ী যে কোন নাম দিয়ে সেভ করতে পারব। এই নাম দিয়ে পরবর্তীতে কনসোলে প্রোফাইল পরিবর্তন করতে পারব।

প্রথম CDK অ্যাপ

এবার আমরা ক্লাউড কিট ব্যবহার করে একটা অ্যাপ তৈরি করব। একেবারে সহজ একটা ল্যাম্বডা ফাংশন দিয়ে শুরু করি। যেমনঃ

cdk init app --language javascript

যা জাভাস্ক্রিপ্ট ব্যবহার করে একটা CDK প্রজেক্ট তৈরি করে দিবে। যার মধ্যে থাকবেঃ

bin/
lib/
test/
cdk.json
package.json

এবং অন্যান্য অটো জেনারেটেড ফাইল। এটা যেহেতু একটা নড প্রজেক্ট, তাই package.json ফাইলে আমরা নিজেদের প্রয়োজন মত প্যাকেজ যোগ করতে পারব।

এর ভেতর lambda নামক একটা ফোল্ডার তৈরি করি। এবং তার ভেতর hello.js নামক একটা ফাইল তৈরি করিঃ

exports.handler = async () => {
    return {
      statusCode: 200,
      body: JSON.stringify("Hello from Lambda!"),
    };
  };

lib/lamda-stack.js এর ভেতরে নিচের মত লিখিঃ

const { Stack, Duration, CfnOutput } = require('aws-cdk-lib');
const { Function, Runtime, Code, FunctionUrlAuthType } = require('aws-cdk-lib/aws-lambda');
const { LambdaRestApi } = require('aws-cdk-lib/aws-apigateway');
const path = require('path');

class LamdaStack extends Stack {
  /**
   *
   * @param {Construct} scope
   * @param {string} id
   * @param {StackProps=} props
   */
  constructor(scope, id, props) {
    super(scope, id, props);

    // Lambda function
    const helloLambda = new Function(this, 'HelloLambda', {
      runtime: Runtime.NODEJS_22_X,
      handler: 'hello.handler',
      code: Code.fromAsset(path.join(__dirname, '../lambda')),
    });


    //  Function URL  
    const functionUrl = helloLambda.addFunctionUrl({
      authType: FunctionUrlAuthType.NONE,
    });

    // Output the API URL
    new CfnOutput(this, 'ApiUrl', {
      value: functionUrl.url,
      description: 'URL of the Function URL',
    });

  }
}

module.exports = { LamdaStack }

এখানে

  1.  aws-cdk-lib/aws-lambda ব্যবহার করে ল্যাম্বডা ইম্পোর্ট করে নিয়েছি।
  2.  HelloLambda নামে একটা ল্যাম্বডা ফাংশন তৈরি করেছি। যা Node.js 22 ব্যবহার করছে।
  3. hello.handler পয়েন্ট করেছি lambda ফোল্ডারের hello.js এর সাথে।

এই তো। এবার আমরা এই এই প্রজেক্ট বুটোস্ট্র্যাপ করতে হবে। যেটা শুধু মাত্র একবারই লাগবে প্রতিটা রিজিয়নের জন্য। যার জন্য কমান্ড লিখবঃ

cdk bootstrap aws://YOUR_ACCOUNT_ID/us-east-1 --profile mypersonal

aws কনসোলে লগিন করলে একাউন্ট আইডি উপরের দিকে দেখাবে। সেখানে ক্লিক করে কপি করে নেওয়া যাবে। এরপর প্রজেক্ট ডিপ্লয় করবঃ

cdk deploy --profile mypersonal

ডিপ্লয় করার পর কনসোলে ঐ ফাংশনের URL রিটার্ণ করবে এমনঃ

Outputs:
LamdaStack.ApiUrl = https://l7varnr4asdfpyvdi0pjknj.lambda-url.ap-south-1.on.aws/

এই ফাংশনের URL তৈরি করার সময় আমরা FunctionUrlAuthType.NONE দিয়েছি, তাই ব্রাউজারে সরাসরি দেখতে পাবো আউটপুট।

ফাংশন URL এর পরিবর্তে API গেটওয়ে ব্যবহার করতে পারি। তার জন্য এভাবে লিখবঃ

const { Stack, Duration, CfnOutput } = require('aws-cdk-lib');
const { Function, Runtime, Code, FunctionUrlAuthType } = require('aws-cdk-lib/aws-lambda');
const { LambdaRestApi } = require('aws-cdk-lib/aws-apigateway');
const path = require('path');

class LamdaStack extends Stack {
  /**
   *
   * @param {Construct} scope
   * @param {string} id
   * @param {StackProps=} props
   */
  constructor(scope, id, props) {
    super(scope, id, props);

    // Lambda function
    const helloLambda = new Function(this, 'HelloLambda', {
      runtime: Runtime.NODEJS_22_X,
      handler: 'hello.handler',
      code: Code.fromAsset(path.join(__dirname, '../lambda')),
    });

    //  API Gateway (REST API)
    const api = new LambdaRestApi(this, 'HelloApi', {
      handler: helloLambda,
      proxy: true, // Forward all requests to Lambda
    });

 
    // Output the API URL
    new CfnOutput(this, 'ApiUrl', {
      value: api.url,
      description: 'URL of the API Gateway',
    });
  }
}

module.exports = { LamdaStack }

যা আউটপুট হিসেবে API Gateway URL দেখাবে। API Gateway সম্পর্কে বিস্তারিত পরে জানব। এই URL পরবর্তীতে API লিস্ট থেকেও পাওয়া যাবে API Gateway তে। সার্চবারে API Gateway লিখে সার্চ করব। এরপর দেখব HelloAPI নামে একটা API রয়েছে। বামপাশ থেকে Stages এ ক্লিক করলে URL পাবো।

AWS CDK + Lambda + API Gateway + DynamoDB

এবার আরেকটু বেশি কিছু ট্রাই করি। যেমন API গেটওয়ে ব্যবহার করে ডেটাবেজে কিছু ডেটা রাখব এবং ডেটা রিড করব। তার জন্য lamda-stack.js এভাবে লিখবঃ

const { Stack, CfnOutput } = require('aws-cdk-lib');
const { Table, AttributeType, BillingMode } = require('aws-cdk-lib/aws-dynamodb');
const { Function, Runtime, Code } = require('aws-cdk-lib/aws-lambda');
const { LambdaRestApi } = require('aws-cdk-lib/aws-apigateway');
const path = require('path');

class LamdaStack extends Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    // DynamoDB
    const table = new Table(this, "ItemsTable", {
      partitionKey: { name: "id", type: AttributeType.STRING },
      billingMode: BillingMode.PAY_PER_REQUEST,
      tableName: "Items",
    });

    // Lambda Function
    const apiLambda = new Function(this, "ApiLambda", {
      runtime: Runtime.NODEJS_22_X,
      handler: "handler.main",
      code: Code.fromAsset(path.join(__dirname, "../lambda")),
      environment: {
        TABLE_NAME: table.tableName,
      },
    });

    table.grantReadWriteData(apiLambda);

    // API Gateway
    const api = new LambdaRestApi(this, "ItemsApi", {
      handler: apiLambda,
      proxy: false,
    });

    const items = api.root.addResource("item");
    items.addMethod("POST");

    const itemId = items.addResource("{id}");
    itemId.addMethod("GET");

    new CfnOutput(this, "ApiUrl", {
      value: api.url,
    });
  }
}

module.exports = { LamdaStack };

lambda/handler.js ফাইল নামক ফাইল তৈরি করে এভাবে লিখবঃ

const {
    DynamoDBClient,
    PutItemCommand,
    GetItemCommand,
  } = require("@aws-sdk/client-dynamodb");
  
  const client = new DynamoDBClient({});
  
  exports.main = async (event) => {
    const tableName = process.env.TABLE_NAME;
  
    try {
      // Handle POST /item
      if (event.httpMethod === "POST") {
        const body = JSON.parse(event.body);
  
        await client.send(
          new PutItemCommand({
            TableName: tableName,
            Item: {
              id: { S: body.id },
              value: { S: body.value },
            },
          })
        );
  
        return {
          statusCode: 200,
          body: JSON.stringify({ message: "Item stored" }),
        };
      }
  
      // Handle GET /item/{id}
      if (event.httpMethod === "GET") {
        const id = event.pathParameters.id;
  
        const result = await client.send(
          new GetItemCommand({
            TableName: tableName,
            Key: { id: { S: id } },
          })
        );
  
        return {
          statusCode: 200,
          body: JSON.stringify({ item: result.Item }),
        };
      }
  
      return {
        statusCode: 400,
        body: "Invalid request",
      };
    } catch (err) {
      console.error(err);
      return { statusCode: 500, body: "Server error" };
    }
  };
  

এখানে আমরা দুইটা API এন্ডপয়েন্ট তৈরি করেছি। একটা হচ্ছে GET, আরেকটা POST। পোস্ট মেথড ব্যবহার করে ডায়নামোডিবিতে ডেটা রাখব। এবং GET মেথড ব্যবহার করে ডেটা রিড করব। তার জন্য প্রথমে ডেটা রাইট করি।

bin/lamda.js ফাইলে const cdk = require('aws-cdk-lib/core'); এর পরিবর্তে const cdk = require('aws-cdk-lib'); ব্যবহার করব।

ডিপ্লয় করার পর আমরা URL পাবো এমনঃ

https://xxxx.execute-api.ap-south-1.amazonaws.com/prod/

এরপর ঐ URL এর সাথে /item যোগ করে নিচের মত করে পোস্ট রিকোয়েস্ট করবঃ

POST https://xxxx.execute-api.ap-south-1.amazonaws.com/prod/item
{
  "id": "1",
  "value": "Hello"
}

এরপর সাকসেস হলে সাকসেস মেসেজ রিটার্ণ করবে। তারপর গেট রিকোয়েস্ট করে ঐ আইটেম পাবোঃ

https://xxxx.execute-api.ap-south-1.amazonaws.com/prod/item/1

Lambda + CDK + API Gateway + Cognito

কগনিটোর কাজ হচ্ছে অথেনটিকেশন। এবার আমরা API এন্ডপয়েন্টে অথেনটিকেশন যোগ করব। তার জন্য lib/lamda-stack.js এভাবে লিখবঃ

const { Stack, CfnOutput } = require('aws-cdk-lib');
const { Table, AttributeType, BillingMode } = require('aws-cdk-lib/aws-dynamodb');
const { Function, Runtime, Code } = require('aws-cdk-lib/aws-lambda');
const { RestApi, LambdaIntegration, AuthorizationType } = require('aws-cdk-lib/aws-apigateway');
const { UserPool, UserPoolClient } = require('aws-cdk-lib/aws-cognito');
const { CognitoUserPoolsAuthorizer } = require('aws-cdk-lib/aws-apigateway');
const path = require('path');

class LamdaStack extends Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    // DynamoDB
    const table = new Table(this, "ItemsTable", {
      partitionKey: { name: "id", type: AttributeType.STRING },
      billingMode: BillingMode.PAY_PER_REQUEST,
      tableName: "Items",
    });

    // Create a user pool
    const userPool = new UserPool(this, 'MyUserPool', {
      userPoolName: 'MyUserPool',
      selfSignUpEnabled: true,
      signInAliases: { email: true }
    });

    // App client - Enable USER_PASSWORD_AUTH flow
    const appClient = new UserPoolClient(this, 'AppClient', {
      userPool,
      generateSecret: false,
      authFlows: {
        userPassword: true, 
        userSrp: true,       
      },
    });

    // Lambda Function
    const apiLambda = new Function(this, "ApiLambda", {
      runtime: Runtime.NODEJS_22_X,
      handler: "handler.main",
      code: Code.fromAsset(path.join(__dirname, "../lambda")),
      environment: {
        TABLE_NAME: table.tableName,
        USER_POOL_ID: userPool.userPoolId,
        APP_CLIENT_ID: appClient.userPoolClientId,
      },
    });



    table.grantReadWriteData(apiLambda);


    // API Gateway
    const api = new RestApi(this, "ItemsApi", {
      restApiName: "Items API",
    });

    // Create authorizer ( 
    const authorizer = new CognitoUserPoolsAuthorizer(this, 'CognitoAuthorizer', {
      cognitoUserPools: [userPool],
      authorizerName: 'MyCognitoAuthorizer',
    });

    // Create Lambda integration
    const lambdaIntegration = new LambdaIntegration(apiLambda);

    const items = api.root.addResource("item");
    items.addMethod("POST", lambdaIntegration, {
      authorizer: authorizer,  // add authorizer to the method
      authorizationType: AuthorizationType.COGNITO,
    });

    const itemId = items.addResource("{id}");
    itemId.addMethod("GET", lambdaIntegration, {
      authorizer: authorizer, // add authorizer to the method
      authorizationType: AuthorizationType.COGNITO,
    });

    new CfnOutput(this, "ApiUrl", {
      value: api.url,
    });
  }
}

module.exports = { LamdaStack };

lambda/handler.js আগের মতই থাকবেঃ

const {
    DynamoDBClient,
    PutItemCommand,
    GetItemCommand,
  } = require("@aws-sdk/client-dynamodb");
  
  const client = new DynamoDBClient({});
  
  exports.main = async (event) => {
    const tableName = process.env.TABLE_NAME;
  
    try {
      // Handle POST /item
      if (event.httpMethod === "POST") {
        const body = JSON.parse(event.body);
  
        await client.send(
          new PutItemCommand({
            TableName: tableName,
            Item: {
              id: { S: body.id },
              value: { S: body.value },
            },
          })
        );
  
        return {
          statusCode: 200,
          body: JSON.stringify({ message: "Item stored" }),
        };
      }
  
      // Handle GET /item/{id}
      if (event.httpMethod === "GET") {
        const id = event.pathParameters.id;
  
        const result = await client.send(
          new GetItemCommand({
            TableName: tableName,
            Key: { id: { S: id } },
          })
        );
  
        return {
          statusCode: 200,
          body: JSON.stringify({ item: result.Item }),
        };
      }
  
      return {
        statusCode: 400,
        body: "Invalid request",
      };
    } catch (err) {
      console.error(err);
      return { statusCode: 500, body: "Server error" };
    }
  };
 

এরপর ডিপ্লয় করবঃ

cdk deploy

কগনিটো ইউজার তৈরি

aws কনসোল থেকে সরাসরি আমরা ইউজার তৈরি করতে পারব। তার জন্য কনসোলে গিয়ে congnito সার্চ করে নিব। এরপর ইউজারপুলে দেখব MyUserPool নামে একটা ইউজারপুল রয়েছে, তা সিলেক্ট করব। বাম দিকে User management থেকে Users এ ক্লিক করে ইউজার তৈরি করে নিতে পারব।

আবার কমান্ডলাইন থেকেও ইউজার তৈরি করে নেওয়া যাবে। আমরা কমান্ডলাইন ব্যবহার করে ইউজার তৈরি করবঃ

aws cognito-idp admin-create-user \
  --region ap-south-1 \
  --user-pool-id USER_POOL_ID \
  --username YOUR_EMAIL \
  --user-attributes Name=email,Value=YOUR_EMAIL Name=email_verified,Value=true \
  --temporary-password 'TempPassword123!' \
  --message-action SUPPRESS

এখানে ap-south-1 এর পরবির্তে প্রোফাইল কোন রিজিয়নের, সে রিজিয়ন সেট করতে হবে USER_POOL_ID পাওয়া যাবে কনসলেঃ

এরপর একাউন্ট তৈরি হবে। একাউন্ট তৈরির পর পাসওয়ার্ড পরিবর্তন করে নিতে হবে। তার জন্যঃ

aws cognito-idp admin-set-user-password \
  --region ap-south-1 \
  --user-pool-id USER_POOL_ID \
  --username YOUR_EMAIL \
  --password 'YourPermanentPassword123!' \
  --permanent

এরপর লগিন করতে পারব। API এর জন্য এক্সেস টোকেন লাগবে। তার জন্যঃ

aws cognito-idp initiate-auth \
  --region ap-south-1 \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id USER_POOL_CLIENT_ID \
  --auth-parameters USERNAME="YOUR_EMAIL",PASSWORD=‘YOUR_PASSWORD’

টোকেন জেনারেট করার জন্য ক্লায়েন্ট আইডি লাগবে। তা পাওয়া যাবে নির্দিষ্ট ইউজারপুল ওপেন করার পর App Clients সিলেক্ট করেঃ

এরপর আমাদের AuthenticationResult দিবে। ঐখানে AccessToken, RefreshToken, IdToken থাকবে। IdToken ব্যবহার করে আমরা GET অথবা POST রিকোয়েস্ট করে এবার API এক্সেস করতে পারব।

ডিলিট CDK প্রজেক্ট

টেস্ট শেষে আমাদের সব কিছু ক্লিন করে নেওয়া উত্তম। না না হলে অ্যামাজন আনওয়ান্টেড বিল ধরিয়ে দিতে পারে। তার জন্য লিখবঃ

cdk destroy

Leave a Comment