Search
📚

EP6. Supabase URL 단축 서비스 로직 구현

생성일
2025/05/05 23:55
태그
Version 1
Supabase
마지막 업데이트

URL 단축 서비스 로직 구현

1. 요구조건 분석

https://도메인/{shortURL} 형태로 접근하면 사용자의 URL로 리다이렉트 시켜야 한다.
 shortURL과 redirectURL을 데이터베이스에 저장한다.
 shortURL는 유일해야한다.
 shortURL은 충분히 확보하기 위해 6자리의 영문 대소문자, 숫자로 구성한다. (헷갈리기 쉬운 I, l (대문자 I, 소문자 l)은 제외)
 shortURL로 해당 URL이 어떤 redirectURL로 연결되어야 하는지 조회 가능해야 한다.

2. ShortPathRepository

import { supabase } from "../../lib/supabase.ts"; export class ShortPathRepository { private generateRandomShortPath(): string { const characters = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789"; // 대문자 I, 소문자 l 제외 let result = ""; for (let i = 0; i < 6; i++) { const randomIndex = Math.floor(Math.random() * characters.length); result += characters[randomIndex]; } return result; } private async isShortPathDuplicate(shortPath: string): Promise<boolean> { const { data, error } = await supabase .from("v1_short_paths") .select("id") .eq("short_path", shortPath) .single(); if (error && error.code !== "PGRST116") { // PGRST116: No rows found console.error("Error checking short path duplication:", error); throw new Error("Failed to check short path duplication"); } return !!data; // 중복이면 true 반환 } async insertShortPath(redirectURL: string): Promise<string> { let shortPath = this.generateRandomShortPath(); // 중복되지 않는 shortPath 생성 while (await this.isShortPathDuplicate(shortPath)) { shortPath = this.generateRandomShortPath(); } const { error } = await supabase.from("v1_short_paths").insert({ short_path: shortPath, redirect_url: redirectURL, }); if (error) { console.error("Error inserting short path:", error); throw new Error("Failed to insert short path"); } return shortPath; } async getRedirectURLByShortPath(shortPath: string): Promise<string | null> { const { data, error } = await supabase .from("v1_short_paths") .select("redirect_url") .eq("short_path", shortPath) .single(); if (error) { return null; } return data?.redirect_url || null; } }
TypeScript
복사

3. ShortPathService

import { ShortPathRepository } from "../repositories/shortPathRepository.ts"; export class ShortPathService { private shortPathRepository: ShortPathRepository; constructor() { this.shortPathRepository = new ShortPathRepository(); } async insertShortPath(redirectURL: string): Promise<string> { return await this.shortPathRepository.insertShortPath(redirectURL); } async getRedirectURLByShortPath(shortPath: string): Promise<string | null> { return await this.shortPathRepository.getRedirectURLByShortPath(shortPath); } }
TypeScript
복사

4. ShortPathController

import { ShortPathService } from "../services/shortPathService.ts"; import { Context } from "https://deno.land/x/hono/mod.ts"; export class ShortPathController { private shortPathService: ShortPathService; constructor() { this.shortPathService = new ShortPathService(); } async postShortPathV1(c: Context) { try { // 요청 본문에서 redirectURL 추출 const body = await c.req.json(); const redirectURL = body.redirectURL; if (!redirectURL || typeof redirectURL !== "string") { return c.json({ error: "Invalid or missing redirectURL" }, 400); } // ShortPath 생성 const shortPath = await this.shortPathService.insertShortPath(redirectURL); return c.json({ shortPath }, 201); // 생성된 shortPath 반환 } catch (error) { console.error("Error in postShortPathV1:", error); return c.json({ error: "Failed to create short path" }, 500); } } async getRedirectURLByShortPathV1(c: Context) { try { // URL 파라미터에서 shortPath 추출 const shortPath = c.req.query("shortPath"); console.log(shortPath); if (!shortPath || typeof shortPath !== "string") { return c.json({ error: "Invalid or missing shortPath" }, 400); } // ShortPath로부터 redirectURL 조회 const redirectURL = await this.shortPathService.getRedirectURLByShortPath(shortPath); if (!redirectURL) { return c.json({ error: "Short path not found" }, 404); } return c.json({ redirectURL }, 200); // 조회된 redirectURL 반환 } catch (error) { console.error("Error in getRedirectURLByShortPathV1:", error); return c.json({ error: "Failed to fetch redirect URL" }, 500); } } }
TypeScript
복사

5. CROS

웹브라우저의 경우 현재의 도메인과 다른 도메인으로 API 요청시 CORS 오류가 발생할 수 있습니다. 도메인을 구매하여 사용하는 경우엔 문제없겠지만 보통 Vercel + Supabase 조합만으로 사용하는 경우엔 도메인이 달라 API 요청이 불가할 수 있습니다.
다른 도메인에서의 API 호출을 허용하기 위해 cors 추가 처리를 진행해야 합니다.
supabase/functions/api/index.ts
import { Hono } from "https://deno.land/x/hono/mod.ts"; import { shortPathRouter } from "./routers/shortPathRouter.ts"; import { cors } from "https://deno.land/x/hono/middleware.ts"; // Hono 인스턴스를 생성하여 애플리케이션을 설정합니다. const app = new Hono(); app.use("*", cors()); // 기본 경로를 "/api"로 설정하고, "/users" 경로에 대해 userRouter를 설정합니다. app.basePath("/api").route("/shortPath", shortPathRouter); // 애플리케이션을 시작하여 요청을 수신합니다. Deno.serve(app.fetch);
TypeScript
복사
Hono framework에서 지원하는 cors 설정을 적용합니다.
적용 후 bash scripts/deploy.sh 명령어로 Public 배포합니다.

6. Postman을 활용한 API 테스트

POST 요청
Database에 추가된 shortPath와 redirectURL
GET 요청
POST 요청을 통해 redirectURL을 등록하며, GET 요청을 통해 shortPath로 redirectURL을 조회 가능해졌습니다.
끝.