NodeJSやNestJSを使用して、型安全なURL短縮ツールを作る方法

著者は、Write for Donationsプログラムの一環として、寄付の受領先としてTech Education Fundを選択しました。

以下の文章を日本語で自然な言い方で言い換える(1つの選択肢のみ):
イントロダクション

URLとは、Uniform Resource Locatorの略称で、ウェブ上の固有リソースに割り当てられるアドレスです。URLは一意であるため、同じURLを持つ2つのリソースは存在できません。

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

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

前提条件

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

  • A local development environment for Node.js version 16 or higher. Follow the How To Install Node.js and Create a Local Development Environment tutorial that suits your system.
  • The NestJS CLI installed on your system, which you will set up in Step 1, and familiarity with NestJS. Review Getting Started with NestJS.
  • Familiarity with TypeScript. You can review the How To Code in TypeScript series to learn more.

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

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

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

  1. npm install -g @nestjs/cli

 

-gフラグを使用すると、システム全体にNest CLIがグローバルにインストールされます。

以下の出力が表示されます。 (Ika no shutsuryoku ga hyōji sa remasu.)

  1. Output

    ...

  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

 

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

  1. nest new URL-shortener

 

以下の出力が表示されます。

  1. Output

    ...

  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. Output

    √ 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

 

このディレクトリで以降のすべてのコマンドを実行します。

Note

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

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

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

TypeORMは、TypeScriptアプリケーションとリレーショナルデータベースの相互作用を容易にするオブジェクトリレーショナルマッパーです。NestJS独自の@nestjs/typeormパッケージにより、このORMはNestJSとシームレスに連携します。NestJSのネイティブtypeormパッケージとこの依存関係を使用して、SQLiteデータベースとのやり取りを行います。

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を生成するために、この依存関係を使用します。

ナノ-IDをインストールするために、以下のコマンドを実行してください。

  1. npm install nanoid@^3.0.0

 

Note

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

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

以下のコマンドを実行して、モジュールを生成してください。

  1. nest generate module url

 

以下の出力結果が表示されます:

Output

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に対して、テストファイルを生成せずにファイルを生成するよう指示します。このチュートリアルではテストファイルは必要ありません。

以下の出力が表示されます。

Output

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

その後、コントローラを生成するために以下のコマンドを実行してください。

  1. nest generate controller url –no-spec

 

以下の出力を確認することができます。

Output

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コードを追加してください。

以下は、src/url/url.entity.tsの表現を日本語で自然に言い換えるものです(1つのオプションのみ):
エスアールシー/ユーアールシー/ユーアール.エンティティ.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

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

    @Column()
    urlCode: string;

    @Column()
    longUrl: string;

    @Column()
    shortUrl: string;
}

最初に、’typeorm’からエンティティ、カラム、およびPrimaryGeneratedColumnのデコレーターをインポートします。

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

各プロパティは適切なデコレーターで指定および注釈付けされます:idにはPrimaryGeneratedColumnが使用され、その他のプロパティにはColumnが使用されます。PrimaryGeneratedColumnは、注釈を付けたプロパティに自動的に値を生成するデコレーターです。 TypeOrmは各リソースに対してidを生成するためにこれを使用します。Columnは、データベース内の列として注釈を付けたプロパティを追加するデコレーターです。

データベースに保存すべきプロパティは以下の通りです。 (Dētabēsu ni hozon subeki puropatī wa ika no tōri desu.)

  • id is the primary key for the database table.
  • urlCode is the unique id generated by the nanoid package and will be used to identify each URL.
  • longUrl is the URL sent to your application to be shortened.
  • shortUrl is the shortened URL.

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

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

最初に、nanoまたはお好みのテキストエディタでsrc/app.module.tsを開いてください。

  1. nano src/app.module.ts

 

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

src/app.module.tsを日本語で言い換えると、「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 {}

最初に、@nestjs/typeormからTypeOrmModuleと、./url/url.entityからUrlをインポートします。AppControllerとAppServiceに関連する行がまだあるかもしれませんが、ファイル内に残しておいてもチュートリアルの残りに影響はありません。

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

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

  • The type property denotes the kind of database you are using TypeOrm to interact with. In this case, it is set to ‘sqlite’.
  • The database property denotes the preferred name for your database. In this case, it is set to ‘URL.sqlite’.
  • The entities property is an array of all the entities in your project. In this case, you have just one entity specified as Url inside the array.
  • The synchronize option automatically syncs your database tables with your entity and updates the tables each time you run the code. In this case, it is set to true.

Note

注意:開発環境では、synchronizeをtrueに設定することが理想的です。しかし、本番環境では常にfalseに設定する必要があります。なぜなら、データの損失が発生する可能性があるためです。

ファイルを保存して閉じる。

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

src/url/url.module.tsを開いてください。

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

 

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

src/url/url.module.tsの内容を日本語で言い換えると、次のようになります:

「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デコレータの内部で、@nestjs/typeormからTypeOrmModuleをインポートし、./url.entityからUrlをインポートして、imports配列を作成します。imports配列の内部で、TypeOrmModuleのforFeatureメソッドを呼び出します。forFeatureメソッドは、エンティティの配列を引数として受け取るため、Urlエンティティを渡します。

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

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

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

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

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

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

これらの方法を実装する前に、サービスにリポジトリへのアクセス権限を与える必要があります。これにより、アプリケーションがデータベースに対して読み書きすることが可能になります。

最初に、src/url/url.service.tsを開いてください。 (Saisho ni, src/url/url.service.ts o hiraite kudasai.)

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

 

既存のファイルに、以下のハイライトされた行を追加してください。

以下は、日本語での表現例です。

src/url/url.service.ts → 「src/url/urlサービス.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クラスの中で、コンストラクタを作成します。コンストラクタの中で、プライベート変数のrepoをパラメータとして宣言します。その後、repoにはジェネリック型Urlを持つRepositoryの型を割り当てます。そして、repo変数に@InjectRepositoryデコレータを付けて、Urlを引数として渡します。

ファイルを保存して閉じる。

サービスは、repo変数を通じてリポジトリへのアクセス権を持つようになりました。データベースのクエリやTypeOrmのメソッドは、それに対して呼び出されます。

次に、非同期メソッドであるshortenUrlを作成します。このメソッドはURLを引数として受け取り、短縮されたURLを返します。メソッドに与えられるデータが有効かどうかを検証するために、データ転送オブジェクトとクラスバリデータ、クラストランスフォーマーパッケージを使用します。

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

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

最初に、URLフォルダ内にDTOS(データ転送オブジェクト)フォルダを作成してください。

  1. mkdir src/url/dtos

 

それから、そのフォルダ内にurl.dto.tsという名前のファイルを作成してください。

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

 

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

以下のものを日本語で言い換えてください(1つのオプションを提供してください):
src/url/dto/url.dto.tsソース/url/dto/url.dto.ts

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

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

class-validatorからIsStringとIsNotEmptyデコレーターをインポートします。次に、ShortenURLDtoクラスを作成してエクスポートします。ShortenURLDtoクラス内で、longUrlプロパティを作成し、その型を文字列に設定します。

あなたはまた、longUrlプロパティにIsStringとIsNotEmptyデコレータを付け加えます。これらのデコレータを使ってlongUrlプロパティにアノテーションを付けることで、常にlongUrlが文字列であり、かつ空ではないことが保証されます。

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

それでは、src/main.ts ファイルを開いてください。

  1. nano src/main.ts

 

既存のファイルに、ハイライトされたコードを追加してください。

メインのファイルは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();

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

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

ファイルを保存して閉じる。

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

短縮URLメソッドの作成

ショートURLメソッドは、URLの短縮に関するほとんどの処理を担当します。それは、ShortenURLDto型のurlパラメータを受け取ります。

最初に、src/url/url.service.ts ファイルを開いてください。 (Mazu ni, src/url/url.service.ts fairu o aite kudasai.)

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

 

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

以下は、src/url/url.service.tsの内容を日本語で言い換えたものです。1つのオプションを提供します。

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';
...

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

それでは、以下のコードをコンストラクタの下にあるUrlService クラスに追加してください。

src/url/url.service.tsの内容を日本語で言い換えると、次のようになります。「src/url/url.service.tsファイルは、URLに関連するサービスを提供します。」
...
async shortenUrl(url: ShortenURLDto) {}

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

src/url/url.service.tsの内容を日本語で言い換えると、以下のようになります。

src/url/url.service.tsの機能

...
    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であるかを検証するチェックが行われます。

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

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

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

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

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

最後に、もしエラーが発生した場合、エラーはcatchブロックでコンソールにログが出力され、UnprocessableEntityExceptionのメッセージがスローされます。

以下は、あなたのurl.service.tsファイルが今後どのような形になるかです。

src/url/url.service.tsを日本語で自然に言い換えると、次のようになります:

ソース/URL/URLサービス.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の短縮ロジックの最初の部分を設定します。次に、サービス内でリダイレクトのメソッドを実装します。

リダイレクトメソッドの作成

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

まだsrc/url/url/service.tsファイル内において、UrlServiceクラスの最後に以下のコードを追加して、リダイレクトメソッドを実装してください。

src/url/url.service.tsを日本語で言い換えると:

「src/url/url.service.ts」

...
  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');
    }
  }

リダイレクトメソッドは、urlCodeを引数として受け取り、データベース内に一致するurlCodeを持つリソースを見つけようとします。もしリソースが存在する場合は、そのリソースを返します。存在しない場合は、NotFoundExceptionエラーをスローします。

あなたの完成したurl.service.tsファイルは、これからはこのようになります。

以下はsrc/url/url.service.tsの日本語による言い換えです:

ソース/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';

@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を短縮するためのメソッドと、短縮されたURLを元のURLにリダイレクトするための別のメソッドの2つで完了しています。

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

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

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

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

最初に、src/url/url.controller.tsファイルを開いてください。

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

 

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

以下は、日本語での一つのオプションです。
src/url/url.controller.ts -> src/url/url.controller.tsです。
import { Controller } from '@nestjs/common';
import { UrlService } from './url.service';

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

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

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

‘UrlController’では、’url’引数を削除した後、以下のようになります。

以下の文を日本語で自然に言い換えてください。1つのオプションだけ必要です:
src/url/url.controller.tsソースコードディレクトリ「src/url/url.controller.ts」

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

まだsrc/url/url.controller.tsファイルにいる状態で、ハイライトされた項目をimport文に追加してください。

以下の文を日本語で言い換えてください(1つの選択肢で):
src/url/url.controller.tssrc/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';

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

次に、POSTルートハンドラを定義するために、以下の行をコンストラクタの下にUrlControllerに追加してください。

以下は、src/url/url.controller.tsを日本語で自然に述べたものです。(1つの選択肢です)

「src/url/url.controller.ts」を日本語で言い換えると、以下のようになります。

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

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

その後、`Post`デコレータを使ってメソッド全体にアノテーションを付け、引数として`’shorten’`を渡します。このハンドラは、`localhost:/shorten`に送信されたすべてのPostリクエストを処理します。そして、サービスの`shortenUrl`メソッドを呼び出し、引数としてurlを渡します。

次に、GETルートのハンドラーをリダイレクトするために、以下の行をPOSTルートの下に追加してください。

src/url/url.controller.tsの内容を日本語でネイティブに言い換えます。

パス src/url/url.controller.ts のコントローラーファイルの内容を日本語で書き換えてください。

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

    return res.redirect(url.longUrl);
  }

以下の内容を日本語で表現すると:

「あなたは2つのパラメータ(Resデコレーターで注釈を付けたresとParamデコレーターで注釈を付けたcode)を持つリダイレクトメソッドを作成します。Resデコレーターは、注釈が付けられたクラスをExpressのレスポンスオブジェクトに変換し、Expressのリダイレクトメソッドのようなライブラリ固有のコマンドを使用できるようにします。Paramデコレーターは、reqオブジェクトからparamsプロパティを抽出し、その値で注釈が付けられたパラメーターを補完します。」

Getデコレータを使ってリダイレクトメソッドに注釈をつけ、ワイルドカードパラメータ’:code’を渡します。次に、’code’を引数としてParamデコレータに渡します。

それでは、サービスのリダイレクトメソッドを呼び出し、結果を待機して変数「url」に格納します。

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

あなたのsrc/url/url.controller.tsファイルは、次のようになります。

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

 

以下の出力が表示されます。

Output

[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公式ドキュメントをご覧ください。

コメントを残す 0

Your email address will not be published. Required fields are marked *