Django + Next.js (App Router)を使った短縮URL作成サイト構築【解説】
この記事の目次
ワイの書いた短縮URLサービス
ある日、ワイは思った。
「短縮URLサービスって自分で作れるんちゃう?」
そう思い立ったワイは、休日に Django と Next.js (App Router) を使って短縮URLサービスを構築することにしたんや。せっかくやから、今回はその記録を記事にして残しておくで!
ワイの目指すゴール
「とにかく動くものを早く作りたい」
せやから、こんな感じの機能だけに絞ることにした:
- 入力された長いURLを短縮して保存
- 短縮URLにアクセスすると元のURLにリダイレクト
- シンプルな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サービスが完成や!」
よめ太郎「せやけど、セキュリティやパフォーマンスのチェックも忘れたらあかんで」
娘「分かった!次はそこも勉強するね!」