にょっす速報🐮✋

Django + Next.js (App Router)を使った短縮URL作成サイト構築【解説】

この記事の目次


ワイの書いた短縮URLサービス

ある日、ワイは思った。

「短縮URLサービスって自分で作れるんちゃう?」

そう思い立ったワイは、休日に DjangoNext.js (App Router) を使って短縮URLサービスを構築することにしたんや。せっかくやから、今回はその記録を記事にして残しておくで!


ワイの目指すゴール

「とにかく動くものを早く作りたい」

せやから、こんな感じの機能だけに絞ることにした:

  1. 入力された長いURLを短縮して保存
  2. 短縮URLにアクセスすると元のURLにリダイレクト
  3. シンプルなWeb UI

ワイの環境

  • Django (バックエンド): URL管理とリダイレクト処理を担当。
  • Next.js (App Router): フロントエンドの表示を担当。
  • SQLite: 開発時の簡易的なデータベース。
  • Vercel: フロントエンドをデプロイするためのホスティング先。
  • Ubuntu: ワイの開発環境。

バックエンド(Django)の構築

1. プロジェクト作成

django-admin startproject shorturl
cd shorturl
python manage.py startapp urls

2. モデルを作る

from django.db import models

class ShortURL(models.Model):
    original_url = models.URLField()
    short_code = models.CharField(max_length=10, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)

ワイ「これで、長いURLと短縮コードを管理するモデルが完成や!」


3. 短縮コード生成ロジック

短縮コードはランダムに生成することにした。

import random
import string

def generate_short_code(length=6):
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

4. URLを短縮するエンドポイント

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .models import ShortURL
from .utils import generate_short_code

@csrf_exempt
def create_short_url(request):
    if request.method == 'POST':
        import json
        data = json.loads(request.body)
        original_url = data.get('url')

        # 短縮コード生成
        short_code = generate_short_code()

        # モデルに保存
        short_url = ShortURL.objects.create(original_url=original_url, short_code=short_code)
        return JsonResponse({'short_code': short_url.short_code})

    return JsonResponse({'error': 'Invalid request'}, status=400)

5. リダイレクト処理

from django.shortcuts import get_object_or_404, redirect

def redirect_to_original(request, short_code):
    short_url = get_object_or_404(ShortURL, short_code=short_code)
    return redirect(short_url.original_url)

6. URLルーティング

from django.urls import path
from .views import create_short_url, redirect_to_original

urlpatterns = [
    path('create/', create_short_url, name='create_short_url'),
    path('<str:short_code>/', redirect_to_original, name='redirect_to_original'),
]

フロントエンド(Next.js)の構築

1. プロジェクト作成

npx create-next-app@latest shorturl-frontend
cd shorturl-frontend

2. URLを短縮するフォーム

'use client';

import { useState } from 'react';

export default function Home() {
  const [url, setUrl] = useState('');
  const [shortCode, setShortCode] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const response = await fetch('http://localhost:8000/create/', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ url }),
    });
    const data = await response.json();
    setShortCode(data.short_code);
  };

  return (
    <div>
      <h1>URLを短縮するで!</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={url}
          onChange={(e) => setUrl(e.target.value)}
          placeholder="長いURLを入力してな"
        />
        <button type="submit">短縮する</button>
      </form>
      {shortCode && (
        <p>
          短縮URL: <a href={`http://localhost:8000/${shortCode}`}>{`http://localhost:8000/${shortCode}`}</a>
        </p>
      )}
    </div>
  );
}

3. スタイルの調整

body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-color: #f8f9fa;
}

form {
  display: flex;
  gap: 10px;
}

input {
  padding: 10px;
  font-size: 16px;
  width: 300px;
}

button {
  padding: 10px;
  font-size: 16px;
  background-color: #007bff;
  color: white;
  border: none;
  cursor: pointer;
}

完成したサイトを使ってみた結果

ワイ「これで、長いURLもサクッと短縮できるようになったで!」

  • 入力フォームにURLを入れると、短縮URLが表示される
  • 短縮URLにアクセスするとリダイレクトされる

よめ太郎の登場

よめ太郎「ちょっと待てや」

ワイ「なんや?」

よめ太郎「このコード、CORSの設定とかデプロイどうすんねん?」

ワイ「」


ワイの作った短縮URLサービス

ワイの「雑に作ったけど動く」短縮URLサービスを、よめ太郎さんがしっかり作り直す(リファクタリング)話です。


とある夜

娘(9歳)「パパ、URL短縮サービスってどう作るの?」

ワイ「おぉ、娘ちゃん、ええ質問や!」
ワイ「ちょうど今作っとるから見せたるでぇ〜」

娘「やった〜!」


ワイが作った初期版(クソコード)

ワイ「まず、バックエンドはDjangoや」
ワイ「URLを保存するモデルをこう作ったで」

from django.db import models

class URL(models.Model):
    original_url = models.URLField(max_length=2000)
    short_url = models.CharField(max_length=10, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)

娘「へぇ〜、original_urlが元のURLで、short_urlが短縮されたURLなんだね!」

ワイ「せや」
ワイ「ほんで、このモデルを元にAPIを作ったんや」

from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from .models import URL
import random
import string

def generate_short_url():
    return ''.join(random.choices(string.ascii_letters + string.digits, k=6))

def create_short_url(request):
    original_url = request.POST.get('original_url')
    short_url = generate_short_url()
    URL.objects.create(original_url=original_url, short_url=short_url)
    return JsonResponse({'short_url': short_url})

def redirect_to_original(request, short_url):
    url = get_object_or_404(URL, short_url=short_url)
    return JsonResponse({'original_url': url.original_url})

娘「なるほど〜」
娘「でも、ちょっと質問していい?」


娘の疑問

娘「短縮URLを生成するgenerate_short_urlって、ランダムで作るんだよね?」

ワイ「せやな」

娘「もし同じ短縮URLができちゃったらどうなるの?」

ワイ「・・・」

よめ太郎「それはバグるで」

ワイ「ファッ!?」

娘「それに、APIを作るのにビュー関数で書いてるけど・・・」
娘「もっといい書き方があるんじゃないの?」

ワイ「・・・」

よめ太郎「せや、ビュー関数をクラスベースにした方がええで」


よめ太郎のリファクタリング案(Django編)

よめ太郎「まず、短縮URLの生成ロジックをモデル側に移すんや」

import random
import string

class URL(models.Model):
    original_url = models.URLField(max_length=2000)
    short_url = models.CharField(max_length=10, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)

    @staticmethod
    def generate_short_url():
        return ''.join(random.choices(string.ascii_letters + string.digits, k=6))

    @classmethod
    def create_shortened_url(cls, original_url):
        short_url = cls.generate_short_url()
        while cls.objects.filter(short_url=short_url).exists():
            short_url = cls.generate_short_url()
        return cls.objects.create(original_url=original_url, short_url=short_url)

ワイ「ほほう、これでランダム生成の重複も防げるな」

よめ太郎「次は、Django REST Framework(DRF)でAPIを書くんや」

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import URL

class URLShortenerView(APIView):
    def post(self, request):
        original_url = request.data.get('original_url')
        if not original_url:
            return Response({'error': 'original_url is required'}, status=status.HTTP_400_BAD_REQUEST)
        url = URL.create_shortened_url(original_url)
        return Response({'short_url': url.short_url}, status=status.HTTP_201_CREATED)

class URLRedirectView(APIView):
    def get(self, request, short_url):
        url = get_object_or_404(URL, short_url=short_url)
        return Response({'original_url': url.original_url}, status=status.HTTP_200_OK)

娘「これでAPIのエラー処理もちゃんと追加されたね!」


Next.jsでフロントエンドを構築

ワイ「次はフロントエンドや!」
ワイ「URLを入力して短縮する画面をNext.jsで作るで」

'use client';

import { useState } from 'react';

export default function Home() {
  const [originalUrl, setOriginalUrl] = useState('');
  const [shortUrl, setShortUrl] = useState('');

  const handleShorten = async () => {
    const response = await fetch('/api/shorten', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ original_url: originalUrl }),
    });
    const data = await response.json();
    setShortUrl(data.short_url);
  };

  return (
    <div>
      <h1>短縮URLサービス</h1>
      <input
        type="text"
        value={originalUrl}
        onChange={(e) => setOriginalUrl(e.target.value)}
        placeholder="URLを入力してください"
      />
      <button onClick={handleShorten}>短縮する</button>
      {shortUrl && (
        <div>
          短縮URL: <a href={shortUrl}>{shortUrl}</a>
        </div>
      )}
    </div>
  );
}

娘「これでユーザーが簡単に短縮URLを作れるね!」


まとめ

ワイ「これで、DjangoとNext.jsを組み合わせた短縮URLサービスが完成や!」
よめ太郎「せやけど、セキュリティやパフォーマンスのチェックも忘れたらあかんで」

娘「分かった!次はそこも勉強するね!」


);

コメント(0件)


トピックス