VPS

Django vs Laravel比較分析: どちらがより効率的にWebサイトを作れる?

文章目的

フルスタック型のWebフレームワークとして人気の
– PHPベースのLaravel
– PythonベースのDjango
どちらが良いのかを比較してみます。

Django
公式サイト | Github | 公式文章

Laravel
公式サイト | Github | 公式文章

その2つを比較対象に選んだのは、Webフレームワークとしての人気に加え、プログラミング言語として
– PHPはWordPressがあり、WPのシェアが世界のWebサイトにおいていつかは過半数のシェアに届きそうな位まだ伸び続けている為
– PythonはAIでの利用が伸びている為
それぞれ暫く安泰なプログラミング言語と言えるので、暫くはその言語のニーズがあるものとして学んで損がないWebフレームワークと言える為です。

尚、Ruby on Railsは、昔はユニークでしたが、Laravel等他言語の後発のWebフレームワークも同様な機能を実現出来るようになった為、その価値の低下&実際Rubyでなければいけないという理由が希薄な為、将来性としては怪しい感じになっているので、比較検討対象に入れていません。

発表の順番は
2005/07 Django
2005/12 Ruby on Rails
2011/06 Laravel
という歴史になっており、LaravelはRuby on Railsの影響を受けている為、Django以上にコマンドの類似性等の面で親和性が高いです。
その上で、Laravelは後々見てみると分かりますが、後発な分先進の良い所を取込んでるのと、更新がより頻繁な分、より今の時代の機能(UI方面等)を公式として取り込んでいる事が感じられます。

尚、こうしたWebフレームワークの比較記事では、各Webフレームワークの機能をリストして
「どのWeb Frameworkも同じ様な機能を提供していて良いものだから後は君の選択次第」
としてる記事が世の中の大半ですが、この文章では結論としてどっちが良いのかをあえて示す形にしています。
どちらの言語であっても問題無い方が迷った時に選択の判断指針の一助にして頂ければと。

先にDjangoとLaravelのどちらが良いか・選択基準はどうすると良いかの結論

結論から言えば、PHPのPythonに対して言語として劣る部分を考慮しても、Web FrameworkとしてはLaravelの方が機能としては大きく優れており、開発効率も上げてくれます。
Object Oriented Programming要素に欠ける部分も、Laravel自体が文字列(Str)や配列(Arr, Collection)の機能を大幅に拡張するHelper/Wrapperを提供しており、言語的差異によるデメリットは小さくなっている部分があります。
PHPもPythonも両方使えて当然、もしくは両方それぞれ別の人材として採用ができるのならば、Laravelで問題ないでしょう。

その上で、更に詳細に行くとすると、

– GISに凄く強いニーズがあるのならDjangoも候補
– Pythonに兎に角全て寄せたいならDjango

– WebはPHP(+JS)、オフラインはPython等と分けて運用できるのなら、純粋にWeb FWとしての機能を評価してLaravel
といった選択が、選択基準としては良いと思われます。

尚、自分はまずこうしたFWの使い勝手の比較を経て、Laravelを選んで
ハローワーク+
は作りました(毎日数万UUに利用される規模のサービス)。
オフライン側はベースはPythonで作ったので、Web App側もPythonに統一した方が技術セット的に良さげに見えますが、別の言語を使ってでも、Web App側はLaravelの方が開発効率良いなー、という結論になりました。

数で見るニーズ

Google Trendsの検索数変化状況(日本)を見ると、Laravelがリードしている事が伺えます。

GithubスターでもLaravelがWebフレームワークでは1位になりました。

但し、米国では、Python系フレームワークの方が検索数は多いです。

そうした第三者的な指標はありますが、以下では、自分で実際に使ってみて、純粋に使い勝手として評価を行います。
なお、言語の仕様自体の評価としては、Pythonの方がPHPより良いと認識しています。

各Webフレームワークの個別の記事

  1. Djangoのインストール/設定/開発方法
  2. Laravelのインストール/設定/開発方法

各Webフレームワークの検証時に使ったサンプルコード

機能の検証を行うにおいては、各WebフレームワークをDocker環境で開発を立ち上げるのに役立つテンプレート構築ついでに行っています。
利用してみたい方は、以下のGithubレポジトリをご活用下さい。

Django https://github.com/hikarine3/docker-django-postgresql
Laravel https://github.com/hikarine3/docker-laravel-boilerplate

機能比較

項目 Django Laravel 勝者 理由
総合的な使い勝手 普通 良い Laravel 言語がPythonなので、AIもWebもPytnonで…というのは魅力的だが、単純にWebフレームワークとして見ると、文章等情報の読み易さ・充実度合い・雛形自動生成機能の範囲・リリースのサイクル等で、後発の立場のLaravelの方が遥かに良い。
Webフレームワークに期待するのは、定型処理のスキップ・カプセル化だが、Django本体自身は残念ながらUI方面の貧弱・自分で実装する部分の残し具合等、先行したが故の古臭さから抜け切れていない。
Laravelはリリースのサイクルも定め、開発の進行が定期的・早い
基盤言語 Python PHP Django 基盤の言語としてはPythonの方がPHPより簡素でオフラインと共通で使えるので使い易い
日本での求人数 少ない 多い Laravel Laravelが4倍程上
世界での求人数 多い 少ない Django Djangoが3倍程上
Githubstarの数(2020/06) 6万 5万 Laravel Laravelの方が数が多い
Webサーバーの立ち上げ 普通 簡単 Laravel PHPに比べPythonの方がインストールで難儀する点はまだ多い。
又LaravelはHomestead、Laradock等開発環境立ち上げ支援が充実している
サーバー運用 やや難 やや簡単 Laravel LaravelはFPMというFast CGIによる定番のアプリケーションサーバーがあるが、djangoはgunicornというアプリケーションサーバが別途必要になったり、Djangoの方が複雑
大規模開発 簡単 やや難 Django アプリケーションの分離レベルがDjangoの方が高い分、設計的により疎結合で開発し易い面はある
モデルの定義 やや難 やや簡単 Laravel Djangoはmodle.pyに集約して、migrationsは別途自動生成出来るメリットがある=migrations.pyを見れば現在のモデルの状況が分かるのは利点。
但しフィールド名の名付け方とかいまいち&comment()機能等LaravelにあってもDjangoに無い機能もある。
加えてLaravelはmigration履歴をまとめる機能もversion8から出したので、Djangoの方が現状のテーブル状況把握し易いというメリットもなくなった
DBへのデータ一括取込 普通 やや難 Django DjangoはコマンドのみでのJSON/XMLだが取込み対応
Laravelは自分で書く必要ある。
とはいえ、実際そのまま取り込むのではなく、半角・全角の統一等、どうせ前処理も必要になるのは当然なので、そこまでデメリットでもないが
ORMの使い易さ 簡単 簡単 Laravel Laravelの方が覚え易い文法
View(Template)の文法の書き易さ 簡単 簡単 Laravel コードを書く機能を全く許さないDjangoの方が設計的には良さげに見えなくもないが、実運用上は書けちゃう選択肢もあるLaravelの方が便利だったりもする。また、Laravelはversion8からはLivewireの組み込みも可能になり、React/Vue/Ajaxを気にせず、バックエンドの記述だけで、動的なページを作れるという選択肢も生まれ、ますます生産性が高まった
サイト共通のBase Viewの作り易さ 簡単 簡単 Laravel 正直どちらも変わらないが、デフォルトで出来るUI的にはLaravelの方が上
ページネーションの実装し易さ やや簡単 簡単 Laravel Laravelの方がクールなUIのページネーションをより簡単に実現してくれる。またLaravelはv8の途中からはカーソルベースのページネーションが可能になって、どんなにページ数が増えてもサーバー負荷がページめくりで死なないという、運用上はとても嬉しい機能が加わった
CSSの管理し易さ やや難 簡単 Laravel LaravelはデフォルトでSASSコンパイルの仕組みが組み込まれてる。
Djangoは自分で3rd Partyライブラリ等を入れて機能拡張する必要がある
Single Page Applicationの実装し易さ 簡単 Laravel サポート機能が組み込まれてる
認証機能の実装し易さ やや難 簡単 Laravel DjangoではView=実質Controllerレベルで認証のサポートがあるが結局諸々実装しないと使えない。
Laravelはコマンド一つでView含めて使えるようになる。
ある意味Laravelの最大のメリット
SNSログインの実装し易さ 普通 簡単 Laravel Laravelのパッケージとして機能が提供されている
フォーム機能の実装し易さ 普通 普通 Laravel Inputのタイプが豊富
管理画面の実装し易さ 簡単 普通 Django Laravelは3rd Partyの管理画面機能を追加しないといけない。
Djangoは無骨ではあるがデフォルトでモデルから管理画面を自動生成する機能が付いている
メール機能の実装し易さ 簡単 普通だが機能豊富 Laravel Laravelの方がただメールを送るよりもキューに入れておく等多様なパターンにシンプルに対応出来る構造になっている
文章等情報充実度 限られる 豊富 Laravel Laravelの方が相当充実&精錬されてる
言語の将来性 Python PHP Django 暫くは勢い的にAI用途に支えられたPythonに軍配。
但しPHPも進化が早い上に、Webサイトの裏側の仕組みとしてのシェアを只管上げ続けているWordPressと言う存在がある為、Web系の言語として一定のポジションを占め続ける事に危惧はまだない
開発の活発さ 活発 Laravel 例えば2021/09にDjangoはBugfixを一つリリースしてるだけですが、Laravelは5つのバージョンを出し、新機能・Bugix含め多数リリースしており、明らかに開発の活発さではLaravelの方が上回っています。逆に言えばDjangoは安定しているとは言えますが、機能的にLaravelに劣っている部分が更に差を広げられているとも言えます。

比較項目の詳細分析・記録

Webサーバの立ち上げ

Django

pip install django;
PJ=djangopj;
django-admin startproject $PJ;
cd $PJ;
python manage.py runserver;

Laravel

composer global require laravel/installer;
PJ=examplepj;
laravel new $PJ;
cd $PJ;
php artisan key:generate;
php artisan serve;

Webサーバの運用の定番構成

Django

Gunycorn + nginx + Python + PostgreSQL(or MariaDB)


Laravel

nginx + PHP + MariaDB(or PostgreSQL)


モデルの定義

DjangoはMigrationでは一つのファイルを触り続けるが、LaravelはMigration毎に履歴的に変更用ファイルを作っていく事になる。
但し、Laravel8から、そうした変更履歴をまとめる機能も提供されている

Django

https://docs.djangoproject.com/en/3.0/topics/migrations/

from django.db import models

from django.utils import timezone

class Country(models.Model):
  name = models.CharField(max_length=255)
  key = models.CharField(max_length=2)
  created_at = models.DateTimeField(default=timezone.now)
  modified_at = models.DateTimeField(blank=True, null=True)

  class Meta:
    verbose_name_plural = "countries"

  def publish(self):
      self.modified_at = timezone.now()
      self.save()

  def __str(self):
      return self.name

Laravel

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCountriesTable extends Migration
{
    public function up()
    {
        Schema::create('countries', function (Blueprint $table) {
            $table->string('id')->unique()->comment('iso2 country code');
            $table->string('name');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('countries');
    }
}
<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
}

DBへのデータ一括取込

Django

https://docs.djangoproject.com/en/3.0/howto/initial-data/

CSVに対応せず、JSON/XMLのみ対応なので注意。
指定形式のJSONファイルを作ったら

manage.py loaddata JSONファイル;

と打つとデータを入れられる。

CSVを取込みたければ、3rd PartyのLibraryを使うか自分で実装する必要がある。
(所定書式のJSON/XMLの用意自体にプログラミングが必要な事も多いと思いますが)。

尚、bulk_createとかを使って、プログラミングを通じて、データ生成した方が楽な事もあるかとは思います。


Laravel

CSVから取り込む機能を実装した例

<?php
use Illuminate\Database\Seeder;
use Hikarine3\CsvParser;
use Carbon\Carbon;

class CountrySeeder extends Seeder
{
    public function run()
    {
        ini_set('memory_limit','1024M');
        $file = __DIR__ .'/data/locations/countries.tsv';
        if( !file_exists($file) ) {
            die($file . " doesn't exist.");
        }
        $parser = new CsvParser();
        $datas = $parser->parse(['delimiter' => "\t", 'file' => $file]);
        foreach ($datas as $data) {
            if(isset($data['id']) && isset($data['name']) ) {
                DB::table('countries')->insert([
                    'id' => strtolower($data['id']),
                    'name' => $data['name'],
                    'created_at' => Carbon::now(),
                    'updated_at' => Carbon::now()
                ]);
            }
        }
    }
}

ORMの使い易さ

Django

CountryのController(DjangoではView)の実装例

from django.shortcuts import render
from .models import Country
from .models import Prefecture
from pprint import pprint
from django.core.paginator import Paginator

# Create your views here.
def index(request):
  pgsz = 3
  country_list = Country.objects.order_by('name')
  paginator = Paginator(country_list, pgsz)
  page_number = request.GET.get('page')
  page_obj = paginator.get_page(page_number)
  return render(request, 'geo/index.html', { 'page_obj': page_obj })

Laravel

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class Country extends Controller
{
    public function list() {
        $pgsz = 10;
        $countries = DB::table('countries')->orderBy('name', 'asc')->paginate($pgsz);
        return view('countries.list')->with(['countries' => $countries]);
    }
}

View(Template)

Django

<h2>Country list</h2>
{% extends "base.html" %}

{% block title %}
Country list
{% endblock %}

{% block content %}
<h1>Country list</h1>
{% if page_obj %}
    <ul>
    {% for country in page_obj %}
        <li><a href="/countries/{{ country.key }}/">{{ country.name }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No countries are available.</p>
{% endif %}

Laravel

@extends('layouts.app')

@section('title')
Top page
@endsection

@section('content')
<div>
    <h3>Countries</h3>
    Only <a href="/geo/us/">United States</a> has state list as data in default status.
    <ul class="jp_map">
    @foreach ($countries as $country)
        <li><a href="/geo/{{ $country->id }}/">{{ $country->name }}</a></li>
    @endforeach
    </ul>
    {{ $countries->links() }}
</div>

@endsection

サイト共通のBase View

Django

ベースとなるtemplate

{% load static %}
<html>
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <img src="{% static "images/sitelogo.png" %}" alt="Logo">
    {% block content %}{% endblock %}
</body>
</html>

Laravel

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                        <!-- Authentication Links -->
                        @guest
                            <li class="nav-item">
                                <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                            </li>
                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }} <span class="caret"></span>
                                </a>

                                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>
</html>

ページネーション

Django

https://docs.djangoproject.com/en/3.0/topics/pagination/

Controller

from django.core.paginator import Paginator
from django.shortcuts import render

from myapp.models import Contact

def listing(request):
    contact_list = Contact.objects.all()
    paginator = Paginator(contact_list, 25) # Show 25 contacts per page.

    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    return render(request, 'list.html', {'page_obj': page_obj})

View

{% for contact in page_obj %}
    {# Each "contact" is a Contact model object. #}
    {{ contact.full_name|upper }}<br>
    ...
{% endfor %}

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1">&laquo; first</a>
            <a href="?page={{ page_obj.previous_page_number }}">previous</a>
        {% endif %}

        <span class="current">
            Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">next</a>
            <a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
        {% endif %}
    </span>
</div>

Laravel

Controller

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class Country extends Controller
{
    public function list() {
        $pgsz = 10;
        $countries = DB::table('countries')->orderBy('name', 'asc')->paginate($pgsz);
        return view('countries.list')->with(['countries' => $countries]);
    }
}

View

@extends('layouts.app')

@section('title')
Top page
@endsection

@section('content')
<div>
    <h3>Countries</h3>
    Only <a href="/geo/us/">United States</a> has state list as data in default status.
    <ul class="jp_map">
    @foreach ($countries as $country)
        <li><a href="/geo/{{ $country->id }}/">{{ $country->name }}</a></li>
    @endforeach
    </ul>
    {{ $countries->links() }}
</div>

@endsection

認証機能の実装

Django

https://docs.djangoproject.com/ja/3.0/topics/auth/default/

View(Laravelで言うController)は用意されてるが、Templateは公式の例等からとってきて作る必要がある。


Laravel

UIと完全にセットで1コマンドで実装出来る機能が提供されている


SNSログインの実装

Django

social-app-djangoを使って実装する


Laravel

https://laravel.com/docs/7.x/socialite


Form機能の実装

Django

https://docs.djangoproject.com/en/3.0/topics/forms/


Laravel

https://laravel.com/docs/master/blade#forms


管理画面の実装

Django

設定を触ると簡単にテーブルに紐づく形で管理画面を作れる


Laravel

https://voyager.devdojo.com/
の様な3rd Party提供の自動生成管理画面はあるが、Laravel付属という形のではない


メール機能

Django

https://docs.djangoproject.com/en/3.0/topics/email/


Laravel

https://laravel.com/docs/7.x/mail


文献の充実具合

Django

https://docs.djangoproject.com/en/3.0/


Laravel

https://laravel.com/docs/7.x


言語の将来性

Django

Python
今はAI系の処理に支えられて盛り上がっていて、教育への組み込みも考えても、将来有望。
但し、結局解析系では大量データ処理&速度&省資源化が重要な為、Rustの様なC系よりかは簡易だがコンパイル系で速度が速い言語に脅かされる可能性は十分有


Laravel

PHPは未だWebサイトでシェアが増え続けているWordPressが栄え続ける限り絶える事はないというチートさはある。
PHPは特に固い言語・一貫性を好まれる方から、言語としてはいまいちとされる部分があるが、使って作られてるソフト・FWの強さと、Webアプリとしては許容される緩さによる開発効率の利点的に、消滅は遠いと思われる。

VPSニュース
2022/10/24 AWS Lightsail[解説]のベンチマークをGeekbenchで取り直しました
2022/8/26 UpCloud[解説]が近い将来Kubernetesサービスの提供開始を発表。このサイトで公開の海外発VPSはk8s対応がこれで標準に
2022/8/16 UpCloud[解説]のベンチマークを全て取得し直して更新しました[更に読む]