ウェブサイト検索

NodeJSとNestJSを使って型安全なURL短縮サービスを構築する方法


著者は「Write for DOnations」プログラムの一環として、寄付を受けるためにテクノロジー教育基金を選びました。

紹介

URLは、Uniform Resource Locatorの略で、ウェブ上のユニークなリソースに与えられたアドレスです。URLはユニークであるため、同じURLを持つ2つのリソースは存在しません。

URLの長さと複雑さはさまざまです。URLはexample.comのように短いものもあれば、http://llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.co.ukのように長いものもあります。複雑なURLは見た目が悪く、検索エンジン最適化(SEO)の問題を引き起こし、マーケティング計画に悪影響を与えることがあります。URL短縮サービスは、長いURLを短いURLにマッピングし、短いURLが使用されると元のURLにリダイレクトします。

このチュートリアルでは、NestJSを使用してURL短縮機を作成します。まず、サービス内でURLの短縮とリダイレクトのロジックを実装します。次に、短縮とリダイレクトのリクエストを容易にするためのルートハンドラーを作成します。

前提条件

このチュートリアルに従うためには、次のものが必要です:

  • Node.js バージョン 16 以上のローカル開発環境。自分のシステムに適した「Node.js のインストール方法とローカル開発環境の作成」に関するチュートリアルに従ってください。
  • あなたのシステムにインストールされたNestJS CLI(ステップ1で設定します)とNestJSに対する理解が必要です。NestJSの初め方を確認してください。
  • TypeScriptに親しみがあります。「TypeScriptでコードを書く方法」シリーズを見て、さらに学ぶことができます。

ステップ1 — 開発環境の準備

このステップでは、URL短縮ロジックを実装するために必要なすべてをセットアップします。グローバルにNestJSをインストールし、新しいNestJSアプリケーションのボイラープレートを生成し、依存関係をインストールし、プロジェクトのモジュール、サービス、およびコントローラーを作成します。

まず、以前にインストールしていない場合は、Nest CLIをグローバルにインストールします。このCLIを使用して、プロジェクトディレクトリと必要なファイルを生成します。次のコマンドを実行して、Nest CLIをインストールしてください:

  1. npm install -g @nestjs/cli

-g フラグは、Nest CLI をシステムにグローバルにインストールします。

次の出力が表示されます:

  1. ...
  2. added 249 packages, and audited 250 packages in 3m
  3. 39 packages are looking for funding
  4. run npm fund for details
  5. found 0 vulnerabilities

それから、newコマンドを使用してプロジェクトを作成し、必要なボイラープレートのスターターファイルを生成します:

  1. nest new URL-shortener

次の出力が表示されます:

  1. ...
  2. ⚡ We will scaffold your app in a few seconds..
  3. CREATE url-shortener/.eslintrc.js (631 bytes)
  4. CREATE url-shortener/.prettierrc (51 bytes)
  5. CREATE url-shortener/nest-cli.json (118 bytes)
  6. CREATE url-shortener/package.json (2002 bytes)
  7. CREATE url-shortener/README.md (3339 bytes)
  8. CREATE url-shortener/tsconfig.build.json (97 bytes)
  9. CREATE url-shortener/tsconfig.json (546 bytes)
  10. CREATE url-shortener/src/app.controller.spec.ts (617 bytes)
  11. CREATE url-shortener/src/app.controller.ts (274 bytes)
  12. CREATE url-shortener/src/app.module.ts (249 bytes)
  13. CREATE url-shortener/src/app.service.ts (142 bytes)
  14. CREATE url-shortener/src/main.ts (208 bytes)
  15. CREATE url-shortener/test/app.e2e-spec.ts (630 bytes)
  16. CREATE url-shortener/test/jest-e2e.json (183 bytes)
  17. ? Which package manager would you ❤️ to use? (Use arrow keys)
  18. > npm
  19. yarn
  20. pnpm

npmを選択してください。

次の出力が表示されます:

  1. √ Installation in progress... ☕
  2. 🚀 Successfully created project url-shortener
  3. 👉 Get started with the following commands:
  4. $ cd url-shortener
  5. $ npm run start
  6. Thanks for installing Nest 🙏
  7. Please consider donating to our open collective
  8. to help us maintain this package.
  9. 🍷 Donate: https://opencollective.com/nest

作成したプロジェクトディレクトリに移動します:

  1. cd url-shortener

あなたはこのディレクトリ内ですべての後続のコマンドを実行します。

注意: NestJS CLIは、新しいプロジェクトを生成するときにapp.controller.ts、app.controller.spec.ts、およびapp.service.tsファイルを作成します。このチュートリアルではそれらは必要ないので、削除するか無視することができます。

次に、必要な依存関係をインストールします。

このチュートリアルにはいくつかの依存関係が必要で、これらはNodeJSのデフォルトのパッケージマネージャーであるnpmを使用してインストールします。必要な依存関係にはTypeORM、SQLite、Class-validator、Class-transformer、およびNano-IDが含まれます。

TypeORMは、TypeScriptアプリケーションとリレーショナルデータベースとのインタラクションを容易にするオブジェクトリレーショナルマッパーです。このORMは、NestJSの専用@nestjs/typeormパッケージのおかげで、NestJSとシームレスに動作します。SQLiteデータベースとインタラクトするために、NestJSのネイティブtypeormパッケージを使用することになります。

TypeORMとその専用のNestJSパッケージをインストールするには、以下のコマンドを実行してください:

  1. npm install @nestjs/typeorm typeorm

SQLiteは、小型で高速、自己完結型のSQLデータベースエンジンを実装したライブラリです。この依存関係をデータベースとして使用し、短縮URLを保存および取得します。

SQLiteをインストールするには、次のコマンドを実行します:

  1. npm install sqlite3

このclass-validatorパッケージには、NestJSにおけるデータ検証に使用されるデコレーターが含まれています。この依存関係をデータ転送オブジェクトと共に使用して、アプリケーションに送られるデータを検証します。

class-validatorをインストールするには、次のコマンドを実行してください:

  1. npm install class-validator

class-transformer パッケージを使用すると、プレーンオブジェクトをクラスのインスタンスに変換したり、逆にクラスのインスタンスをプレーンオブジェクトに変換したりできます。この依存関係は class-validator とともに使用する必要があります。一人では機能しません。

class-transformerをインストールするには、次のコマンドを実行してください:

  1. npm install class-transformer

Nano-IDは、安全でURLフレンドリーなユニーク文字列IDジェネレーターです。この依存関係を使用して、各URLリソースのためのユニークなIDを生成します。

次のコマンドを実行してNano-IDをインストールします:

  1. npm install nanoid@^3.0.0

注意: Nano-ID のバージョンが 3.0.0 より高い場合、CommonJS モジュールのサポートが無効になります。この問題は、TypeScript コンパイラによって生成された JavaScript コードが依然として CommonJS モジュールシステムを使用しているため、アプリケーションでエラーを引き起こす可能性があります。

必要な依存関係をインストールした後、Nest CLIを使用してプロジェクトのモジュール、サービス、およびコントローラーを生成します。モジュールはプロジェクトを整理し、サービスはURL短縮のためのすべてのロジックを処理し、コントローラーはルートを処理します。

次のコマンドを実行してモジュールを生成してください:

  1. nest generate module url

次の出力が表示されます:

CREATE src/url/url.module.ts (80 bytes)
UPDATE src/app.module.ts (304 bytes)

次に、サービスを生成するために以下のコマンドを実行します:

  1. nest generate service url --no-spec

--no-spec フラグは、Nest CLI にテストファイルなしでファイルを生成するよう指示します。このチュートリアルではテストファイルは必要ありません。

次の出力が表示されます:

CREATE src/url/url.service.ts (87 bytes)
UPDATE src/url/url.module.ts (151 bytes)

次に、コントローラーを生成するために次のコマンドを実行します:

  1. nest generate controller url --no-spec

次の出力が表示されます:

CREATE src/url/url.controller.ts (95 bytes)
UPDATE src/url/url.module.ts (233 bytes)

このステップでは、アプリケーションと開発に必要なほとんどのファイルを生成しました。次に、アプリケーションをデータベースに接続します。

ステップ2 — アプリケーションをデータベースに接続する

このステップでは、データベース内のURLリソースをモデル化するためのエンティティを作成します。エンティティとは、保存されたデータの必要なプロパティを含むファイルです。また、アプリケーションとデータベースとの間のアクセス層としてリポジトリも作成します。

nanoやお好みのテキストエディタを使用して、src/urlフォルダ内にurl.entity.tsというファイルを作成し、開いてください。

  1. nano src/url/url.entity.ts

このファイルには、データをモデル化するためのエンティティが含まれます。

次に、あなたの src/url/url.entity.ts ファイルに、次のTypeScriptコードを追加してください:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Url {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    urlCode: string;

    @Column()
    longUrl: string;

    @Column()
    shortUrl: string;
}

まず、typeormからEntity、Column、およびPrimaryGeneratedColumnデコレーターをインポートします。

このコードは、クラスをエンティティとしてマークする Entity デコレーターで注釈されたクラス Url を作成してエクスポートします。

各プロパティには適切なデコレーターが指定されており、PrimaryGeneratedColumnはID用、Columnは他のプロパティ用です。PrimaryGeneratedColumnは、アノテーションをつけたプロパティの値を自動的に生成するデコレーターです。TypeOrmは、各リソースのIDを生成するためにこれを使用します。Columnは、アノテーションをつけたプロパティをデータベースのカラムとして追加するデコレーターです。

データベースに保存すべきプロパティは以下の通りです:

  • idはデータベーステーブルの主キーです。
  • urlCodeは、nanoidパッケージによって生成された一意のIDであり、各URLを識別するために使用されます。
  • longUrl は、短縮されるためにあなたのアプリケーションに送信されるURLです。
  • shortUrl は短縮されたURLです。

ファイルを保存して閉じてください。

次に、アプリケーションとデータベースの接続を作成します。

まず、nanoまたは自分の好みのテキストエディタでsrc/app.module.tsを開きます:

  1. nano src/app.module.ts

次に、強調表示された行をファイルに追加してください。

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Url } from './url/url.entity';
import { UrlModule } from './url/url.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'sqlite',
      database: 'URL.sqlite',
      entities: [Url],
      synchronize: true,
    }),
    UrlModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

まず、TypeOrmModuleを@nestjs/typeormから、Urlを./url/url.entityからインポートします。AppControllerやAppServiceに関連する行が残っているかもしれません。ファイルにそれらを残しても、チュートリアルの他の部分には影響しません。

インポート配列では、アプリケーション内のすべてのモジュールで接続を共有するために、TypeOrmModuleのforRootメソッドを呼び出します。forRootメソッドは、設定オブジェクトを引数として受け取ります。

構成オブジェクトには接続を作成するプロパティが含まれています。これらのプロパティには以下が含まれます。

  • type プロパティは、TypeOrm を使用して操作するデータベースの種類を示します。この場合、'sqlite' に設定されています。
  • このdatabaseプロパティは、あなたのデータベースに対する推奨名を示します。この場合、'URL.sqlite'に設定されています。
  • <code>entities</code>プロパティは、プロジェクト内のすべてのエンティティの配列です。この場合、配列の中に<code>Url</code>として指定された1つのエンティティがあります。
  • synchronizeオプションは、自動的にデータベースのテーブルをエンティティと同期させ、コードを実行するたびにテーブルを更新します。この場合、trueに設定されています。

注意: synchronize を true に設定することは、開発環境でのみ理想的です。本番環境では常に false に設定すべきです。データ損失を引き起こす可能性があります。

ファイルを保存して閉じてください。

次に、アプリケーションとデータベースの間にアクセスレイヤーとして機能するリポジトリを作成します。エンティティを親モジュールに接続する必要があり、この接続によりNestとTypeOrmが自動的にリポジトリを作成できるようになります。

src/url/url.module.tsを開く:

  1. nano src/url/url.module.ts

既存のファイルに、ハイライトされた行を追加します:

import { Module } from '@nestjs/common';
import { UrlService } from './url.service';
import { UrlController } from './url.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Url } from './url.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Url])],
  providers: [UrlService],
  controllers: [UrlController],
})
export class UrlModule {}

あなたは、Module デコレーターの内部に imports 配列を作成し、その中で @nestjs/typeorm から TypeOrmModule を、./url.entity から Url をインポートします。imports 配列の中で、TypeOrmModule の forFeature メソッドを呼び出します。forFeature メソッドは、エンティティの配列を引数として受け取るので、Url エンティティを渡します。

ファイルを保存して閉じてください。

NestとTypeOrmは、サービスとデータベースの間でアクセス層として機能するリポジトリを裏で作成します。

このステップでは、アプリケーションをデータベースに接続しました。これで、URL短縮のロジックを実装する準備ができました。

ステップ3 — サービスロジックの実装

このステップでは、2つのメソッドを使ってサービスロジックを実装します。最初のメソッド、shortenUrlは、すべてのURL短縮ロジックを含みます。2つ目のメソッド、redirectは、ユーザーを元のURLにリダイレクトするためのすべてのロジックを含みます。また、アプリケーションに入るデータを検証するためのデータ転送オブジェクトも作成します。

リポジトリへのサービスアクセスの提供

これらのメソッドを実装する前に、アプリケーションがデータベース内のデータを読み書きできるように、サービスにリポジトリへのアクセスを許可します。

最初に src/url/url.service.ts を開いてください:

  1. nano src/url/url.service.ts

次のハイライトされた行を既存のファイルに追加してください:

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Url } from './url.entity';

@Injectable()
export class UrlService {
  constructor(
    @InjectRepository(Url)
    private repo: Repository<Url>,
  ) {}
}

typeormからRepositoryをインポートし、@nestjs/typeormからInjectRepositoryをインポートし、./url.entityからUrlをインポートします。

あなたのUrlServiceクラスでは、constructorを作成します。constructorの中で、プライベート変数repoをパラメータとして宣言します。そして、repoにUrlのジェネリック型を持つRepositoryの型を割り当てます。repo変数にはInjectRepositoryデコレーターを注釈付けし、Urlを引数として渡します。

ファイルを保存して閉じてください。

あなたのサービスは今、repo変数を通じてリポジトリにアクセスできるようになりました。すべてのデータベースクエリとTypeOrmメソッドは、これに対して呼び出されます。

次に、非同期メソッドshortenUrlを作成します。このメソッドはURLを引数として受け取り、短縮されたURLを返します。メソッドに入力されたデータが有効であることを確認するために、データ転送オブジェクトを使用し、class-validatorとclass-transformerパッケージを使用してデータを検証します。

データ転送オブジェクトの作成

非同期メソッドを作成する前に、shortenUrl 非同期メソッドに必要なデータ転送オブジェクトを作成します。データ転送オブジェクトとは、アプリケーション間でデータがどのように送信されるかを定義するオブジェクトです。

まず、urlフォルダー内にdtos(データ転送オブジェクト)フォルダーを作成します:

  1. mkdir src/url/dtos

次に、そのフォルダー内にurl.dto.tsという名前のファイルを作成します:

  1. nano src/url/dtos/url.dto.ts

新しいファイルに次のコードを追加してください:

import { IsString, IsNotEmpty } from 'class-validator';

export class ShortenURLDto {
  @IsString()
  @IsNotEmpty()
  longUrl: string;
}

あなたはからIsStringとIsNotEmptyデコレーターをインポートします。次に、ShortenURLDtoクラスを作成しエクスポートします。ShortenURLDtoクラスの内部では、longUrlプロパティを作成し、その型をstringに設定します。

あなたはまた、longUrl プロパティに IsString および IsNotEmpty デコレーターを注釈します。これらのデコレーターで longUrl プロパティを注釈することにより、longUrl が常に文字列であり、空でないことが保証されます。

ファイルを保存して閉じてください。

次に、あなたの src/main.ts ファイルを開いてください:

  1. nano src/main.ts

強調表示されたコードの部分を既存のファイルに追加してください。

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  await app.listen(3000);
}
bootstrap();

あなたはValidationPipeをインポートします。これはパッケージを使用して、アプリケーションに入るすべてのデータに対してバリデーションルールを強制します。

次に、アプリケーションインスタンス(app)の上でuseGlobalPipesメソッドを呼び出し、whitelistプロパティがtrueに設定されたオプションオブジェクトを持つValidationPipeのインスタンスを渡します。useGlobalPipesメソッドはアプリケーションレベルでValidationPipeをバインドし、すべてのルートが不正なデータから保護されることを保証します。whitelistプロパティをtrueに設定すると、検証された(返された)オブジェクトからDTOに指定されていないプロパティが削除されます。

ファイルを保存して閉じてください。

次に、データ転送オブジェクトをurl.service.tsファイルにインポートし、shortenUrlメソッドに適用します。

shortenUrl メソッドの作成

shortenUrl メソッドは、ほとんどのURL短縮ロジックを処理します。これは、ShortenURLDto 型のパラメータ url を受け取ります。

まず、あなたの src/url/url.service.ts ファイルを開いてください:

  1. nano src/url/url.service.ts

ハイライトされた行をファイルに追加してください。

import {
  BadRequestException,
  Injectable,
  NotFoundException,
  UnprocessableEntityException,
} from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Url } from './url.entity';
import { ShortenURLDto } from './dtos/url.dto';
import { nanoid } from 'nanoid';
import { isURL } from 'class-validator';
...

まず、エラーハンドリングに使用するために、NotFoundExeception、BadRequestException、UnprocessableEntityExceptionを@nestjs/commonからインポートします。次に、nanoidから{nanoid}を、class-validatorからisURLをインポートします。isURLは、提供されたlongUrlが有効なURLかどうかを確認するために使用されます。最後に、データ検証のためにShortenURLDtoを'./dtos/url.dto'からインポートします。

それから、以下のconstructorの下にUrlServiceクラスに次のものを追加します:

...
async shortenUrl(url: ShortenURLDto) {}

次に、あなたのshortenUrlメソッドに以下のコードを追加してください:

...
    const { longUrl } = url;

    //checks if longurl is a valid URL
    if (!isURL(longUrl)) {
      throw new BadRequestException('String Must be a Valid URL');
    }

    const urlCode = nanoid(10);
    const baseURL = 'http://localhost:3000';

    try {
      //check if the URL has already been shortened
      let url = await this.repo.findOneBy({ longUrl });
      //return it if it exists
      if (url) return url.shortUrl;

      //if it doesn't exist, shorten it
      const shortUrl = `${baseURL}/${urlCode}`;

      //add the new record to the database
      url = this.repo.create({
        urlCode,
        longUrl,
        shortUrl,
      });

      this.repo.save(url);
      return url.shortUrl;
    } catch (error) {
      console.log(error);
      throw new UnprocessableEntityException('Server Error');
    }

上のコードブロックでは、longUrlがurlオブジェクトからデストラクチャーされました。次に、isURLメソッドを使用して、longUrlが有効なURLかどうかを確認します。

urlCodeはnanoidを使用して生成されます。デフォルトでは、nanoidは21文字のユニークな文字列を生成します。デフォルトの動作を上書きするには、引数として希望の長さを渡します。この場合、URLをできるだけ短くするために、10の値を渡します。

次に、基本URLが定義されます。基本URLは、ウェブサイトアドレスの一貫したルートです。開発環境では、ローカルホストサーバーであり、運用環境ではドメイン名になります。このチュートリアルでは、サンプルコードにlocalhostが使用されています。

try-catch ブロックは、エラーハンドリングのためにデータベースとやり取りするすべてのコードを含みます。

URLを2回短縮すると重複データが発生する可能性があるため、URLが存在するかどうかを確認するためにデータベースで検索クエリが実行されます。存在する場合は、そのshortUrlが返されます; 存在しない場合は、短縮する処理が進行します。データベースにURLの記録が見つからない場合、baseURLとurlCodeを連結して短縮URLが生成されます。

次に、url エンティティインスタンスが urlCode、longUrl、および shortUrl で作成されます。url インスタンスは、repo の save メソッドを呼び出して、インスタンスを引数として渡すことでデータベースに保存されます。その後、shortUrl が返されます。

最後に、エラーが発生した場合、キャッチブロックでエラーがコンソールにログとして記録され、UnprocessableEntityException メッセージがスローされます。

これがあなたの url.service.ts ファイルの見た目になります:

import {
  BadRequestException,
  Injectable,
  NotFoundException,
  UnprocessableEntityException,
} from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Url } from './url.entity';
import { ShortenURLDto } from './dtos/url.dto';
import { nanoid } from 'nanoid';
import { isURL } from 'class-validator';

@Injectable()
export class UrlService {
  constructor(
    @InjectRepository(Url)
    private repo: Repository<Url>,
  ) {}

  async shortenUrl(url: ShortenURLDto) {
    const { longUrl } = url;

    //checks if longurl is a valid URL
    if (!isURL(longUrl)) {
      throw new BadRequestException('String Must be a Valid URL');
    }

    const urlCode = nanoid(10);
    const baseURL = 'http://localhost:3000';

    try {
      //check if the URL has already been shortened
      let url = await this.repo.findOneBy({ longUrl });
      //return it if it exists
      if (url) return url.shortUrl;

      //if it doesn't exist, shorten it
      const shortUrl = `${baseURL}/${urlCode}`;

      //add the new record to the database
      url = this.repo.create({
        urlCode,
        longUrl,
        shortUrl,
      });

      this.repo.save(url);
      return url.shortUrl;
    } catch (error) {
      console.log(error);
      throw new UnprocessableEntityException('Server Error');
    }
  }
}

ファイルを保存してください。

ここで、URL短縮ロジックの最初の部分を設定します。次に、サービスでredirectメソッドを実装します。

redirect メソッドの作成

redirect メソッドには、ユーザーを長いURLにリダイレクトするロジックが含まれます。

まだ src/url/url/service.ts ファイル内で、UrlService クラスの下部に次のコードを追加して、redirect メソッドを実装します:

...
  async redirect(urlCode: string) {
    try {
      const url = await this.repo.findOneBy({ urlCode });
      if (url) return url;
    } catch (error) {
      console.log(error);
      throw new NotFoundException('Resource Not Found');
    }
  }

このredirectメソッドは、urlCodeを引数として取り、データベース内で一致するurlCodeを持つリソースを探そうとします。リソースが存在する場合は、そのリソースを返します。それ以外の場合は、NotFoundExceptionエラーをスローします。

あなたの完成した url.service.ts ファイルは今このようになります:

import {
  BadRequestException,
  Injectable,
  NotFoundException,
  UnprocessableEntityException,
} from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Url } from './url.entity';
import { ShortenURLDto } from './dtos/url.dto';
import { nanoid } from 'nanoid';
import { isURL } from 'class-validator';

@Injectable()
export class UrlService {
  constructor(
    @InjectRepository(Url)
    private repo: Repository<Url>,
  ) {}

  async shortenUrl(url: ShortenURLDto) {
    const { longUrl } = url;

    //checks if longurl is a valid URL
    if (!isURL(longUrl)) {
      throw new BadRequestException('String Must be a Valid URL');
    }

    const urlCode = nanoid(10);
    const baseURL = 'http://localhost:3000';

    try {
      //check if the URL has already been shortened
      let url = await this.repo.findOneBy({ longUrl });
      //return it if it exists
      if (url) return url.shortUrl;

      //if it doesn't exist, shorten it
      const shortUrl = `${baseURL}/${urlCode}`;

      //add the new record to the database
      url = this.repo.create({
        urlCode,
        longUrl,
        shortUrl,
      });

      this.repo.save(url);
      return url.shortUrl;
    } catch (error) {
      console.log(error);
      throw new UnprocessableEntityException('Server Error');
    }
  }

  async redirect(urlCode: string) {
    try {
      const url = await this.repo.findOneBy({ urlCode });
      if (url) return url;
    } catch (error) {
      console.log(error);
      throw new NotFoundException('Resource Not Found');
    }
  }
}

ファイルを保存して閉じてください。

あなたのURL短縮ロジックは、URLを短縮するための1つの方法と、短縮されたURLを元のURLにリダイレクトするための別の方法の2つで完了しました。

次のステップでは、コントローラークラス内でこれらの2つのメソッドのルートハンドラーを実装します。

ステップ4 — コントローラーのロジックを実装する

このステップでは、2つのルートハンドラーを作成します。短縮リクエストを処理するためのPOSTルートハンドラーと、リダイレクトリクエストを処理するためのGETルートハンドラーです。

コントローラークラスにルートを実装する前に、サービスをコントローラーに利用できるようにする必要があります。

まず、あなたの src/url/url.controller.ts ファイルを開いてください:

  1. nano src/url/url.controller.ts

ハイライトされた行をファイルに追加してください。

import { Controller } from '@nestjs/common';
import { UrlService } from './url.service';

@Controller('url')
export class UrlController {
  constructor(private service: UrlService) {}
}

まず、UrlServiceを./url.serviceからインポートします。次に、コントローラークラス内でコンストラクターを宣言し、プライベート変数serviceをパラメーターとして初期化します。serviceにUrlServiceの型を割り当てます。

現在、Controllerデコレーターは引数として文字列'url'を持っており、これはコントローラーがlocalhost/3000/url/routeに対するリクエストのみを処理することを意味します。この動作は、短縮URLがベースURL(localhost:3000)とURLコード(wyt4_uyP-Il)を含むため、バグを引き起こします。これにより、新しいURL(localhost:3000/wyt4_uyP-Il)が形成されます。したがって、このコントローラーはリクエストを処理できず、短縮リンクは404 Not Foundエラーを返します。これを解決するために、Controllerデコレーターから'url'引数を削除し、各ハンドラーのために個別のルートを実装してください。

「url」引数を削除した後、あなたの「UrlController」は次のようになります:

@Controller()
export class UrlController {
  constructor(private service: UrlService) {}
}

まだsrc/url/url.controller.tsファイルの中で、ハイライトされた項目をimport文に追加します:

import { Body, Controller, Get, Param, Post, Res } from '@nestjs/common';
import { UrlService } from './url.service';
import { ShortenURLDto } from './dtos/url.dto';

Body、Get、Param、Post、およびResを@nestjs/commonからインポートし、ShortenURLDtoを./dtos/url.dtoからインポートします。デコレーターは、このファイルに追加する際にさらに定義されます。

次に、constructorの下に以下の行をUrlControllerに追加して、POSTルートハンドラを定義します:

...
  @Post('shorten')
  shortenUrl(
    @Body()
    url: ShortenURLDto,
  ) {
    return this.service.shortenUrl(url);
  }

あなたは、ShortenURLDtoの型を持つ引数urlを取るメソッドshortenUrlを作成します。リクエストオブジェクトからボディオブジェクトを抽出し、url変数にその値を設定するために、Bodyデコレーターでurlに注釈を付けます。

次に、全体のメソッドにPostデコレーターを注釈付けし、'shorten'を引数として渡します。このハンドラーは、localhost:<port>/shortenに対して行われるすべてのPostリクエストを処理します。次に、service上でshortenUrlメソッドを呼び出し、urlを引数として渡します。

次に、リダイレクトのためのGETルートハンドラーを定義するために、POSTルートの下に次の行を追加します:

...
  @Get(':code')
  async redirect(
    @Res() res,
    @Param('code')
    code: string,
  ) {
    const url = await this.service.redirect(code);

    return res.redirect(url.longUrl);
  }

あなたは、二つのパラメータを持つredirectメソッドを作成します。resはResデコレーターで注釈され、codeはParamデコレーターで注釈されます。Resデコレーターは、そのクラスをExpressレスポンスオブジェクトに変換し、Expressのredirectメソッドなどのライブラリ特有のコマンドを使用できるようにします。Paramデコレーターは、reqオブジェクトからparamsプロパティを抽出し、装飾されたパラメータにその値を設定します。

あなたはredirectメソッドにGetデコレーターを注釈付けし、ワイルドカードパラメーター':code'を渡します。そして、'code'をParamデコレーターの引数として渡します。

次に、service の redirect メソッドを呼び出し、結果を await して、変数 url に保存します。

最後に、res.redirect()を返し、url.longUrlを引数として渡します。このメソッドは、localhost:<port>/codeへのGETリクエストや、あなたの短縮URLを処理します。

あなたの src/url/url.controller.ts ファイルは今このようになります:

import { Body, Controller, Get, Param, Post, Res } from '@nestjs/common';
import { UrlService } from './url.service';
import { ShortenURLDto } from './dtos/url.dto';

@Controller()
export class UrlController {
  constructor(private service: UrlService) {}

  @Post('shorten')
  shortenUrl(
    @Body()
    url: ShortenURLDto,
  ) {
    return this.service.shortenUrl(url);
  }

  @Get(':code')
  async redirect(
    @Res() res,
    @Param('code')
    code: string,
  ) {
    const url = await this.service.redirect(code);

    return res.redirect(url.longUrl);
  }
}

ファイルを保存して閉じてください。

あなたが POST および GET ルートハンドラーを定義したので、URL 短縮ツールは完全に機能しています。次のステップでは、これをテストします。

ステップ5 — URLショートナーのテスト

このステップでは、前のステップで定義したURL短縮ツールをテストします。

まず、次のコマンドを実行してアプリケーションを開始します:

  1. npm run start

次の出力が表示されます:

[Nest] 12640  - 06/08/2022, 16:20:04     LOG [NestFactory] Starting Nest application...
[Nest] 12640  - 06/08/2022, 16:20:07     LOG [InstanceLoader] AppModule dependencies initialized +2942ms
[Nest] 12640  - 06/08/2022, 16:20:07     LOG [InstanceLoader] TypeOrmModule dependencies initialized +1ms
[Nest] 12640  - 06/08/2022, 16:20:08     LOG [InstanceLoader] TypeOrmCoreModule dependencies initialized +257ms
[Nest] 12640  - 06/08/2022, 16:20:08     LOG [InstanceLoader] TypeOrmModule dependencies initialized +2ms
[Nest] 12640  - 06/08/2022, 16:20:08     LOG [InstanceLoader] UrlModule dependencies initialized +4ms
[Nest] 12640  - 06/08/2022, 16:20:08     LOG [RoutesResolver] UrlController {/}: +68ms
[Nest] 12640  - 06/08/2022, 16:20:08     LOG [RouterExplorer] Mapped {/shorten, POST} route +7ms
[Nest] 12640  - 06/08/2022, 16:20:08     LOG [RouterExplorer] Mapped {/:code, GET} route +2ms
[Nest] 12640  - 06/08/2022, 16:20:08     LOG [NestApplication] Nest application successfully started +7ms

新しいターミナルを開いて、curlまたはお好みのAPIテストツールを使用して、以下のデータまたはお好きなデータを使ってhttp://localhost:3000/shortenにPOSTリクエストを送信してください。curlの使用方法についての詳細は、このチュートリアルの説明を参照してください。

サンプルのPOSTリクエストを作成するには、このコマンドを実行してください:

  1. curl -d "{\"longUrl\":\"http://llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.co.uk\"}" -H "Content-Type: application/json" http://localhost:3000/shorten

-dフラグはHTTP POSTリクエストデータを登録し、-HフラグはHTTPリクエストのヘッダーを設定します。このコマンドを実行すると、長いURLがあなたのアプリケーションに送信され、短縮URLが返されます。

あなたは以下の例のような短いURLをレスポンスとして受け取ります。

http://localhost:3000/MWBNHDiloW

最後に、短いURLをコピーしてブラウザにリンクを貼り付けます。その後、ENTERを押してください。元のリソースにリダイレクトされます。

結論

この記事では、NestJSを使ってURL短縮ツールを作成しました。フロントエンドインターフェースを追加すれば、一般向けに展開することができます。完全なプロジェクトはGithubでレビューできます。

NestJSは、アプリケーションをより安全で、メンテナンスしやすく、スケーラブルにするための型安全性とアーキテクチャを提供します。詳細については、NestJSの公式ドキュメントをご覧ください。

関連記事: