Cloud SQL Node.js সংযোগকারী ব্যবহার করে PostgreSQL-এর জন্য ক্লাউড এসকিউএল-এর সাথে ক্লাউড রানে একটি সম্পূর্ণ স্ট্যাক অ্যাঙ্গুলার অ্যাপ্লিকেশন স্থাপন করুন

১. সংক্ষিপ্ত বিবরণ

ক্লাউড রান একটি সম্পূর্ণভাবে পরিচালিত প্ল্যাটফর্ম যা আপনাকে গুগলের পরিবর্ধনযোগ্য পরিকাঠামোর উপর সরাসরি আপনার কোড চালানোর সুযোগ দেয়। এই কোডল্যাবে দেখানো হবে কিভাবে ক্লাউড এসকিউএল নোড.জেএস কানেক্টর ব্যবহার করে ক্লাউড রানে থাকা একটি অ্যাঙ্গুলার অ্যাপ্লিকেশনকে ক্লাউড এসকিউএল ফর পোস্টগ্রেসকিউএল (Cloud SQL for PostgreSQL) ডেটাবেসের সাথে সংযুক্ত করা যায়।

এই ল্যাবে, আপনি শিখবেন কীভাবে:

  • একটি ক্লাউড এসকিউএল ফর পোস্টগ্রেসকিউএল ইনস্ট্যান্স তৈরি করুন
  • ক্লাউড রান-এ এমন একটি অ্যাপ্লিকেশন স্থাপন করুন যা আপনার ক্লাউড SQL ডেটাবেসের সাথে সংযোগ স্থাপন করে।

২. পূর্বশর্তসমূহ

  1. যদি আপনার আগে থেকে কোনো গুগল অ্যাকাউন্ট না থাকে, তাহলে আপনাকে অবশ্যই একটি গুগল অ্যাকাউন্ট তৈরি করতে হবে।
    • কর্মক্ষেত্র বা শিক্ষা প্রতিষ্ঠানের অ্যাকাউন্টের পরিবর্তে ব্যক্তিগত অ্যাকাউন্ট ব্যবহার করুন। কর্মক্ষেত্র এবং শিক্ষা প্রতিষ্ঠানে এমন কিছু সীমাবদ্ধতা থাকতে পারে, যার ফলে আপনি এই ল্যাবের জন্য প্রয়োজনীয় এপিআই (API) সক্রিয় করতে পারবেন না।

৩. প্রজেক্ট সেটআপ

  1. গুগল ক্লাউড কনসোলে সাইন-ইন করুন।
  2. ক্লাউড কনসোলে বিলিং চালু করুন
    • এই ল্যাবটি সম্পন্ন করতে ক্লাউড রিসোর্সে ১ মার্কিন ডলারেরও কম খরচ হওয়া উচিত।
    • পরবর্তী চার্জ এড়াতে, এই ল্যাবের শেষে দেওয়া ধাপগুলো অনুসরণ করে আপনি রিসোর্সগুলো মুছে ফেলতে পারেন।
    • নতুন ব্যবহারকারীরা ৩০০ মার্কিন ডলারের ফ্রি ট্রায়ালের জন্য যোগ্য।
  3. একটি নতুন প্রজেক্ট তৈরি করুন অথবা বিদ্যমান কোনো প্রজেক্ট পুনরায় ব্যবহার করুন।

৪. ক্লাউড শেল এডিটর খুলুন

  1. ক্লাউড শেল এডিটরে যান
  2. যদি স্ক্রিনের নীচে টার্মিনালটি দেখা না যায়, তাহলে এটি খুলুন:
    • হ্যামবার্গার মেনুতে ক্লিক করুন হ্যামবার্গার মেনু আইকন
    • টার্মিনালে ক্লিক করুন
    • নতুন টার্মিনালে ক্লিক করুন ক্লাউড শেল এডিটরে নতুন টার্মিনাল খুলুন
  3. টার্মিনালে এই কমান্ডটি দিয়ে আপনার প্রজেক্ট সেট করুন:
    • বিন্যাস:
      gcloud config set project [PROJECT_ID]
      
    • উদাহরণ:
      gcloud config set project lab-project-id-example
      
    • যদি আপনি আপনার প্রজেক্ট আইডি মনে করতে না পারেন:
      • আপনি আপনার সমস্ত প্রজেক্ট আইডি তালিকাভুক্ত করতে পারেন:
        gcloud projects list | awk '/PROJECT_ID/{print $2}'
        
      ক্লাউড শেল এডিটর টার্মিনালে প্রজেক্ট আইডি সেট করুন
  4. অনুমোদন করতে বলা হলে, চালিয়ে যাওয়ার জন্য 'Authorize'-এ ক্লিক করুন। ক্লাউড শেল অনুমোদন করতে ক্লিক করুন
  5. আপনি এই বার্তাটি দেখতে পাবেন:
    Updated property [core/project].
    
    যদি আপনি একটি WARNING দেখতে পান এবং আপনাকে Do you want to continue (Y/N)? জিজ্ঞাসা করা হয়, তাহলে সম্ভবত আপনি প্রজেক্ট আইডি ভুলভাবে প্রবেশ করিয়েছেন। N চাপুন, Enter চাপুন এবং gcloud config set project কমান্ডটি আবার চালানোর চেষ্টা করুন।

৫. এপিআই সক্রিয় করুন

টার্মিনালে, এপিআইগুলো সক্রিয় করুন:

gcloud services enable \
  sqladmin.googleapis.com \
  run.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com

অনুমোদন করতে বলা হলে, চালিয়ে যাওয়ার জন্য 'Authorize'-এ ক্লিক করুন। ক্লাউড শেল অনুমোদন করতে ক্লিক করুন

এই কমান্ডটি সম্পন্ন হতে কয়েক মিনিট সময় লাগতে পারে, কিন্তু অবশেষে এটি এইটির মতো একটি সফলতার বার্তা দেবে:

Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.

৬. একটি পরিষেবা অ্যাকাউন্ট তৈরি করুন

Cloud Run ব্যবহারের জন্য একটি Google Cloud পরিষেবা অ্যাকাউন্ট তৈরি ও কনফিগার করুন, যাতে Cloud SQL-এর সাথে সংযোগ করার জন্য এটির সঠিক অনুমতি থাকে।

  1. একটি নতুন সার্ভিস অ্যাকাউন্ট তৈরি করতে, নিচের মতো করে gcloud iam service-accounts create কমান্ডটি চালান:
    gcloud iam service-accounts create quickstart-service-account \
      --display-name="Quickstart Service Account"
    
  2. আপনার সদ্য তৈরি করা Google Cloud পরিষেবা অ্যাকাউন্টে Cloud SQL Client রোলটি যোগ করতে, নিম্নলিখিতভাবে gcloud projects add-iam-policy-binding কমান্ডটি চালান।
    gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
      --member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
      --role="roles/cloudsql.client"
    
  3. আপনার সদ্য তৈরি করা Google Cloud পরিষেবা অ্যাকাউন্টে Cloud SQL Instance User রোলটি যোগ করতে, নিম্নলিখিতভাবে gcloud projects add-iam-policy-binding কমান্ডটি চালান।
    gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
      --member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
      --role="roles/cloudsql.instanceUser"
    
  4. আপনার সদ্য তৈরি করা Google Cloud পরিষেবা অ্যাকাউন্টে Log Writer রোলটি যোগ করতে, নিম্নলিখিতভাবে gcloud projects add-iam-policy-binding কমান্ডটি চালান।
    gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
      --member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
      --role="roles/logging.logWriter"
    

৭. ক্লাউড SQL ডেটাবেস তৈরি করুন

  1. একটি ক্লাউড এসকিউএল ইনস্ট্যান্স তৈরি করতে gcloud sql instances create কমান্ডটি চালান।
    gcloud sql instances create quickstart-instance \
        --database-version=POSTGRES_14 \
        --cpu=4 \
        --memory=16GB \
        --region=us-central1 \
        --database-flags=cloudsql.iam_authentication=on
    

এই কমান্ডটি সম্পন্ন হতে কয়েক মিনিট সময় লাগতে পারে।

  1. quickstart-instance মধ্যে একটি ক্লাউড এসকিউএল ডাটাবেস তৈরি করতে gcloud sql databases create কমান্ডটি চালান।
    gcloud sql databases create quickstart_db \
        --instance=quickstart-instance
    
  2. ডাটাবেস অ্যাক্সেস করার জন্য, আপনার পূর্বে তৈরি করা সার্ভিস অ্যাকাউন্টের জন্য একটি PostgreSQL ডাটাবেস ইউজার তৈরি করুন।
    gcloud sql users create quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam \
        --instance=quickstart-instance \
        --type=cloud_iam_service_account
    

৮. আবেদনপত্র প্রস্তুত করুন।

একটি Next.js অ্যাপ্লিকেশন তৈরি করুন যা HTTP অনুরোধে সাড়া দেয়।

  1. task-app নামের একটি নতুন Next.js প্রজেক্ট তৈরি করতে, এই কমান্ডটি ব্যবহার করুন:
    npx --yes @angular/cli@19.2.5 new task-app \
        --minimal \
        --inline-template \
        --inline-style \
        --ssr \
        --server-routing \
        --defaults
    
  2. task-app ডিরেক্টরি পরিবর্তন করুন:
    cd task-app
    
  1. PostgreSQL ডাটাবেসের সাথে সংযোগ স্থাপনের জন্য pg এবং Cloud SQL Node.js কানেক্টর লাইব্রেরি ইনস্টল করুন।
    npm install pg @google-cloud/cloud-sql-connector google-auth-library
    
  2. TypeScript Next.js অ্যাপ্লিকেশন ব্যবহার করতে @types/pg ডেভ ডিপেন্ডেন্সি হিসেবে ইনস্টল করুন।
    npm install --save-dev @types/pg
    
  1. ক্লাউড শেল এডিটরে server.ts ফাইলটি খুলুন:
    cloudshell edit src/server.ts
    
    এখন স্ক্রিনের উপরের অংশে একটি ফাইল দেখা যাবে। এখানেই আপনি server.ts ফাইলটি সম্পাদনা করতে পারবেন। দেখান যে কোডটি স্ক্রিনের উপরের অংশে থাকবে।
  2. server.ts ফাইলের বিদ্যমান বিষয়বস্তু মুছে ফেলুন।
  3. নিচের কোডটি কপি করে খোলা server.ts ফাইলে পেস্ট করুন:
    import {
      AngularNodeAppEngine,
      createNodeRequestHandler,
      isMainModule,
      writeResponseToNodeResponse,
    } from '@angular/ssr/node';
    import express from 'express';
    import { dirname, resolve } from 'node:path';
    import { fileURLToPath } from 'node:url';
    import pg from 'pg';
    import { AuthTypes, Connector } from '@google-cloud/cloud-sql-connector';
    import { GoogleAuth } from 'google-auth-library';
    const auth = new GoogleAuth();
    
    const { Pool } = pg;
    
    type Task = {
      id: string;
      title: string;
      status: 'IN_PROGRESS' | 'COMPLETE';
      createdAt: number;
    };
    
    const projectId = await auth.getProjectId();
    
    const connector = new Connector();
    const clientOpts = await connector.getOptions({
      instanceConnectionName: `${projectId}:us-central1:quickstart-instance`,
      authType: AuthTypes.IAM,
    });
    
    const pool = new Pool({
      ...clientOpts,
      user: `quickstart-service-account@${projectId}.iam`,
      database: 'quickstart_db',
    });
    
    const tableCreationIfDoesNotExist = async () => {
      await pool.query(`CREATE TABLE IF NOT EXISTS tasks (
          id SERIAL NOT NULL,
          created_at timestamp NOT NULL,
          status VARCHAR(255) NOT NULL default 'IN_PROGRESS',
          title VARCHAR(1024) NOT NULL,
          PRIMARY KEY (id)
        );`);
    }
    
    const serverDistFolder = dirname(fileURLToPath(import.meta.url));
    const browserDistFolder = resolve(serverDistFolder, '../browser');
    
    const app = express();
    const angularApp = new AngularNodeAppEngine();
    
    app.use(express.json());
    
    app.get('/api/tasks', async (req, res) => {
      await tableCreationIfDoesNotExist();
      const { rows } = await pool.query(`SELECT id, created_at, status, title FROM tasks ORDER BY created_at DESC LIMIT 100`);
      res.send(rows);
    });
    
    app.post('/api/tasks', async (req, res) => {
      const newTaskTitle = req.body.title;
      if (!newTaskTitle) {
        res.status(400).send("Title is required");
        return;
      }
      await tableCreationIfDoesNotExist();
      await pool.query(`INSERT INTO tasks(created_at, status, title) VALUES(NOW(), 'IN_PROGRESS', $1)`, [newTaskTitle]);
      res.sendStatus(200);
    });
    
    app.put('/api/tasks', async (req, res) => {
      const task: Task = req.body;
      if (!task || !task.id || !task.title || !task.status) {
        res.status(400).send("Invalid task data");
        return;
      }
      await tableCreationIfDoesNotExist();
      await pool.query(
        `UPDATE tasks SET status = $1, title = $2 WHERE id = $3`,
        [task.status, task.title, task.id]
      );
      res.sendStatus(200);
    });
    
    app.delete('/api/tasks', async (req, res) => {
      const task: Task = req.body;
      if (!task || !task.id) {
        res.status(400).send("Task ID is required");
        return;
      }
      await tableCreationIfDoesNotExist();
      await pool.query(`DELETE FROM tasks WHERE id = $1`, [task.id]);
      res.sendStatus(200);
    });
    
    /**
    * Serve static files from /browser
    */
    app.use(
      express.static(browserDistFolder, {
        maxAge: '1y',
        index: false,
        redirect: false,
      }),
    );
    
    /**
    * Handle all other requests by rendering the Angular application.
    */
    app.use('/**', (req, res, next) => {
      angularApp
        .handle(req)
        .then((response) =>
          response ? writeResponseToNodeResponse(response, res) : next(),
        )
        .catch(next);
    });
    
    /**
    * Start the server if this module is the main entry point.
    * The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.
    */
    if (isMainModule(import.meta.url)) {
      const port = process.env['PORT'] || 4000;
      app.listen(port, () => {
        console.log(`Node Express server listening on http://localhost:${port}`);
      });
    }
    
    /**
    * Request handler used by the Angular CLI (for dev-server and during build) or Firebase Cloud Functions.
    */
    export const reqHandler = createNodeRequestHandler(app);
    
  1. ক্লাউড শেল এডিটরে app.component.ts ফাইলটি খুলুন:
    cloudshell edit src/app/app.component.ts
    
    এখন স্ক্রিনের উপরের অংশে একটি বিদ্যমান ফাইল দেখা যাবে। এখানেই আপনি app.component.ts ফাইলটি সম্পাদনা করতে পারবেন। দেখান যে কোডটি স্ক্রিনের উপরের অংশে থাকবে।
  2. app.component.ts ফাইলের বিদ্যমান বিষয়বস্তু মুছে ফেলুন।
  3. নিচের কোডটি কপি করে খোলা app.component.ts ফাইলে পেস্ট করুন:
    import { afterNextRender, Component, signal } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    type Task = {
      id: string;
      title: string;
      status: 'IN_PROGRESS' | 'COMPLETE';
      createdAt: number;
    };
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [FormsModule],
      template: `
        <section>
          <input
            type="text"
            placeholder="New Task Title"
            [(ngModel)]="newTaskTitle"
            class="text-black border-2 p-2 m-2 rounded"
          />
          <button (click)="addTask()">Add new task</button>
          <table>
            <tbody>
              @for (task of tasks(); track task) {
                @let isComplete = task.status === 'COMPLETE';
                <tr>
                  <td>
                    <input
                      (click)="updateTask(task, { status: isComplete ? 'IN_PROGRESS' : 'COMPLETE' })"
                      type="checkbox"
                      [checked]="isComplete"
                    />
                  </td>
                  <td>{{ task.title }}</td>
                  <td>{{ task.status }}</td>
                  <td>
                    <button (click)="deleteTask(task)">Delete</button>
                  </td>
                </tr>
              }
            </tbody>
          </table>
        </section>
      `,
      styles: '',
    })
    export class AppComponent {
      newTaskTitle = '';
      tasks = signal<Task[]>([]);
    
      constructor() {
        afterNextRender({
          earlyRead: () => this.getTasks()
        });
      }
    
      async getTasks() {
        const response = await fetch(`/api/tasks`);
        const tasks = await response.json();
        this.tasks.set(tasks);
      }
    
      async addTask() {
        await fetch(`/api/tasks`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            title: this.newTaskTitle,
            status: 'IN_PROGRESS',
            createdAt: Date.now(),
          }),
        });
        this.newTaskTitle = '';
        await this.getTasks();
      }
    
      async updateTask(task: Task, newTaskValues: Partial<Task>) {
        await fetch(`/api/tasks`, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ ...task, ...newTaskValues }),
        });
        await this.getTasks();
      }
    
      async deleteTask(task: any) {
        await fetch('/api/tasks', {
          method: 'DELETE',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(task),
        });
        await this.getTasks();
      }
    }
    

অ্যাপ্লিকেশনটি এখন ডেপ্লয় করার জন্য প্রস্তুত।

৯. অ্যাপ্লিকেশনটি ক্লাউড রান-এ স্থাপন করুন।

  1. আপনার অ্যাপ্লিকেশনটি ক্লাউড রান-এ ডেপ্লয় করতে নিচের কমান্ডটি চালান:
    gcloud run deploy to-do-tracker \
        --region=us-central1 \
        --source=. \
        --service-account="quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
        --allow-unauthenticated
    
  2. অনুরোধ করা হলে, আপনি যে চালিয়ে যেতে চান তা নিশ্চিত করতে Y এবং Enter চাপুন:
    Do you want to continue (Y/n)? Y
    

কয়েক মিনিট পর অ্যাপ্লিকেশনটি আপনাকে ভিজিট করার জন্য একটি URL প্রদান করবে।

আপনার অ্যাপ্লিকেশনটি বাস্তবে দেখতে ইউআরএল-টিতে যান। প্রতিবার ইউআরএল-টিতে গেলে বা পৃষ্ঠাটি রিফ্রেশ করলে, আপনি টাস্ক অ্যাপটি দেখতে পাবেন।

১০. অভিনন্দন

এই ল্যাবে, আপনি নিম্নলিখিত বিষয়গুলো করতে শিখেছেন:

  • একটি ক্লাউড এসকিউএল ফর পোস্টগ্রেসকিউএল ইনস্ট্যান্স তৈরি করুন
  • ক্লাউড রান-এ এমন একটি অ্যাপ্লিকেশন স্থাপন করুন যা আপনার ক্লাউড SQL ডেটাবেসের সাথে সংযোগ স্থাপন করে।

পরিষ্কার করা

ক্লাউড এসকিউএল-এর কোনো ফ্রি টিয়ার নেই এবং এটি ব্যবহার করতে থাকলে আপনাকে চার্জ করা হবে। অতিরিক্ত চার্জ এড়ানোর জন্য আপনি আপনার ক্লাউড প্রজেক্টটি ডিলিট করে দিতে পারেন।

পরিষেবাটি ব্যবহার না করা হলে ক্লাউড রান কোনো চার্জ না করলেও, আর্টিফ্যাক্ট রেজিস্ট্রি-তে কন্টেইনার ইমেজ সংরক্ষণের জন্য আপনাকে চার্জ করা হতে পারে। আপনার ক্লাউড প্রজেক্টটি ডিলিট করে দিলে সেই প্রজেক্টের মধ্যে ব্যবহৃত সমস্ত রিসোর্সের বিলিং বন্ধ হয়ে যায়।

আপনি চাইলে প্রজেক্টটি মুছে ফেলতে পারেন:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

আপনি আপনার ক্লাউডশেল ডিস্ক থেকে অপ্রয়োজনীয় রিসোর্সগুলো মুছে ফেলতে চাইতে পারেন। আপনি যা করতে পারেন:

  1. কোডল্যাব প্রজেক্ট ডিরেক্টরিটি মুছে ফেলুন:
    rm -rf ~/task-app
    
  2. সতর্কীকরণ! এই পরবর্তী কাজটি পূর্বাবস্থায় ফেরানো যাবে না! জায়গা খালি করার জন্য আপনি যদি আপনার ক্লাউড শেলের সবকিছু মুছে ফেলতে চান, তাহলে আপনি আপনার সম্পূর্ণ হোম ডিরেক্টরিটি ডিলিট করে দিতে পারেন। খেয়াল রাখবেন, আপনি যা কিছু রাখতে চান তা যেন অন্য কোথাও সেভ করা থাকে।
    sudo rm -rf $HOME
    

শিখতে থাকুন