TypeScript とネストを使用して URL 短縮サービスを構築およびデプロイする方法.js

Dockerでは、活気に満ちた多様で創造的なコミュニティを非常に誇りに思っています。 時々、私たちはブログでコミュニティからのクールな貢献を特集し、私たちのコミュニティが行っている素晴らしい仕事のいくつかを強調しています。 Dockerで何か素晴らしいことに取り組んでいますか? あなたの貢献をアジート・シン・ライナ(@ajeetraina)に送ってください Docker Community Slack そして、私たちはあなたの作品を特集するかもしれません!

 

過去5年間、タイプスクリプトの 人気が急上昇 エンタープライズ開発者の間で。 で スタックオーバーフローの2022年開発者調査、TypeScriptは「最も求められている」カテゴリで3位にランクされました。 スタックオーバーフローはこの区別を予約します 特定の言語やテクノロジで開発していないが、開発に関心を示している開発者。

 

画像6
スタックオーバーフローのデータ提供。

 

TypeScriptの段階的な採用は、開発者コードの品質とわかりやすさの向上によるものです。 全体として、Typescriptは開発者がコードを完全に文書化することを奨励し、使いやすさを通じて自信を高めます.タイプスクリプト 提供 すべての最新のJavaScript機能 つつ 次のような強力な概念を紹介します インターフェイス, 労働 組合 そして 交差タイプ.それ 実行時に失敗させるのではなく、コンパイル中に構文エラーを明確に表示することで、開発者の生産性を向上させます。

ただし、すべてのプログラミング言語には特定の欠点があり、TypeScriptも例外ではないことを忘れないでください。 Lコンパイル時間と新しいJavaScriptユーザーの学習曲線が急であることは、最も注目に値します。 良いニュースは、TypeScriptの長所が理想的なユースケースの短所を上回っていることです。 そのうちの1つに今すぐ取り組みます。 

アプリケーションのビルド

このチュートリアルでは、TypeScript と Nest を使用して基本的な URL 短縮サービスを最初から作成する方法を学習します。

まず、Docker を使用せずに Nest で基本的なアプリケーションを作成します。このアプリケーションで Redis バックエンドを使用して、Nest と TypeScript で単純な URL 短縮サービスを構築する方法について説明します。次に、Docker Compose を使用して、Nest.js、TypeScript、Redis バックエンドを共同で実行してマイクロサービスを強化する方法について説明します。飛び込みましょう。

始める

このチュートリアルを完了するには、次の主要コンポーネントが不可欠です。

 

開始する前に、次のことを確認してください システムにインストールされているノード.次に、次の手順に従って、TypeScript を使用して単純な Web アプリケーションを構築します。

ネストプロジェクトの作成

Nest は現在、JavaScript エコシステムで最も急速に成長しているサーバーサイド開発フレームワークです。 スケーラブルでテスト可能な疎結合アプリケーションの作成に最適です。 Nest は、一般的な Node.js フレームワークよりも抽象化レベルが高く、その API を開発者に公開します。 内部的には、Nestは Express (デフォルト) のような堅牢なHTTPサーバーフレームワークを利用しており、 オプションで Fastify を使用することもできます。 PostgreSQL、MongoDB、MySQLなどのデータベースをサポートしています。NestJSは、Angular、React、Vueの影響を強く受けていますが、すぐに依存性注入を提供します。

初めてのユーザーには、Nest CLI を使用して新しいプロジェクトを作成することをおすすめします。まず、次のコマンドを入力して Nest CLI をインストールします。

npm install -g @nestjs/cli

 

次に、という名前の backend新しい Nest.js プロジェクト ディレクトリを作成しましょう。

mkdir backend

 

ディレクトリに最初のコアNestファイルとサポートモジュールを入力します。 新しいバックエンド ディレクトリから、Nest のブートストラップ コマンドを実行します。 新しいアプリケーションを link-shortener呼び出します。

nest new link-shortener

⚡  We will scaffold your app in a few seconds..

CREATE link-shortener/.eslintrc.js (665 bytes)
CREATE link-shortener/.prettierrc (51 bytes)
CREATE link-shortener/README.md (3340 bytes)
CREATE link-shortener/nest-cli.json (118 bytes)
CREATE link-shortener/package.json (1999 bytes)
CREATE link-shortener/tsconfig.build.json (97 bytes)
CREATE link-shortener/tsconfig.json (546 bytes)
CREATE link-shortener/src/app.controller.spec.ts (617 bytes)
CREATE link-shortener/src/app.controller.ts (274 bytes)
CREATE link-shortener/src/app.module.ts (249 bytes)
CREATE link-shortener/src/app.service.ts (142 bytes)
CREATE link-shortener/src/main.ts (208 bytes)
CREATE link-shortener/test/app.e2e-spec.ts (630 bytes)
CREATE link-shortener/test/jest-e2e.json (183 bytes)

? Which package manager would you ❤️  to use? (Use arrow keys)
❯ npm 
  yarn 
  pnpm 

 

3 つのパッケージ マネージャーはすべて使用できますが、このチュートリアルではこの目的で選択します npm

Which package manager would you ❤️  to use? npm
✔ Installation in progress... ☕

🚀  Successfully created project link-shortener
👉  Get started with the following commands:

$ cd link-shortener
$ npm run start

                                         
                          Thanks for installing Nest 🙏
                 Please consider donating to our open collective
                        to help us maintain this package.                     
               🍷  Donate: https://opencollective.com/nest

 

コマンドが正常に実行されると、ノードモジュールと他のいくつかの定型ファイルを含む新しい link-shortener プロジェクトディレクトリが作成されます。また、次のディレクトリ構造に示すように、いくつかのコアファイルが設定された新しい src/ ディレクトリも作成されます。

tree -L 2 -a
.
└── link-shortener
    ├── dist
    ├── .eslintrc.js
    ├── .gitignore
    ├── nest-cli.json
    ├── node_modules
    ├── package.json
    ├── package-lock.json
    ├── .prettierrc
    ├── README.md
    ├── src
    ├── test
    ├── tsconfig.build.json
    └── tsconfig.json

5 directories, 9 files

 

.ts 終わるコアファイルを見てみましょう (タイプスクリプト)ディレクトリの下 /src:

src % tree
.
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts

0 directories, 5 files

 

ネストはモジュール性を取り入れています。したがって、最も重要な Nest アプリ コンポーネントの 2 つは、コントローラーとプロバイダーです。 コントローラーは、着信要求の処理方法を決定します。 受信要求を受け入れ、何らかの操作を実行し、応答を返す役割を担います。 一方、プロバイダーは、コントローラーまたは特定のプロバイダーに挿入できる追加のクラスです。 これにより、さまざまな補足機能が付与されます。 プロバイダーコントローラー を読んで、それらがどのように機能するかをよりよく理解することを常にお勧めします。

これは app.module.ts アプリケーションのルートモジュールであり、コントローラが使用するいくつかのコントローラとプロバイダをバンドルします。

cat app.module.ts  
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

上記のファイルに示されているように、 AppModule は単なる空のクラスです。 Nestの デコレータは、Nestがそこから機能的なアプリケーションを構築できるようにする構成を提供する責任があります。 @Module

まずは app.controller.ts 単一のルートを持つ基本コントローラをエクスポートします。 は app.controller.spec.ts 、コントローラーの単体テストです。 2つ目は、 app.service.ts 単一のメソッドを持つ基本的なサービスです。 3つ目は、 main.ts アプリケーションのエントリファイルです。を呼び出して NestFactory.createアプリケーションをブートストラップし、 でインバウンド HTTP 要求をリッスンさせることで新しいアプリケーションを起動します。 port 3000.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap(); 

 

アプリケーションの実行

インストールが完了したら、次のコマンドを実行してアプリケーションを起動します。

npm run start

> [email protected] start
> nest start

[Nest] 68686  - 05/31/2022, 5:50:59 PM     LOG [NestFactory] Starting Nest application...
[Nest] 68686  - 05/31/2022, 5:50:59 PM     LOG [InstanceLoader] AppModule dependencies initialized +24ms
[Nest] 68686  - 05/31/2022, 5:50:59 PM     LOG [RoutesResolver] AppController {/}: +4ms
[Nest] 68686  - 05/31/2022, 5:50:59 PM     LOG [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 68686  - 05/31/2022, 5:50:59 PM     LOG [NestApplication] Nest application successfully started +1ms

このコマンドは、で定義されたポートでリッスンしている HTTP サーバーでアプリを起動します。 src/main.ts ファイル。 アプリケーションが正常に実行されたら、ブラウザを開いて http://localhost:3000.「こんにちは世界! メッセージ:

画像5

リポジトリの追加

リポジトリは、オブジェクトの保存を担当するレイヤーです。 ここでは、ハッシュとその元のURLの間のマッピングを格納するリポジトリレイヤーが必要です。

まず、リポジトリのインターフェイスを作成しましょう。 という名前のファイルを作成します。 app.repository.ts 次のように入力します。

import { Observable } from 'rxjs';

export interface AppRepository {
  put(hash: string, url: string): Observable<string>;
  get(hash: string): Observable<string>;
}

export const AppRepositoryTag = 'AppRepository';
[javascript]</pre>
&nbsp;
<p class="c9"><span class="c6">Now, let's create a simple repository that stores the mappings in a hashmap in the memory. Create a file named <code>app.repository.hashmap.ts</code>:</span></p>

<pre>[javascript]
import { AppRepository } from './app.repository';
import { Observable, of } from 'rxjs';

export class AppRepositoryHashmap implements AppRepository {
  private readonly hashMap: Map<string, string>;

  constructor() {
    this.hashMap = new Map<string, string>();
  }

  get(hash: string): Observable<string> {
    return of(this.hashMap.get(hash));
  }

  put(hash: string, url: string): Observable<string> {
    return of(this.hashMap.set(hash, url).get(hash));
  }
}

それでは、ネストに指示しましょう.js もし頼まれた AppRepositoryTag ら それらを提供する AppRepositoryHashMap。 まず、でそれを app.module.tsやってみましょう:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AppRepositoryTag } from './app.repository';
import { AppRepositoryHashmap } from './app.repository.hashmap';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    AppService,
    { provide: AppRepositoryTag, useClass: AppRepositoryHashmap }, // <-- here
  ],
})
export class AppModule {}

次に、新しいエンドポイントの新しいテストを次の場所 app.service.spec.tsに追加しましょう。

import { Test, TestingModule } from "@nestjs/testing";
import { AppService } from "./app.service";
import { AppRepositoryTag } from "./app.repository";
import { AppRepositoryHashmap } from "./app.repository.hashmap";
import { mergeMap, tap } from "rxjs";

describe('AppService', () => {
  let appService: AppService;

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      providers: [
        { provide: AppRepositoryTag, useClass: AppRepositoryHashmap },
        AppService,
      ],
    }).compile();

    appService = app.get<AppService>(AppService);
  });

  describe('retrieve', () => {
    it('should retrieve the saved URL', done => {
      const url = 'docker.com';
      appService.shorten(url)
        .pipe(mergeMap(hash => appService.retrieve(hash)))
        .pipe(tap(retrieved => expect(retrieved).toEqual(url)))
        .subscribe({ complete: done })
    });
  });
});

 

テストを実行する前に、次の関数 app.service.tsを実装しましょう。

import { Inject, Injectable } from '@nestjs/common';
import { map, Observable } from 'rxjs';
import { AppRepository, AppRepositoryTag } from './app.repository';

@Injectable()
export class AppService {
  constructor(
    @Inject(AppRepositoryTag) private readonly appRepository: AppRepository,
  ) {}

  getHello(): string {
    return 'Hello World!';
  }

  shorten(url: string): Observable<string> {
    const hash = Math.random().toString(36).slice(7);
    return this.appRepository.put(hash, url).pipe(map(() => hash)); // <-- here
  }

  retrieve(hash: string): Observable<string> {
    return this.appRepository.get(hash); // <-- and here
  }
}

実際のデータベースへのデータの保存を開始する前に、これらのテストをもう一度実行して、すべてが合格することを確認します。

データベースの追加

これまでのところ、マッピングをメモリに格納しているだけです。 これはテストには問題ありませんが、本番環境ではより集中的で耐久性のある場所に保管する必要があります。 Docker Hub で入手できる人気のキーバリューストアである Redis を使用します。

ディレクトリ から backend/link-shortener 次のコマンドを実行して、このRedisクライアントをインストールしましょう 。

npm install [email protected] --save

 

中に /srcで、Redis を使用する新しいバージョンの AppRepository インターフェイスを作成します。 このファイルを app.repository.redis.ts次のように呼び出します。

import { AppRepository } from './app.repository';
import { Observable, from, mergeMap } from 'rxjs';
import { createClient, RedisClientType } from 'redis';

export class AppRepositoryRedis implements AppRepository {
  private readonly redisClient: RedisClientType;

  constructor() {
    const host = process.env.REDIS_HOST || 'redis';
    const port = +process.env.REDIS_PORT || 6379;
    this.redisClient = createClient({
      url: `redis://${host}:${port}`,
    });
    from(this.redisClient.connect()).subscribe({ error: console.error });
    this.redisClient.on('connect', () => console.log('Redis connected'));
    this.redisClient.on('error', console.error);
  }

  get(hash: string): Observable<string> {
    return from(this.redisClient.get(hash));
  }

  put(hash: string, url: string): Observable<string> {
    return from(this.redisClient.set(hash, url)).pipe(
            mergeMap(() => from(this.redisClient.get(hash))),
    );
  }
}

 

最後に、プロバイダーを変更します app.module.ts インメモリバージョンから新しいRedisリポジトリへ:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AppRepositoryTag } from './app.repository';
import { AppRepositoryRedis } from "./app.repository.redis";

@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    AppService,
    { provide: AppRepositoryTag, useClass: AppRepositoryRedis }, // <-- here
  ],
})
export class AppModule {}

 

バックエンドのファイナライズ

戻る app.controller.ts リダイレクト用の別のエンドポイントを作成します。

import { Body, Controller, Get, Param, Post, Redirect } from '@nestjs/common';
import { AppService } from './app.service';
import { map, Observable, of } from 'rxjs';

interface ShortenResponse {
  hash: string;
}

interface ErrorResponse {
  error: string;
  code: number;
}

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Post('shorten')
  shorten(@Body('url') url: string): Observable<ShortenResponse | ErrorResponse> {
    if (!url) {
      return of({ error: `No url provided. Please provide in the body. E.g. {'url':'https://google.com'}`, code: 400 });
    }
    return this.appService.shorten(url).pipe(map(hash => ({ hash })));
  }

  @Get(':hash')
  @Redirect()
  retrieveAndRedirect(@Param('hash') hash): Observable<{ url: string }> {
    return this.appService.retrieve(hash).pipe(map(url => ({ url })));
  }
}

 

ここをクリック をクリックして、この例で以前に開発したコードにアクセスします。

TypeScript アプリケーションのコンテナ化

Docker を使用すると、TypeScript アプリをコンテナ化し、TypeScript アプリケーション、ランタイム、構成、および OS レベルの依存関係全体をバンドルできます。 これには、クロスプラットフォームのマルチアーキテクチャWebアプリケーションを出荷するために必要なすべてのものが含まれます。 

Docker公式イメージを使用して、Dockerコンテナ内でこのアプリを簡単に実行する方法を見てみましょう。 まず、次のことを行う必要があります ダウンロード ドッカーデスクトップ.Docker Desktop は、有用なイメージを見つけやすくしながら、イメージ構築プロセスを高速化します。 ダウンロードが完了したら、インストールプロセスを完了します。

ドッカーは ドッカーファイル をクリックして、画像の「レイヤー」を指定します。 各レイヤーには、基本イメージの標準構成に基づいて構築された重要な変更が格納されます。 Nest プロジェクトに次の空 Dockerfile を作成します。

 

タッチドッカーファイル

 

お気に入りのテキストエディタを使用してこれ Dockerfileを開きます。 次に、基本イメージを定義する必要があります。 また、イメージのアプリケーションコードを格納するディレクトリをすばやく作成しましょう。 これは、アプリケーションの作業ディレクトリとして機能します。

 

WORKDIR /app

 

以下 写し 命令は、ホストマシンからコンテナイメージにファイルをコピーします。

 

写し。.

 

最後に、この最後の行は、アプリケーションパッケージをコンパイルして実行するようにDockerに指示します。

 

CMD["npm", "run", "start:dev"]

 

これがあなたの完全 Dockerfileです:

FROM node:16
COPY . .
WORKDIR /app
RUN npm install
EXPOSE 3000
CMD ["npm", "run", "start:dev"]

 

サンプルの TypeScript アプリを構築する Dockerfile 方法を効果的に学習しました。 次に、このアプリケーションに関連するDocker作成ファイルを作成する方法を見てみましょう。 港湾労働者 Compose は、マルチコンテナーの Docker アプリケーションを定義および実行するためのツールです。 作成では、YAML ファイルを使用してサービスを構成します。 その後、1 つのコマンドで、構成からすべてのサービスを作成して開始できます。

作成ファイルを使用したサービスの定義

サービスを定義する時が来ました Docker Compose ファイル:

services:
  redis:
    image: 'redis/redis-stack'
    ports:
      - '6379:6379'
      - '8001:8001'
    networks:
      - urlnet
  dev:
    build:
      context: ./backend/link-shortener
      dockerfile: Dockerfile
    environment:
      REDIS_HOST: redis
      REDIS_PORT: 6379
    ports:
      - '3000:3000'
    volumes:
      - './backend/link-shortener:/app'
    depends_on:
      - redis
    networks:
      - urlnet

networks:
  urlnet:

 

サンプル アプリケーションには、次の部分があります。

  • Docker イメージによってサポートされる 2 つのサービス: フロントエンド dev アプリとバックエンド データベース redis
  • redis/redis-stack Docker イメージは Redis の拡張機能であり、最新のデータ モデルと処理エンジンを追加して、完全な開発者エクスペリエンスを提供します。 私たちは使用します port 8001 RedisInsight の場合 — Redisデータを理解して最適化するための視覚化ツール。
  • フロントエンド、アクセス可能 port 3000
  • depends_on パラメーターを使用して、フロントエンド サービスの開始前にバックエンド サービスを作成できます
  • バックエンドに接続された 1 つの永続ボリューム
  • Redis データベースの環境変数

 

前のセクションで実行したフロントエンドとバックエンドのサービスを停止したら、 次のコマンドを使用して docker-compose up 、サービスをビルドして開始しましょう。

docker compose up -d –build

 

手記: Docker Compose v1 を使用している場合、コマンド ライン構文は次のようになります。 ドッカー-作成 をハイフンで囲みます。 Docker Desktop に付属している v2 を使用している場合は、ハイフンが省略され、 docker compose 正しいです。 

docker compose ps
NAME                        COMMAND                  SERVICE             STATUS              PORTS
link-shortener-js-dev-1     "docker-entrypoint.s…"   dev                 running             0.0.0.0:3000-&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;3000/tcp
link-shortener-js-redis-1   "/entrypoint.sh"         redis               running             0.0.0.0:6379-&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;6379/tcp, 0.0.0.0:8001-&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;8001/tcp

 

これで、TypeScript URL 短縮サービスを作成してデプロイできました。 これは以前と同じようにブラウザで使用できます。 https:// でアプリケーションにアクセスする場合ローカルホスト:3000、 わかりやすい「Hello World!」というメッセージが表示されます。 次のコマンドを使用して、 curl 新しいリンクを短縮します。

curl -XPOST -d "url=https://docker.com" localhost:3000/shorten

 

これがあなたの応答です:

{"hash":"l6r71d"}

 

このハッシュは、マシンによって異なる場合があります。 これを使用して、元のリンクにリダイレクトできます。 任意のWebブラウザを開き、https:// にアクセスしますローカルホスト:3000 / L6R71D をクリックして Docker のウェブサイトにアクセスします。

リディスキーの表示

Redis キーを表示するには、https://localhost:8001 にアクセスして RedisInsight ツールを使用します

 

画像1

作成ログの表示

docker compose logs -fを使用して、 作成ログを確認および表示できます。

[6:17:19 AM] Starting compilation in watch mode...
link-shortener-js-dev-1    | 
link-shortener-js-dev-1    | [6:17:22 AM] Found 0 errors. Watching for file changes.
link-shortener-js-dev-1    | 
link-shortener-js-dev-1    | [Nest] 31  - 06/18/2022, 6:17:23 AM     LOG [NestFactory] Starting Nest application...
link-shortener-js-dev-1    | [Nest] 31  - 06/18/2022, 6:17:23 AM     LOG [InstanceLoader] AppModule dependencies initialized +21ms
link-shortener-js-dev-1    | [Nest] 31  - 06/18/2022, 6:17:23 AM     LOG [RoutesResolver] AppController {/}: +3ms
link-shortener-js-dev-1    | [Nest] 31  - 06/18/2022, 6:17:23 AM     LOG [RouterExplorer] Mapped {/, GET} route +1ms
link-shortener-js-dev-1    | [Nest] 31  - 06/18/2022, 6:17:23 AM     LOG [RouterExplorer] Mapped {/shorten, POST} route +0ms
link-shortener-js-dev-1    | [Nest] 31  - 06/18/2022, 6:17:23 AM     LOG [RouterExplorer] Mapped {/:hash, GET} route +1ms
link-shortener-js-dev-1    | [Nest] 31  - 06/18/2022, 6:17:23 AM     LOG [NestApplication] Nest application successfully started +1ms
link-shortener-js-dev-1    | Redis connected

また、Docker ダッシュボードを利用してコンテナーの ID を表示し、アプリケーションに簡単にアクセスまたは管理することもできます。

 

画像3

 

Dockerダッシュボードから重要なログを調べることもできます。

 

画像2

結論

万丈!TypeScript と Nest を使用して URL 短縮サービスを構築してデプロイする方法を学習しました。 1 つの YAML ファイルを使用して、Docker Compose を使用して TypeScript ベースの URL 短縮アプリを数秒で簡単に構築してデプロイする方法を示しました。 いくつかの追加手順を実行するだけで、このチュートリアルを適用しながら、はるかに複雑なアプリケーションを構築できます。

ハッピーコーディング。