NodeJS ve NestJS ile Tip-Güvenli (Type-Safe) URL Kısaltıcı Nasıl İnşa Edilir?
Giriş
Günümüzün dijital dünyasında, uzun ve karmaşık URL’ler hem estetik açıdan hoş görünmez hem de paylaşım, iletişim ve kullanılabilirlik açısından sorunlar yaratabilir. Bu sorunlara çözüm olarak URL kısaltıcılar ortaya çıkmıştır. Bir URL kısaltıcı, uzun bir web adresini çok daha kısa ve yönetilebilir bir forma dönüştüren bir hizmettir. Bu makalede, modern bir web uygulaması geliştirme yaklaşımıyla, NodeJS ve NestJS çatısını kullanarak tip-güvenli bir URL kısaltıcıyı adım adım nasıl inşa edeceğimizi detaylı bir şekilde inceleyeceğiz.
Tip-güvenliği (type-safety), yazılım geliştirmenin önemli bir yönüdür. Özellikle büyük ve karmaşık projelerde, veri türlerinin doğru bir şekilde yönetilmesi, hataları en aza indirmeye, kodun okunabilirliğini artırmaya ve bakımını kolaylaştırmaya yardımcı olur. TypeScript’in gücünü arkasına alan NestJS, geliştiricilere bu tip-güvenli ortamı sunarak, daha sağlam ve öngörülebilir uygulamalar oluşturma imkanı tanır. NodeJS’in asenkron yapısı ve NestJS’in modüler, ölçeklenebilir mimarisi sayesinde, yüksek performanslı ve bakımı kolay bir URL kısaltıcı inşa etmek mümkün olacaktır.
Bu makalede, projemizin temel mimarisinden başlayarak, NestJS’in sunduğu olanaklarla veritabanı entegrasyonu, veri aktarım nesneleri (DTO’lar) aracılığıyla tip-güvenli validasyon, kısaltma mantığı, API endpointlerinin oluşturulması ve hata yönetimi gibi konuları ele alacağız. Son olarak, uygulamamızı daha da güçlü hale getirecek gelişmiş özellikler ve iyileştirmeler üzerinde duracağız. Amacımız, sadece bir URL kısaltıcı inşa etmek değil, aynı zamanda modern web geliştirme pratiklerini ve tip-güvenliğin faydalarını somut bir örnek üzerinden göstermektir.
Temeller: URL Kısaltıcı Mimarisi
Bir URL kısaltıcının temel işlevleri oldukça basittir: uzun bir URL’i alır, benzersiz kısa bir kod üretir ve bu ikisini birbiriyle ilişkilendirerek saklar. Kullanıcı kısa URL’e eriştiğinde, sistem orijinal URL’i bulur ve kullanıcıyı oraya yönlendirir. Bu basit iş akışını desteklemek için belirli bileşenlere ihtiyacımız var:
Kısaltma Mekanizması
Uzun URL’i benzersiz bir kısa koda dönüştüren algoritma. Bu kod genellikle rastgele karakterlerden oluşur veya orijinal URL’in bir hash’i olabilir. Kodun benzersizliği kritik öneme sahiptir.
Veritabanı
Orijinal URL’ler ile bunlara karşılık gelen kısa kodların depolandığı yer. İlişkisel (PostgreSQL, MySQL) veya NoSQL (MongoDB, Redis) veritabanları kullanılabilir. Veritabanı, kısa kodların benzersizliğini sağlamalı ve hızlı okuma/yazma işlemleri sunmalıdır.
API Endpointleri
Kullanıcıların URL kısaltma ve yönlendirme işlemlerini gerçekleştirebileceği arayüzler. Genellikle bir POST endpointi yeni bir URL kısaltmak için ve bir GET endpointi kısa URL’leri orijinal adreslerine yönlendirmek için kullanılır.
Yönlendirme Mantığı
Kısa URL’e yapılan bir istek geldiğinde, veritabanından orijinal URL’i bulan ve kullanıcıyı bu adrese yönlendiren sunucu tarafı mantık. HTTP 301 (Kalıcı Olarak Taşındı) veya 302 (Geçici Olarak Taşındı) durum kodları kullanılır.
Bu mimariyi NestJS’in modüler yapısıyla birleştirdiğimizde, her bileşeni ayrı bir modül, servis veya kontrolör olarak tanımlayabilir, böylece kodun okunabilirliğini, test edilebilirliğini ve bakımını kolaylaştırabiliriz.
NestJS’e Giriş ve Proje Kurulumu
NestJS, TypeScript desteğiyle modern, ölçeklenebilir ve kurumsal düzeyde uygulamalar geliştirmek için tasarlanmış, aşamalı (progressive) bir Node.js çatısıdır. Geliştiricilere tanıdık gelen Angular benzeri bir yapı sunarak, Dependency Injection (Bağımlılık Enjeksiyonu), modülerlik, dekoratörler ve güçlü bir CLI (Command Line Interface) ile birlikte gelir. TypeScript’in statik tip denetimi, NestJS’in tip-güvenli bir ortam sunmasının temelini oluşturur.
Proje Oluşturma
İlk adım olarak, NestJS CLI’yi kurmamız ve yeni bir proje oluşturmamız gerekiyor.
npm install -g @nestjs/cli
nest new url-shortener-app
cd url-shortener-app
Bu komutlar, temel bir NestJS projesi oluşturacak ve gerekli bağımlılıkları yükleyecektir. Proje yapısı, src klasörü altında app.module.ts, app.controller.ts ve app.service.ts gibi dosyaları içerir.
Gerekli Bağımlılıklar
Projemiz için veritabanı etkileşimi, veri validasyonu ve diğer işlevler için ek bağımlılıklara ihtiyacımız olacak.
npm install @nestjs/typeorm typeorm pg class-validator class-transformer @nestjs/config
npm install --save-dev @types/express
* @nestjs/typeorm: NestJS ile TypeORM entegrasyonu sağlar.
* typeorm: NodeJS için güçlü bir ORM (Object-Relational Mapper).
* pg: PostgreSQL veritabanı sürücüsü. (Eğer MongoDB kullanacaksanız mongoose ve @nestjs/mongoose kurmalısınız.)
* class-validator: Gelen verileri doğrulamak için dekoratör tabanlı validasyon sağlar.
* class-transformer: Sınıf örneklerini düz JavaScript nesnelerine veya tam tersine dönüştürmek için kullanılır.
* @nestjs/config: Ortam değişkenlerini yönetmek için NestJS modülü.
* @types/express: Express.js için TypeScript tip tanımları (NestJS varsayılan olarak Express kullanır).
Bu bağımlılıklar, tip-güvenli bir şekilde veritabanı işlemleri yapmamızı ve gelen istekleri doğrulamamızı sağlayacak temel araçlardır.
Veritabanı Seçimi ve Entegrasyonu
URL kısaltıcımızın kalbi, orijinal URL’leri ve kısa kodları depolayan veritabanıdır. Bu makalede, ilişkisel bir veritabanı olan PostgreSQL’i ve NestJS ile sorunsuz bir şekilde çalışan TypeORM’i kullanacağız. TypeORM, veritabanı şemamızı TypeScript sınıfları (Entity’ler) aracılığıyla tanımlamamıza olanak tanır, bu da tip-güvenliğini en üst düzeye çıkarır.
TypeORM ve PostgreSQL Kurulumu
Yukarıda belirtilen bağımlılıkları yüklediyseniz, artık TypeORM’i NestJS uygulamamıza entegre edebiliriz.
Varlık (Entity) Tanımı
Veritabanımızdaki ShortUrl tablosunu temsil edecek bir TypeScript sınıfı oluşturalım. Bu sınıf, TypeORM dekoratörleri ile veritabanı sütunlarını ve kısıtlamalarını tanımlayacak.
src/url-shortener/entities/short-url.entity.ts:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index } from 'typeorm';
@Entity('short_urls') // Veritabanındaki tablo adı
export class ShortUrl {
@PrimaryGeneratedColumn('uuid') // Otomatik olarak UUID oluşturur
id: string;
@Column({ type: 'text', unique: true, nullable: false })
originalUrl: string;
@Index({ unique: true }) // Kısa kodun benzersizliğini garanti eder
@Column({ type: 'varchar', length: 10, nullable: false })
shortCode: string;
@CreateDateColumn({ type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
@Column({ type: 'timestamp with time zone', nullable: true })
expiresAt: Date | null; // URL'nin geçerlilik süresi, isteğe bağlı
}
Bu ShortUrl entity’si, her bir kısaltılmış URL için gerekli olan id, originalUrl, shortCode, createdAt ve expiresAt alanlarını tanımlar. Index({ unique: true }) dekoratörü, shortCode alanının veritabanında benzersiz olmasını sağlar, bu da çakışmaları önlemek için kritik öneme sahiptir.
Veritabanı Bağlantısının Yapılandırılması
app.module.ts dosyamızı TypeORM modülünü içerecek şekilde güncelleyelim. Ortam değişkenlerini yönetmek için @nestjs/config modülünü de kullanacağız.
src/app.module.ts:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ShortUrl } from './url-shortener/entities/short-url.entity';
import { UrlShortenerModule } from './url-shortener/url-shortener.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // ConfigModule'ü global olarak erişilebilir yapar
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
username: configService.get('DB_USERNAME'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_DATABASE'),
entities: [ShortUrl], // Tanımladığımız entity'leri buraya ekliyoruz
synchronize: true, // Geliştirme ortamında şema senkronizasyonu için true, üretimde false olmalı
logging: true, // SQL sorgularını konsola yazdırır
}),
}),
UrlShortenerModule, // URL kısaltıcı modülümüzü ekliyoruz
],
controllers: [],
providers: [],
})
export class AppModule {}
TypeOrmModule.forRootAsync kullanarak, veritabanı yapılandırmamızı ortam değişkenlerinden dinamik olarak alabiliriz. synchronize: true geliştirme aşamasında entity’lerimizde yaptığımız değişikliklerin veritabanı şemasını otomatik olarak güncellemesini sağlar. Ancak, üretim ortamında bu ayarın false olması ve veritabanı geçişleri (migrations) kullanılması şiddetle tavsiye edilir.
Proje kök dizininde bir .env dosyası oluşturarak veritabanı bağlantı bilgilerini tanımlayalım:
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=your_username
DB_PASSWORD=your_password
DB_DATABASE=url_shortener_db
Bu yapılandırma ile NestJS uygulamamız başlatıldığında PostgreSQL veritabanına bağlanacak ve ShortUrl entity’mizi kullanarak short_urls tablosunu otomatik olarak oluşturacaktır.
Tip-Güvenliği İçin DTO’lar ve Validasyon
Veri Aktarım Nesneleri (DTO’lar), ağ üzerinden taşınacak verilerin yapısını tanımlayan basit sınıflardır. NestJS’te DTO’lar, gelen HTTP isteklerinin gövdesini (body) veya sorgu parametrelerini tip-güvenli bir şekilde temsil etmek için kullanılır. class-validator ve class-transformer kütüphaneleriyle birlikte kullanıldığında, DTO’lar gelen verileri otomatik olarak doğrular ve dönüştürür, böylece kontrolörlerimizde manuel validasyon koduna olan ihtiyacı ortadan kaldırır.
Neden DTO Kullanmalıyız?
* Tip-Güvenliği: Gelen verilerin beklenen tiplerde olmasını garanti eder.
* Validasyon: @IsString(), @IsUrl() gibi dekoratörlerle kolayca validasyon kuralları tanımlanır.
* Kod Temizliği: Validasyon mantığını kontrolörlerden ayırır.
* Dokümantasyon: API’nin beklediği veri yapısını açıkça belirtir.
CreateShortUrlDto Tanımı
Yeni bir URL kısaltma isteği için bir DTO oluşturalım. Bu DTO, kullanıcının bize göndereceği originalUrl ve isteğe bağlı olarak customShortCode alanlarını içerecek.
src/url-shortener/dto/create-short-url.dto.ts:
import { IsNotEmpty, IsUrl, IsOptional, IsString, MinLength, MaxLength } from 'class-validator';
export class CreateShortUrlDto {
@IsNotEmpty({ message: 'Orijinal URL boş olamaz.' })
@IsUrl({}, { message: 'Geçerli bir URL giriniz.' })
originalUrl: string;
@IsOptional()
@IsString({ message: 'Özel kısa kod bir metin olmalıdır.' })
@MinLength(3, { message: 'Özel kısa kod en az 3 karakter olmalıdır.' })
@MaxLength(10, { message: 'Özel kısa kod en fazla 10 karakter olabilir.' })
// Regex ile sadece alfanumerik karakterlere izin verilebilir
// @Matches(/^[a-zA-Z0-9_-]+$/, { message: 'Özel kısa kod sadece alfanumerik karakterler, tire ve alt çizgi içerebilir.' })
customShortCode?: string;
}
Bu DTO’da, originalUrl alanının boş olamayacağını (@IsNotEmpty) ve geçerli bir URL formatında olması gerektiğini (@IsUrl) belirtiyoruz. customShortCode alanı ise isteğe bağlıdır (@IsOptional) ve belirli uzunluk kısıtlamalarına sahiptir.
ValidationPipe Kullanımı
NestJS, ValidationPipe aracılığıyla DTO’larımızın otomatik olarak doğrulanmasını sağlar. Bu boruyu global olarak veya belirli bir kontrolör/metot düzeyinde uygulayabiliriz.
src/main.ts dosyasında global olarak yapılandırma:
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, // DTO'da tanımlanmayan alanları otomatik olarak kaldırır
forbidNonWhitelisted: true, // DTO'da tanımlanmayan alanlar varsa hata fırlatır
transform: true, // Gelen veriyi DTO sınıfına dönüştürür
}));
await app.listen(3000);
}
bootstrap();
whitelist: true ve forbidNonWhitelisted: true seçenekleri, güvenlik ve veri bütünlüğü açısından önemlidir. Bunlar, isteğe DTO’da tanımlanmayan fazladan alanlar gönderildiğinde bu alanların göz ardı edilmesini veya hata fırlatılmasını sağlar. transform: true ise, gelen JSON verilerini otomatik olarak CreateShortUrlDto sınıfının bir örneğine dönüştürür, böylece tip-güvenli bir şekilde erişebiliriz.
Kısaltma Mantığı: Servis Katmanı
NestJS mimarisinde iş mantığı genellikle servis katmanında bulunur. Bu, kontrolörlerin yalnızca HTTP isteklerini yönetmesini, servislerin ise veritabanı işlemleri, harici API çağrıları ve karmaşık algoritmalar gibi iş süreçlerini yürütmesini sağlar. URL kısaltma ve orijinal URL’i bulma mantığını bir UrlShortenerService içinde uygulayacağız.
UrlShortenerModule Oluşturma
URL kısaltıcıya özgü tüm bileşenleri (servis, kontrolör, entity) bir modül altında toplamak, uygulamanın modülerliğini ve düzenini artırır.
nest generate module url-shortener
nest generate service url-shortener
nest generate controller url-shortener
src/url-shortener/url-shortener.module.ts:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UrlShortenerService } from './url-shortener.service';
import { UrlShortenerController } from './url-shortener.controller';
import { ShortUrl } from './entities/short-url.entity';
@Module({
imports: [TypeOrmModule.forFeature([ShortUrl])], // Bu modülde ShortUrl entity'sini kullanacağımızı belirtiyoruz
providers: [UrlShortenerService],
controllers: [UrlShortenerController],
exports: [UrlShortenerService], // Diğer modüllerin bu servise erişmesini sağlarız (eğer gerekirse)
})
export class UrlShortenerModule {}
Kısaltma Kodu Üretme Stratejileri
Kısa kodun benzersiz ve kısa olması önemlidir. İşte bazı stratejiler:
1. Rastgele String: Belirli bir karakter kümesinden (örneğin, alfanumerik karakterler ve semboller) rastgele bir string oluşturmak. Çakışma olasılığı düşüktür ancak yine de veritabanında kontrol etmek gerekir.
2. Base62 Dönüşümü: Veritabanındaki artan bir kimliği (ID) alıp Base62 (0-9, a-z, A-Z) formatına dönüştürmek. Bu, benzersizliği garanti eder ve daha kısa kodlar üretir. Ancak, ID’nin açığa çıkması güvenlik riski taşıyabilir ve veritabanı ID’si olmadan çalışmak zordur.
3. Hash Fonksiyonları: Orijinal URL’in bir hash’ini (MD5, SHA-256) almak ve ilk birkaç karakterini kullanmak. Çakışma olasılığı rastgele string’e göre daha yüksektir ve hash’ler genellikle daha uzundur.
Biz bu makalede, rastgele string üretme ve veritabanında benzersizlik kontrolü yapma yaklaşımını benimseyeceğiz, çünkü bu basit ve etkilidir.
src/url-shortener/url-shortener.service.ts:
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ShortUrl } from './entities/short-url.entity';
import { CreateShortUrlDto } from './dto/create-short-url.dto';
import { nanoid } from 'nanoid'; // Benzersiz kısa kodlar oluşturmak için nanoid kullanacağız
@Injectable()
export class UrlShortenerService {
constructor(
@InjectRepository(ShortUrl)
private shortUrlRepository: Repository,
) {}
private generateShortCode(): string {
// nanoid, kısa, benzersiz ve URL-safe ID'ler üretmek için idealdir.
// Varsayılan olarak 21 karakter üretir, biz 7-8 karakterle yetinebiliriz.
return nanoid(7);
}
async createShortUrl(createShortUrlDto: CreateShortUrlDto): Promise {
const { originalUrl, customShortCode } = createShortUrlDto;
// Eğer aynı orijinal URL zaten kısaltılmışsa, mevcut kısaltmayı döndür
const existingShortUrl = await this.shortUrlRepository.findOne({ where: { originalUrl } });
if (existingShortUrl) {
return existingShortUrl;
}
let shortCode: string;
if (customShortCode) {
// Eğer özel bir kısa kod belirtilmişse, benzersizliğini kontrol et
const existingCustomShortCode = await this.shortUrlRepository.findOne({ where: { shortCode: customShortCode } });
if (existingCustomShortCode) {
throw new ConflictException('Bu özel kısa kod zaten kullanılıyor.');
}
shortCode = customShortCode;
} else {
// Özel kısa kod yoksa, rastgele bir tane oluştur ve benzersizliğini garanti et
let isUnique = false;
while (!isUnique) {
shortCode = this.generateShortCode();
const found = await this.shortUrlRepository.findOne({ where: { shortCode } });
if (!found) {
isUnique = true;
}
}
}
const newShortUrl = this.shortUrlRepository.create({
originalUrl,
shortCode,
// expiresAt: new Date(Date.now() + 1000 60 60 24 30), // 30 gün sonra sona ersin
});
await this.shortUrlRepository.save(newShortUrl);
return newShortUrl;
}
async getOriginalUrl(shortCode: string): Promise {
const shortUrl = await this.shortUrlRepository.findOne({ where: { shortCode } });
if (!shortUrl) {
throw new NotFoundException('Kısaltılmış URL bulunamadı.');
}
// URL'nin süresi dolmuş mu kontrolü (isteğe bağlı)
if (shortUrl.expiresAt && shortUrl.expiresAt < new Date()) {
// URL'yi pasifize edebilir veya silebiliriz
// await this.shortUrlRepository.remove(shortUrl);
throw new NotFoundException('Kısaltılmış URL süresi dolmuş.');
}
return shortUrl.originalUrl;
}
}
Bu serviste nanoid kütüphanesini kullanarak kısa ve benzersiz kodlar üretiyoruz. createShortUrl metodunda, eğer kullanıcı özel bir kısa kod belirtmişse, bunun zaten kullanılıp kullanılmadığını kontrol ediyoruz. Aksi takdirde, rastgele bir kod üretiyor ve veritabanında benzersizliğini sağlıyoruz. getOriginalUrl metodu ise, verilen kısa koda karşılık gelen orijinal URL'i bulur veya bir NotFoundException fırlatır.
API Katmanı: Controller
Kontrolörler, gelen HTTP isteklerini işleyen ve uygun servis metotlarını çağıran sınıflardır. NestJS'te @Controller() dekoratörü ile tanımlanırlar ve @Get(), @Post(), @Put(), @Delete() gibi HTTP metot dekoratörleri ile belirli endpoint'leri yönetirler.
src/url-shortener/url-shortener.controller.ts:
import { Controller, Post, Body, Get, Param, Res, HttpStatus } from '@nestjs/common';
import { UrlShortenerService } from './url-shortener.service';
import { CreateShortUrlDto } from './dto/create-short-url.dto';
import { Response } from 'express'; // express'ten Response tipini içe aktarıyoruz
import { ConfigService } from '@nestjs/config';
// Yanıt için tip-güvenli bir arayüz tanımlayabiliriz
interface ShortUrlResponse {
originalUrl: string;
shortCode: string;
shortUrl: string;
}
@Controller() // Kök dizinde endpointleri tanımlayacağız
export class UrlShortenerController {
constructor(
private readonly urlShortenerService: UrlShortenerService,
private readonly configService: ConfigService,
) {}
@Post('shorten')
async shortenUrl(@Body() createShortUrlDto: CreateShortUrlDto): Promise {
const shortUrlEntity = await this.urlShortenerService.createShortUrl(createShortUrlDto);
const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3000'; // Ortam değişkeninden BASE_URL al
const fullShortUrl = ${baseUrl}/${shortUrlEntity.shortCode};
return {
originalUrl: shortUrlEntity.originalUrl,
shortCode: shortUrlEntity.shortCode,
shortUrl: fullShortUrl,
};
}
@Get(':shortCode')
async redirectToOriginalUrl(
@Param('shortCode') shortCode: string,
@Res() res: Response, // Express Response objesini doğrudan kullanmak için @Res() dekoratörü
): Promise {
const originalUrl = await this.urlShortenerService.getOriginalUrl(shortCode);
res.redirect(HttpStatus.MOVED_PERMANENTLY, originalUrl); // HTTP 301 Kalıcı Yönlendirme
}
}
POST /shorten endpoint'i, CreateShortUrlDto tipinde bir istek gövdesi alır. ValidationPipe sayesinde, bu DTO otomatik olarak doğrulanır ve tip-güvenli bir şekilde createShortUrlDto değişkenine atanır. Servis çağrıldıktan sonra, kısaltılmış URL'i içeren bir yanıt döndürülür. BASE_URL ortam değişkenini .env dosyanıza eklemeyi unutmayın (örn: BASE_URL=http://localhost:3000).
GET /:shortCode endpoint'i, dinamik bir parametre olan shortCode'u alır. Bu kod kullanılarak orijinal URL bulunur ve res.redirect() metodu ile kullanıcı doğrudan orijinal URL'e yönlendirilir. HttpStatus.MOVED_PERMANENTLY (301) SEO açısından daha iyidir, çünkü arama motorlarına bu kısa URL'in kalıcı olarak orijinal URL'e taşındığını bildirir.
Hata Yönetimi ve İstisnalar
NestJS, uygulamalarda oluşan hataları yönetmek için güçlü bir mekanizma sunar. Standart HTTP istisnaları (örn. NotFoundException, BadRequestException, ConflictException) doğrudan NestJS'ten import edilebilir ve fırlatılabilir. Bu istisnalar, otomatik olarak uygun HTTP durum kodları ve hata mesajlarıyla JSON yanıtlarına dönüştürülür.
Yukarıdaki servis ve kontrolör kodlarımızda NotFoundException ve ConflictException kullandık. Örneğin:
* throw new NotFoundException('Kısaltılmış URL bulunamadı.');
* throw new ConflictException('Bu özel kısa kod zaten kullanılıyor.');
Bu, kullanıcıya anlaşılır hata mesajları sunarken, geliştiricinin de sorunun kaynağını hızlıca tespit etmesine yardımcı olur. NestJS'in dahili hata yönetimi çoğu senaryo için yeterli olsa da, daha karmaşık veya özelleştirilmiş hata yanıtları için ExceptionFilter'lar oluşturulabilir.
Gelişmiş Özellikler ve İyileştirmeler
Temel bir URL kısaltıcı çalışır durumda olduğuna göre, şimdi uygulamamızı daha sağlam, kullanışlı ve ölçeklenebilir hale getirecek bazı gelişmiş özellikler ve iyileştirmeler üzerinde durabiliriz.
TTL (Time-To-Live) / Süre Sonu
Bazı URL'lerin belirli bir süre sonra geçerliliğini yitirmesi istenebilir. ShortUrl entity'mizde zaten expiresAt alanı bulunmaktadır.
* Uygulama: createShortUrl metodunda, isteğe bağlı olarak expiresAt alanını ayarlayabiliriz (örn: 30 gün sonrası).
* Otomatik Silme/Pasifleştirme: Süresi dolmuş URL'leri periyodik olarak kontrol etmek ve silmek veya pasifleştirmek için bir cron job (zamanlanmış görev) kullanabiliriz. NestJS'in @nestjs/schedule modülü bu tür görevler için idealdir.
npm install @nestjs/schedule
Ve app.module.ts'e ScheduleModule.forRoot() ekleyip, servisimize @Cron() dekoratörlü bir metot ekleyebiliriz.
Kullanıcı Tanımlı Kısa Kodlar
CreateShortUrlDto içinde customShortCode alanını zaten ekledik. Servisimizde bu kodun benzersizliğini kontrol ederek kullanıcının istediği kısa kodu kullanmasına izin veriyoruz. Bu, markalaşma veya kolay hatırlanabilirlik için önemlidir.
Analitik ve İstatistikler
Kısaltılmış URL'lerin ne kadar tıklandığını, hangi coğrafyalardan erişildiğini veya hangi zamanlarda daha popüler olduğunu takip etmek isteyebiliriz.
* Uygulama: ShortUrl entity'sine clickCount: number gibi bir alan ekleyebiliriz. getOriginalUrl metodunda her başarılı yönlendirmede bu sayacı artırabiliriz.
* Daha Detaylı Analitik: Her tıklamada kullanıcının IP adresi, tarayıcı bilgisi, referans URL gibi verileri ayrı bir ClickLog entity'sinde saklayabiliriz. Bu, daha zengin istatistikler sunar.
Rate Limiting (İstek Sınırlama)
API'mizi kötü niyetli kullanımlardan veya aşırı yüklenmelerden korumak için istek sınırlaması uygulamak önemlidir.
* Uygulama: @nestjs/throttler gibi bir kütüphane kullanarak belirli bir IP adresinden veya kullanıcıdan belirli bir zaman diliminde yapılabilecek istek sayısını sınırlayabiliriz.
npm install @nestjs/throttler
Ve app.module.ts'e ThrottlerModule'ü ekleyip, kontrolörlere veya metotlara @Throttle() dekoratörünü uygulayabiliriz.
Önbellekleme (Caching)
Sık erişilen kısa kodlar için veritabanı sorgularını azaltmak ve yanıt sürelerini iyileştirmek amacıyla önbellekleme kullanabiliriz. Redis gibi bir in-memory veritabanı bu iş için mükemmeldir.
* Uygulama: @nestjs/cache-manager modülü ve Redis adaptörü ile UrlShortenerService'deki getOriginalUrl metodu için önbellekleme uygulayabiliriz.
npm install cache-manager cache-manager-redis-store
npm install --save-dev @types/cache-manager-redis-store
Testler
Uygulamanın doğru çalıştığından emin olmak ve gelecekteki değişikliklerin mevcut işlevselliği bozmadığını garanti etmek için unit ve E2E (uçtan uca) testleri yazmak kritik öneme sahiptir. NestJS, @nestjs/testing modülü ile kapsamlı bir test altyapısı sunar.
* Unit Testleri: UrlShortenerService gibi iş mantığını içeren servislerin her bir metodunu izole bir şekilde test etmek.
* E2E Testleri: Uygulamanın API endpoint'lerini çağırarak ve beklenen yanıtları kontrol ederek tüm akışı test etmek.
Güvenlik
URL kısaltıcılar, kötü amaçlı URL'lerin yayılması için kullanılabilir.
* XSS ve SQL Injection: NestJS ve TypeORM, bu tür saldırılara karşı yerleşik korumalar sağlar. Ancak yine de kullanıcıdan gelen verileri dikkatlice işlemek önemlidir.
* Kötü Amaçlı URL Tespiti: Kısaltılmak istenen URL'leri bilinen kötü amaçlı URL listeleriyle (örn. Google Safe Browsing API) karşılaştırarak kara listeye alınmış siteleri engellemek.
Bu gelişmiş özellikler, URL kısaltıcımızı sadece işlevsel değil, aynı zamanda güvenli, performanslı ve bakımı kolay bir uygulamaya dönüştürecektir. Her bir özellik, uygulamanın farklı bir yönünü iyileştirir ve kullanıcı deneyimini zenginleştirir.
Sonuç
Bu makalede, NodeJS ve NestJS çatısını kullanarak sıfırdan tip-güvenli bir URL kısaltıcı uygulamasını nasıl inşa edeceğimizi detaylı bir şekilde inceledik. NestJS'in modüler yapısı, TypeScript'in sağladığı tip-güvenliği ve TypeORM gibi güçlü kütüphanelerle entegrasyonu sayesinde, sadece çalışan bir uygulama değil, aynı zamanda bakımı kolay, ölçeklenebilir ve sağlam bir çözüm geliştirdik.
Projemizin temel adımları arasında NestJS projesi kurulumu, PostgreSQL ile TypeORM entegrasyonu, veri aktarım nesneleri (DTO'lar) aracılığıyla gelen isteklerin tip-güvenli validasyonu, nanoid kullanarak benzersiz kısa kodlar üretme mantığı, API endpointlerinin oluşturulması ve NestJS'in dahili hata yönetimi mekanizmalarının kullanımı yer aldı.
Tip-güvenliğin önemi, özellikle CreateShortUrlDto gibi DTO'lar ve ShortUrl entity'si aracılığıyla açıkça görüldü. Bu yapılar, hem geliştirme sürecinde olası hataları azaltmamıza yardımcı oldu hem de kodun niyetini ve veri yapısını daha şeffaf hale getirdi. NestJS'in ValidationPipe gibi dahili boruları, bu tip-güvenli validasyon sürecini otomatikleştirmemizi sağlayarak geliştirici verimliliğini artırdı.
Uygulamamızı daha da iyileştirmek için TTL, analitik, rate limiting, önbellekleme ve kapsamlı testler gibi gelişmiş özelliklere de değindik. Bu iyileştirmeler, bir URL kısaltıcının gerçek dünya senaryolarında karşılaşabileceği zorluklara karşı daha dayanıklı olmasını sağlar.
Sonuç olarak, NestJS ve TypeScript'in birleşimi, modern ve güçlü bir backend uygulaması geliştirmek için mükemmel bir kombinasyon sunmaktadır. Bu makaledeki adımları takip ederek, sadece bir URL kısaltıcı inşa etmekle kalmayacak, aynı zamanda NestJS'in temel prensiplerini ve tip-güvenli geliştirme pratiklerini somut bir örnek üzerinden deneyimlemiş olacaksınız. Bu bilgiler, gelecekteki daha karmaşık projeleriniz için sağlam bir temel oluşturacaktır.
