Nest.js 短縮URLサービスにクリック計測を追加する
Emily Parker
Product Engineer · Leapcell

前の記事で、基本的な短縮URLサービスを構築しました。
包括的な短縮URLサービスとなるためには、データ分析機能を追加することが不可欠です。データ分析は、短縮URLサービスのコアバリューの1つです。リンクのクリックを追跡することで、その普及効果、ユーザープロファイル、その他の情報を理解することができます。
次は、サービスにクリック計測機能を追加し、クリックごとに時間、IPアドレス、デバイス情報を記録します。
1. クリックレコード用のデータベースエンティティを作成する
まず、各クリックレコードを保存するための新しいデータベーステーブルが必要です。同様に、TypeORMエンティティを作成してその構造を定義します。
src
ディレクトリに click
という名前の新しいフォルダを作成し、その中に click.entity.ts
ファイルを作成します。
// src/click/click.entity.ts import { ShortLink } from '../short-link/short-link.entity'; import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn, } from 'typeorm'; @Entity() export class Click { @PrimaryGeneratedColumn('uuid') id: string; // ShortLink エンティティとの多対一の関係を確立 // これは、1つの短縮URLが多数のクリックレコードに対応できることを意味します @ManyToOne(() => ShortLink, (shortLink) => shortLink.clicks) @JoinColumn({ name: 'shortLinkId' }) // データベースに外部キー列を作成します shortLink: ShortLink; @CreateDateColumn() clickedAt: Date; @Column({ type: 'varchar', length: 45 }) // IPv4またはIPv6アドレスを格納するため ipAddress: string; @Column({ type: 'text' }) userAgent: string; }
説明:
@ManyToOne
: このデコレーターは、Click
とShortLink
の間の関係を確立します。1つの短縮URLには、多数(Many)のクリックが関連付けることができます。@JoinColumn
: データベースレベルで2つのテーブルを関連付けるために使用される外部キー列の名前を指定します。
2. ShortLink
エンティティを更新する
短縮URLからすべてのクリックレコードをクエリできるように、short-link.entity.ts
ファイルも更新して双方向の関係を確立する必要があります。
src/short-link/short-link.entity.ts
を開き、clicks
プロパティを追加します。
// src/short-link/short-link.entity.ts import { Click } from '../click/click.entity'; // Click エンティティをインポート import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, Index, OneToMany, } from 'typeorm'; @Entity() export class ShortLink { @PrimaryGeneratedColumn('uuid') id: string; // ... その他の既存フィールド ... @Column({ unique: true }) @Index() shortCode: string; @Column({ type: 'text' }) longUrl: string; @CreateDateColumn() createdAt: Date; // 新しいプロパティ @OneToMany(() => Click, (click) => click.shortLink) clicks: Click[]; }
説明:
@OneToMany
:ShortLink
からClick
への一対多の関係を確立します。1つの短縮URL(One)は、多数(Many)のクリックレコードを持つことができます。
3. クリックサービス用のモジュールとロジックを作成する
ShortLink
と同様に、Click
用の専用モジュールとサービスを作成し、関連するロジックを処理し、コードのモジュール性を維持します。
Nest CLI を使用して、必要なファイルを迅速に生成します。
nest generate resource click
ClickModule を構成する
src/click/click.module.ts
を開き、TypeOrmModule
をインポートし、Click
エンティティを登録します。
// src/click/click.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ClickService } from './click.service'; import { Click } from './click.entity'; @Module({ imports: [TypeOrmModule.forFeature([Click])], providers: [ClickService], exports: [ClickService], // ClickService をエクスポートして、他のモジュールで使用できるようにします }) export class ClickModule {}
ClickService を実装する
src/click/click.service.ts
を開き、クリックを記録するためのロジックを記述します。
// src/click/click.service.ts import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Click } from './click.entity'; import { ShortLink } from '../short-link/short-link.entity'; @Injectable() export class ClickService { constructor( @InjectRepository(Click) private readonly clickRepository: Repository<Click> ) {} async recordClick(shortLink: ShortLink, ipAddress: string, userAgent: string): Promise<Click> { const newClick = this.clickRepository.create({ shortLink, ipAddress, userAgent, }); return this.clickRepository.save(newClick); } }
4. リダイレクト中のクリック計測を統合する
次に、ShortLinkController
の redirect
メソッドに追跡ロジックを統合します。
ShortLinkModule
を更新する
まず、ShortLinkModule
が ClickModule
をインポートして ClickService
を使用できるようにする必要があります。
// src/short-link/short-link.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ShortLinkController } from './short-link.controller'; import { ShortLinkService } from './short-link.service'; import { ShortLink } from './short-link.entity'; import { ClickModule } from '../click/click.module'; // ClickModule をインポート @Module({ imports: [TypeOrmModule.forFeature([ShortLink]), ClickModule], // ClickModule を追加 controllers: [ShortLinkController], providers: [ShortLinkService], }) export class ShortLinkModule {}
ShortLinkController
を変更する
src/short-link/short-link.controller.ts
を開き、ClickService
を注入し、redirect
メソッドでそれを呼び出します。
// src/short-link/short-link.controller.ts import { Controller, Get, Post, Body, Param, Res, NotFoundException, Req } from '@nestjs/common'; import { Response, Request } from 'express'; import { ShortLinkService } from './short-link.service'; import { CreateShortLinkDto } from './dto/create-short-link.dto'; import { ClickService } from '../click/click.service'; // ClickService をインポート @Controller() export class ShortLinkController { constructor( private readonly shortLinkService: ShortLinkService, private readonly clickService: ClickService // ClickService を注入 ) {} // ... createShortLink メソッドは変更されません ... @Post('shorten') // ... @Get(':shortCode') async redirect( @Param('shortCode') shortCode: string, @Res() res: Response, @Req() req: Request // Express Request オブジェクトを注入 ) { const link = await this.shortLinkService.findOneByCode(shortCode); if (!link) { throw new NotFoundException('Short link not found.'); } // ユーザーのリダイレクトを遅延させないように、クリックの記録を非同期で行います this.clickService.recordClick(link, req.ip, req.get('user-agent') || ''); // リダイレクトを実行します return res.redirect(301, link.longUrl); } }
主な変更点:
ClickService
を注入しました。redirect
メソッドで、@Req()
デコレーターを介してexpress
のRequest
オブジェクトを取得しました。req
オブジェクトから、req.ip
(IPアドレス)とreq.get('user-agent')
(デバイスとブラウザの情報)を取得しました。this.clickService.recordClick()
を呼び出して、クリックレコードを保存しました。注意、クリック記録操作がリダイレクトをブロックしないように、ここではawait
を使用しませんでした。
5. 統計情報を表示する
データが記録されたら、それを見るためのエンドポイントも必要です。ShortLinkController
に統計エンドポイントを追加しましょう。
// src/short-link/short-link.controller.ts // ... (インポートとコンストラクタ) @Controller() export class ShortLinkController { // ... (コンストラクタとその他のメソッド) // 新しい統計エンドポイント @Get('stats/:shortCode') async getStats(@Param('shortCode') shortCode: string) { const linkWithClicks = await this.shortLinkService.findOneByCodeWithClicks(shortCode); if (!linkWithClicks) { throw new NotFoundException('Short link not found.'); } return { shortCode: linkWithClicks.shortCode, longUrl: linkWithClicks.longUrl, totalClicks: linkWithClicks.clicks.length, clicks: linkWithClicks.clicks.map((click) => ({ clickedAt: click.clickedAt, ipAddress: click.ipAddress, userAgent: click.userAgent, })), }; } }
このエンドポイントが機能するためには、ShortLinkService
に findOneByCodeWithClicks
メソッドを追加する必要があります。
src/short-link/short-link.service.ts
を開きます。
// src/short-link/short-link.service.ts // ... @Injectable() export class ShortLinkService { constructor( @InjectRepository(ShortLink) private readonly shortLinkRepository: Repository<ShortLink> ) {} // ... (findOneByCode および create メソッド) // 関連を使用して関連するクリックをロードする新しいメソッド async findOneByCodeWithClicks(shortCode: string): Promise<ShortLink | null> { return this.shortLinkRepository.findOne({ where: { shortCode }, relations: ['clicks'], // キー: TypeORM に関連するクリックエンティティをロードするように指示します }); } }
これでサービスを再起動します。短縮URLにアクセスした後、http://localhost:3000/stats/your-short-code
に GET
リクエストを行うと、以下のようなJSONレスポンスが表示され、詳細なクリックレコードが含まれています。
{ "shortCode": "some-hash", "longUrl": "https://www.google.com/", "totalClicks": 1, "clicks": [ { "clickedAt": "2025-09-17T23:00:00.123Z", "ipAddress": "::1", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ..." } ] }
これで、短縮URLサービスにはクリック計測と統計分析機能が備わりました!
本番環境への更新
サービスがすでにオンラインで実行されている場合、ローカルでデバッグした後の次のステップは、更新を本番サーバーにデプロイすることです。
Leapcell にデプロイされたアプリケーションの場合、この手順は非常に簡単です。GitHub にコードをプッシュするだけで、Leapcell が最新のコードを自動的にプルしてアプリケーションを更新します。
さらに、Leapcell 自体には強力な組み込みtraffic analysis機能があります。クリック計測を自分で実装しなくても、Leapcell ダッシュボードに表示されるデータから深いユーザーインサイトを得ることができます。