Featured image of post Mojolicious入門 — Mojolicious::Liteで始めるPerlのWeb開発

Mojolicious入門 — Mojolicious::Liteで始めるPerlのWeb開発

Perl製モダンWebフレームワークMojolicious(Mojolicious::Lite含む)の基本、ルーティング、テンプレート、JSON API、WebSocket、デプロイ方法などを初心者向けに解説します。

Mojoliciousとは

Mojoliciousは、Perlで書かれたモダンなWebフレームワークです。「The Web in a Box!」というキャッチフレーズの通り、Webアプリケーション開発に必要なすべてがこれひとつに詰まっています。

Mojoliciousの特徴

  1. 依存関係ゼロ: コアPerl以外の依存モジュールがありません。CPANからインストールするだけで、すぐに使い始められます。
  2. フルスタック: HTTPクライアント、WebSocketサポート、テンプレートエンジン、JSONパーサーなど、Web開発に必要なすべてが含まれています。
  3. 非同期I/O: イベントループを内蔵し、高性能な非同期処理が可能です。
  4. 開発が楽しい: 美しいAPIと優れたドキュメント、充実したデバッグ機能により、開発体験が素晴らしいです。
  5. RESTful: RESTful APIの開発に最適化されています。

Mojolicious::Liteとは

Mojoliciousには2つのモードがあります:

  • Mojolicious::Lite: 単一ファイルで完結する軽量版。プロトタイピングや小規模アプリに最適。
  • Mojolicious: 大規模アプリケーション向けのフルスタック版。MVCパターンで構成。

今回は初心者向けに、Mojolicious::Liteを中心に解説します。

インストール

CPANからインストールするだけです:

1
cpanm Mojolicious

または、cpanmを使わない場合:

1
curl -L https://cpanmin.us | perl - -M https://cpan.metacpan.org -n Mojolicious

インストール後、バージョンを確認:

1
mojo version

最新版は9.x系です(2025年12月現在)。

Hello World - 最小のWebアプリケーション

まずは最もシンプルな例から始めましょう。hello.plという名前で以下のコードを作成します:

1
2
3
4
5
6
7
8
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;

get '/' => sub ($c) {
  $c->render(text => 'Hello, Mojolicious!');
};

app->start;

実行方法:

1
perl hello.pl daemon

ブラウザでhttp://localhost:3000にアクセスすると、「Hello, Mojolicious!」と表示されます。

コードの解説

  • use Mojolicious::Lite -signatures;: Mojolicious::Liteを読み込み、サブルーチンシグネチャを有効化
  • get '/' => sub ($c) { ... };: GETリクエストのルーティングを定義。$cはコントローラーオブジェクト
  • $c->render(text => 'Hello, Mojolicious!');: テキストレスポンスを返す
  • app->start;: アプリケーションを起動

たったこれだけで、Webアプリケーションが完成です!

ルーティング - URLと処理の対応

Mojoliciousのルーティングは非常に柔軟です。様々なHTTPメソッドとURLパターンに対応できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;

# GETリクエスト
get '/' => sub ($c) {
  $c->render(text => 'Home Page');
};

# パラメータ付きルート
get '/hello/:name' => sub ($c) {
  my $name = $c->param('name');
  $c->render(text => "Hello, $name!");
};

# ワイルドカード
get '/files/*filepath' => sub ($c) {
  my $path = $c->param('filepath');
  $c->render(text => "File path: $path");
};

# 正規表現
get '/article/:id' => [id => qr/\d+/] => sub ($c) {
  my $id = $c->param('id');
  $c->render(text => "Article ID: $id");
};

# POSTリクエスト
post '/submit' => sub ($c) {
  my $data = $c->param('data');
  $c->render(text => "Received: $data");
};

app->start;

プレースホルダーの種類

  • :name - 標準プレースホルダー(/以外の任意の文字列)
  • *filepath - ワイルドカード(/を含む任意の文字列)
  • [name => qr/.../] - 正規表現による制約

試してみましょう:

1
2
3
4
5
6
7
8
curl http://localhost:3000/hello/Perl
# => Hello, Perl!

curl http://localhost:3000/files/path/to/file.txt
# => File path: path/to/file.txt

curl http://localhost:3000/article/123
# => Article ID: 123

テンプレート - HTMLを返す

インラインテンプレートを使えば、HTMLレスポンスも簡単です:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;

get '/' => sub ($c) {
  $c->render(template => 'index');
};

get '/user/:name' => sub ($c) {
  my $name = $c->param('name');
  $c->render(template => 'user', name => $name);
};

app->start;

__DATA__

@@ index.html.ep
<!DOCTYPE html>
<html>
<head>
  <title>Mojolicious App</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 40px; }
    h1 { color: #8B4513; }
  </style>
</head>
<body>
  <h1>Welcome to Mojolicious!</h1>
  <p>Visit <a href="/user/YourName">/user/YourName</a></p>
</body>
</html>

@@ user.html.ep
<!DOCTYPE html>
<html>
<head>
  <title>User: <%= $name %></title>
</head>
<body>
  <h1>Hello, <%= $name %>!</h1>
  <p>Current time: <%= localtime %></p>
  
  % if ($name eq 'Perl') {
    <p>You have excellent taste in programming languages!</p>
  % }
  
  <a href="/">Back to home</a>
</body>
</html>

テンプレートの記法

  • <%= ... %>: Perlコードの評価結果を出力(HTMLエスケープあり)
  • <%== ... %>: HTMLエスケープなしで出力
  • % ...: 行頭の%でPerlコードを記述
  • %% ...: 実際の%を出力したい場合

JSON API - RESTful APIを作る

RESTful APIの作成もMojoliciousの得意分野です:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
use Mojo::JSON qw(decode_json encode_json);

# データストア(本番環境ではデータベースを使用)
my $todos = [
  {id => 1, title => 'Learn Mojolicious', done => 0},
  {id => 2, title => 'Build an app', done => 0},
];
my $next_id = 3;

# 全件取得
get '/api/todos' => sub ($c) {
  $c->render(json => $todos);
};

# 1件取得
get '/api/todos/:id' => sub ($c) {
  my $id = $c->param('id');
  my ($todo) = grep { $_->{id} == $id } @$todos;
  
  return $c->render(json => {error => 'Not found'}, status => 404)
    unless $todo;
  
  $c->render(json => $todo);
};

# 新規作成
post '/api/todos' => sub ($c) {
  my $json = $c->req->json;
  
  return $c->render(json => {error => 'Invalid data'}, status => 400)
    unless $json && $json->{title};
  
  my $todo = {
    id => $next_id++,
    title => $json->{title},
    done => $json->{done} // 0,
  };
  
  push @$todos, $todo;
  $c->render(json => $todo, status => 201);
};

# 更新
put '/api/todos/:id' => sub ($c) {
  my $id = $c->param('id');
  my $json = $c->req->json;
  my ($todo) = grep { $_->{id} == $id } @$todos;
  
  return $c->render(json => {error => 'Not found'}, status => 404)
    unless $todo;
  
  $todo->{title} = $json->{title} if defined $json->{title};
  $todo->{done} = $json->{done} if defined $json->{done};
  
  $c->render(json => $todo);
};

# 削除
del '/api/todos/:id' => sub ($c) {
  my $id = $c->param('id');
  my $count = @$todos;
  
  @$todos = grep { $_->{id} != $id } @$todos;
  
  return $c->render(json => {error => 'Not found'}, status => 404)
    if @$todos == $count;
  
  $c->render(json => {message => 'Deleted'});
};

app->start;

APIのテスト

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 全件取得
curl http://localhost:3000/api/todos

# 1件取得
curl http://localhost:3000/api/todos/1

# 新規作成
curl -X POST http://localhost:3000/api/todos \
  -H 'Content-Type: application/json' \
  -d '{"title":"Write article","done":false}'

# 更新
curl -X PUT http://localhost:3000/api/todos/1 \
  -H 'Content-Type: application/json' \
  -d '{"done":true}'

# 削除
curl -X DELETE http://localhost:3000/api/todos/1

WebSocket - リアルタイム通信

MojoliciousはWebSocketを標準でサポートしています。チャットアプリを作ってみましょう:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;

# 接続中のクライアントを保存
my $clients = {};

get '/' => sub ($c) {
  $c->render(template => 'chat');
};

websocket '/ws' => sub ($c) {
  # クライアントIDを生成
  my $id = sprintf '%s', $c->tx;
  $clients->{$id} = $c->tx;
  
  $c->app->log->info("Client $id connected");
  
  # メッセージ受信時の処理
  $c->on(message => sub ($c, $msg) {
    $c->app->log->info("Received: $msg");
    
    # 全クライアントにブロードキャスト
    for my $client (values %$clients) {
      $client->send($msg);
    }
  });
  
  # 切断時の処理
  $c->on(finish => sub ($c, $code, $reason) {
    delete $clients->{$id};
    $c->app->log->info("Client $id disconnected");
  });
};

app->start;

__DATA__

@@ chat.html.ep
<!DOCTYPE html>
<html>
<head>
  <title>WebSocket Chat</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 20px; }
    #messages { 
      border: 1px solid #ccc; 
      height: 300px; 
      overflow-y: auto; 
      padding: 10px; 
      margin-bottom: 10px;
    }
    #input { width: 300px; }
  </style>
</head>
<body>
  <h1>WebSocket Chat</h1>
  <div id="messages"></div>
  <input type="text" id="input" placeholder="Type a message...">
  <button id="send">Send</button>

  <script>
    const ws = new WebSocket('ws://localhost:3000/ws');
    const messages = document.getElementById('messages');
    const input = document.getElementById('input');
    const send = document.getElementById('send');

    ws.onmessage = function(event) {
      const p = document.createElement('p');
      p.textContent = event.data;
      messages.appendChild(p);
      messages.scrollTop = messages.scrollHeight;
    };

    function sendMessage() {
      if (input.value) {
        ws.send(input.value);
        input.value = '';
      }
    }

    send.onclick = sendMessage;
    input.onkeypress = function(e) {
      if (e.key === 'Enter') sendMessage();
    };
  </script>
</body>
</html>

複数のブラウザタブでhttp://localhost:3000を開いて、リアルタイムチャットを試してみてください!

テストの書き方

Mojoliciousにはテストフレームワークも組み込まれています。test.tというファイルを作成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use Test::More;
use Test::Mojo;

# テスト対象のアプリケーション
use Mojolicious::Lite -signatures;

get '/' => sub ($c) {
  $c->render(text => 'Hello Test!');
};

get '/json' => sub ($c) {
  $c->render(json => {message => 'Hello', status => 'ok'});
};

# テストの実行
my $t = Test::Mojo->new;

# GETリクエストのテスト
$t->get_ok('/')
  ->status_is(200)
  ->content_is('Hello Test!');

# JSONレスポンスのテスト
$t->get_ok('/json')
  ->status_is(200)
  ->json_is('/message' => 'Hello')
  ->json_is('/status' => 'ok');

# 存在しないパス
$t->get_ok('/notfound')
  ->status_is(404);

done_testing();

テストの実行:

1
perl test.t

より高度なテスト例

POSTリクエストやJSONペイロードのテスト:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use Test::More;
use Test::Mojo;

use Mojolicious::Lite -signatures;

post '/api/user' => sub ($c) {
  my $json = $c->req->json;
  return $c->render(json => {error => 'Name required'}, status => 400)
    unless $json->{name};
  
  $c->render(json => {
    id => 1,
    name => $json->{name},
    created => 1
  });
};

my $t = Test::Mojo->new;

# 正常系
$t->post_ok('/api/user' => json => {name => 'Taro'})
  ->status_is(200)
  ->json_is('/id' => 1)
  ->json_is('/name' => 'Taro')
  ->json_is('/created' => 1);

# 異常系
$t->post_ok('/api/user' => json => {})
  ->status_is(400)
  ->json_is('/error' => 'Name required');

done_testing();

実用的なアプリケーション例 - URLショートナー

これまで学んだことを活かして、実用的なURLショートナーを作ってみましょう:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
use Mojo::Util qw(b64_encode b64_decode);

# データストア(簡易版)
my $urls = {};
my $counter = 1000;

# ホームページ
get '/' => sub ($c) {
  $c->render(template => 'index');
};

# URL短縮API
post '/api/shorten' => sub ($c) {
  my $url = $c->param('url');
  
  return $c->render(json => {error => 'URL required'}, status => 400)
    unless $url;
  
  # 簡易的なID生成(本番環境ではより堅牢な方法を使用)
  my $id = sprintf '%x', $counter++;
  $urls->{$id} = $url;
  
  my $short_url = $c->url_for("/s/$id")->to_abs;
  
  $c->render(json => {
    original => $url,
    short => $short_url,
    id => $id
  });
};

# リダイレクト
get '/s/:id' => sub ($c) {
  my $id = $c->param('id');
  my $url = $urls->{$id};
  
  return $c->render(text => 'Not found', status => 404)
    unless $url;
  
  $c->redirect_to($url);
};

# 統計情報
get '/api/stats' => sub ($c) {
  $c->render(json => {
    total_urls => scalar keys %$urls,
    urls => [map { {id => $_, url => $urls->{$_}} } keys %$urls]
  });
};

app->start;

__DATA__

@@ index.html.ep
<!DOCTYPE html>
<html>
<head>
  <title>URL Shortener</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 600px;
      margin: 50px auto;
      padding: 20px;
    }
    input[type="text"] {
      width: 100%;
      padding: 10px;
      margin: 10px 0;
      box-sizing: border-box;
    }
    button {
      padding: 10px 20px;
      background: #8B4513;
      color: white;
      border: none;
      cursor: pointer;
    }
    button:hover { background: #654321; }
    #result {
      margin-top: 20px;
      padding: 15px;
      background: #f0f0f0;
      display: none;
    }
  </style>
</head>
<body>
  <h1>🔗 URL Shortener</h1>
  <p>Enter a long URL to shorten it:</p>
  
  <input type="text" id="url" placeholder="https://example.com/very/long/url">
  <button onclick="shortenUrl()">Shorten URL</button>
  
  <div id="result"></div>

  <script>
    async function shortenUrl() {
      const url = document.getElementById('url').value;
      if (!url) {
        alert('Please enter a URL');
        return;
      }

      const response = await fetch('/api/shorten', {
        method: 'POST',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        body: 'url=' + encodeURIComponent(url)
      });

      const data = await response.json();
      
      if (data.error) {
        alert(data.error);
        return;
      }

      const result = document.getElementById('result');
      result.innerHTML = `
        <h3>✅ URL Shortened!</h3>
        <p><strong>Original:</strong> ${data.original}</p>
        <p><strong>Short URL:</strong> <a href="${data.short}" target="_blank">${data.short}</a></p>
      `;
      result.style.display = 'block';
    }
  </script>
</body>
</html>

このアプリケーションは以下の機能を持っています:

  1. URL短縮: 長いURLを短縮URLに変換
  2. リダイレクト: 短縮URLにアクセスすると元のURLにリダイレクト
  3. 統計情報: /api/statsで全URLの一覧を取得
  4. レスポンシブUI: シンプルで使いやすいインターフェース

プラグインの活用

Mojoliciousには豊富なプラグインエコシステムがあります。よく使われるプラグインをいくつか紹介します:

Mojolicious::Plugin::OpenAPI

OpenAPI(Swagger)仕様に基づいたAPI開発:

1
2
3
4
5
use Mojolicious::Lite -signatures;

plugin OpenAPI => {url => 'api.yaml'};

app->start;

Mojolicious::Plugin::Authentication

ユーザー認証機能の追加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use Mojolicious::Lite -signatures;

plugin 'Authentication' => {
  load_user => sub ($app, $uid) {
    return {username => 'user', uid => $uid};
  },
  validate_user => sub ($c, $username, $password) {
    return $username eq 'admin' && $password eq 'secret' ? 1 : undef;
  },
};

get '/protected' => sub ($c) {
  return $c->redirect_to('/login') unless $c->is_user_authenticated;
  $c->render(text => 'Secret area');
};

app->start;

デプロイ方法

開発環境

開発時は組み込みサーバーで十分です:

1
2
3
4
5
# リロード機能付き(ファイル変更を自動検知)
morbo myapp.pl

# 本番モード
perl myapp.pl daemon -l http://*:8080

本番環境

本番環境では、以下のような選択肢があります:

1. Hypnotoad(推奨)

Mojolicious組み込みの高性能プリフォークサーバー:

1
hypnotoad myapp.pl

ゼロダウンタイムデプロイが可能:

1
2
# 新バージョンをデプロイ(自動的に古いプロセスを置き換え)
hypnotoad myapp.pl

2. リバースプロキシ構成

Nginx + Hypnotoadの組み合わせ:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
upstream myapp {
  server 127.0.0.1:8080;
}

server {
  listen 80;
  server_name example.com;

  location / {
    proxy_pass http://myapp;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

3. Plack/PSGI

PSGI互換サーバーでも動作します:

1
plackup myapp.psgi

パフォーマンスと非同期処理

Mojoliciousの真価は非同期処理にあります。複数のHTTPリクエストを並列に処理する例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use Mojolicious::Lite -signatures;

get '/parallel' => sub ($c) {
  my $delay = Mojo::IOLoop->delay(sub ($delay, @results) {
    my ($user, $posts, $comments) = @results;
    $c->render(json => {
      user => $user,
      posts => $posts,
      comments => $comments
    });
  });

  # 3つのAPIを並列に呼び出し
  my $ua = $c->ua;
  $ua->get('https://api.example.com/user/1' => $delay->begin);
  $ua->get('https://api.example.com/posts' => $delay->begin);
  $ua->get('https://api.example.com/comments' => $delay->begin);
};

app->start;

設定管理

環境ごとに設定を変える方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use Mojolicious::Lite -signatures;

# 設定ファイルの読み込み
plugin Config => {file => 'myapp.conf'};

get '/' => sub ($c) {
  my $db_host = $c->config('database')->{host};
  $c->render(text => "DB Host: $db_host");
};

app->start;

myapp.confの例:

1
2
3
4
5
6
7
8
{
  database => {
    host => 'localhost',
    port => 5432,
    name => 'myapp'
  },
  secrets => ['my-secret-key']
}

デバッグ技法

Mojoliciousには強力なデバッグ機能があります:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ログレベルの設定
app->log->level('debug');

# ログ出力
app->log->debug('Debug message');
app->log->info('Info message');
app->log->warn('Warning message');
app->log->error('Error message');

# リクエスト/レスポンスのダンプ
get '/debug' => sub ($c) {
  $c->app->log->debug($c->req->headers->to_string);
  $c->app->log->debug($c->req->body);
  $c->render(text => 'Check logs');
};

開発時はMOJO_LOG_LEVEL環境変数でログレベルを制御:

1
MOJO_LOG_LEVEL=debug perl myapp.pl daemon

まとめ

Mojoliciousは「楽しく」「モダン」なPerlのWebフレームワークです。この記事で紹介した内容をまとめます:

学んだこと

  1. 基本: Mojolicious::Liteでの最小構成から開始
  2. ルーティング: 柔軟なURLパターンマッチング
  3. テンプレート: EPテンプレートエンジンでHTML生成
  4. JSON API: RESTful APIの実装
  5. WebSocket: リアルタイム通信
  6. テスト: Test::Mojoによる自動テスト
  7. 実用例: URLショートナーの実装
  8. デプロイ: 本番環境への展開方法

Mojoliciousの魅力

  • 学習曲線が緩やか: シンプルな例から始めて、段階的に機能を追加できる
  • ドキュメントが充実: perldoc Mojolicious::Guidesで詳細なガイドが読める
  • コミュニティが活発: IRCチャンネル、メーリングリスト、GitHub
  • 実戦投入できる: 小規模から大規模まで対応可能
  • 楽しい: 開発体験が素晴らしく、コードを書くのが楽しくなる

次のステップ

Mojoliciousをさらに深く学ぶには:

  1. 公式ガイド: perldoc Mojolicious::Guides::Tutorial
  2. クックブック: perldoc Mojolicious::Guides::Cookbook
  3. プラグイン: CPAN で Mojolicious::Plugin::* を検索
  4. 実例: GitHubで実際のプロジェクトを見てみる
  5. コミュニティ: #mojo on irc.libera.chat に参加

参考リンク

Mojoliciousで、楽しいPerl Webアプリケーション開発を始めましょう!

バージョン情報

この記事のコード例は以下の環境で動作確認しています:

  • Perl: 5.30以上(サブルーチンシグネチャ対応版)
  • Mojolicious: 9.0以上

古いPerlバージョンを使用している場合は、サブルーチンシグネチャ(-signatures)の部分を従来のmy ($c) = @_;形式に書き換えてください。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。