前の記事で、基本的な短縮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 ダッシュボードに表示されるデータから深いユーザーインサイトを得ることができます。
Xでフォローする:@LeapcellJP
関連記事:


