Webフレームワーク

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 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
といった選択が、選択基準としては良いと思われます。

数で見るニーズ

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

機能比較

項目DjangoLaravel勝者理由
総合的な使い勝手普通良いLaravel言語がPythonなので、AIもWebもPytnonで…というのは魅力的だが、単純にWebフレームワークとして見ると、文章等情報の読み易さ・充実度合い・雛形自動生成機能の範囲・リリースのサイクル等で、後発の立場のLaravelの方が遥かに良い。
Webフレームワークに期待するのは、定型処理のスキップ・カプセル化だが、Django本体自身は残念ながらUI方面の貧弱・自分で実装する部分の残し具合等、先行したが故の古臭さから抜け切れていない。
Laravelはリリースのサイクルも定め、開発の進行が定期的・早い
基盤言語PythonPHPDjango基盤の言語としてはPythonの方がPHPより簡素でオフラインと共通で使えるので使い易い
日本での求人数少ない多いLaravelLaravelが4倍程上
世界での求人数多い少ないDjangoDjangoが3倍程上
Githubstarの数(2020/06)6万5万LaravelLaravelの方が数が多い
Webサーバーの立ち上げ普通簡単LaravelPHPに比べPythonの方がインストールで難儀する点はまだ多い。
又LaravelはHomestead、Laradock等開発環境立ち上げ支援が充実している
サーバー運用やや難やや簡単Laravelアプリケーションサーバが別途立ち上がる分Djangoの方が複雑
大規模開発簡単やや難Djangoアプリケーションの分離レベルがDjangoの方が高い分、設計的により疎結合で開発し易い面はある
モデルの定義やや難やや簡単LaravelDjangoはmodle.pyに集約して、migrationsは別途自動生成出来るメリットがある=migrations.pyを見れば現在のモデルの状況が分かるのは利点。 但しフィールド名の名付け方とかいまいち&comment()機能等LaravelにあってもDjangoに無い機能もある
DBへのデータ一括取込普通やや難DjangoDjangoはコマンドのみでのJSON/XMLだが取込み対応
Laravelは自分で書く必要ある
ORMの使い易さ簡単簡単LaravelLaravelの方が覚え易い文法
View(Template)の文法の書き易さ簡単簡単Djangoコードを書く機能を全く許さないDjangoの方が設計的には一応良いという事で。LaravelはPHPコードを書ける
サイト共通のBase Viewの作り易さ簡単簡単Laravel正直どちらも変わらないが、デフォルトで出来るUI的にはLaravelの方が上
ページネーションの実装し易さやや簡単簡単LaravelLaravelの方がクールなUIのページネーションをより簡単に実現してくれる
CSSの管理し易さやや難簡単LaravelLaravelはデフォルトでSASSコンパイルの仕組みが組み込まれてる。
Djangoは自分で3rd Partyライブラリ等を入れて機能拡張する必要がある
Single Page Applicationの実装し易さ簡単Laravelサポート機能が組み込まれてる
認証機能の実装し易さやや難簡単LaravelDjangoではView=実質Controllerレベルで認証のサポートがあるが結局諸々実装しないと使えない。
Laravelはコマンド一つで使えるようになる
SNSログインの実装し易さ普通簡単LaravelLaravelのパッケージとして機能が提供されている
フォーム機能の実装し易さ普通普通LaravelInputのタイプが豊富
管理画面の実装し易さ簡単普通DjangoLaravelは3rd Partyの管理画面機能を追加しないといけない。
Djangoはデフォルトでモデルから管理画面を自動生成する機能が付いている
メール機能の実装し易さ簡単普通だが機能豊富LaravelLaravelの方がただメールを送るよりもキューに入れておく等多様なパターンにシンプルに対応出来る構造になっている
文章等情報充実度限られる豊富LaravelLaravelの方が相当充実&精錬されてる
言語の将来性PythonPHPDjango暫くは勢い的にAI用途に支えられたPythonに軍配。但しPHPも進化早い+WP&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 + PostgreSQL + Python


Laravel

nginx + PHP + MariaDB


モデルの定義

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/4.2/html


管理画面の実装

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に脅かされる可能性は有


Laravel

PHPはWPが栄え続ける限り絶える事はないというチートさはある。
言語としてはいまいちだが、使って作られてるソフト・FWの強さ的に、消滅は遠いと思われる。