Prologue

~実務に寄せやすい、Webフレームワーク~
NimでWebアプリやWeb API を作るためのフレームワーク

カテゴリー:Web
インストール方法:nimble install prologue
対応Nimバージョン:2.0.0以降
公式レポジトリ:planety/prologue




Prologueとは?


概要

Prologue は、Nim で HTTP サーバーを組み立てるときに必要になりやすい要素を、最初からある程度まとまった形で提供するフレームワークである。
単にリクエストを受け取って文字列を返すだけではなく、設定管理、ルーティング、ミドルウェア、Cookie、静的ファイル配信、エラーハンドラ、セッション、バリデーション、WebSocket、OpenAPI ドキュメント公開の入口まで揃っているため、
学習用の小さなアプリから、ある程度まとまった業務用ツールや API サーバーまで育てやすい。

Prologue の中心は Context である。各リクエストが入るたびに Context が初期化され、その中に requestresponsesession などが入る。
ハンドラは proc hello(ctx: Context) {.async.} のような形で書き、ctx.response でレスポンスを返す。
この構造は初心者にとっても理解しやすく、どこに入力があり、どこに出力を書けばよいかが見えやすい。

さらに、newApp() でアプリケーション本体を作り、addRouteget などでルートを登録し、最後に run() で起動する流れがはっきりしている。
小さく始めたあとで、設定ファイルの導入、ルートのグループ化、ミドルウェア追加、セッション導入といった拡張を段階的に足せる点が、Prologue の扱いやすさにつながっている。

インストール方法

まず前提として、Nim のパッケージ導入には nimble を使う。
Nimble は Nim の標準パッケージマネージャで、通常は Nim と一緒に入る。したがって、Prologue を使う前に Nim 本体が入っていれば、追加の準備はかなり少ない。

Prologue のインストールは、コマンドプロンプトで nimble install prologue と打つだけで完了する。
これで Prologue とその依存関係が自動的にダウンロードされ、ビルドされる。
インストール後は、Nim のコード内で import prologue と書くだけで、Prologue の機能を使えるようになる。

CLI ツールの logue を使う場合は、~/.nimble/bin が環境変数 PATH に入っている必要がある。
ここが通っていないと、インストール自体は終わっていても logue コマンドが見つからない。


Chronos 系バックエンドを試したい場合は、公式の package 定義にある feature を使って次のように導入できる。
通常の学習段階では必須ではないが、将来的にバックエンド差し替えを意識するなら知っておいてよい。

                    
nimble install prologue[kairos]
                    
                

これで、Prologue のバックエンドがデフォルトの asyncdispatch から kairos に切り替わる。
なお、バックエンドを切り替えるときは、プロジェクトの nim.cfg に prologue:backend = kairos と書いておくと、ビルドのたびに正しいバックエンドが選ばれるようになる。


導入後、最小構成のアプリは import prologuenewApp()addRoute()run() で起動できる。

プロジェクトのひな形を一気に作りたいなら、logue init プロジェクト名 が使える。

                    
logue init myapp
                    
                

JSON 設定ファイル前提にしたいなら --useConfig もある。

                    
logue init myapp --useConfig
                    
                

特徴

Prologue の強みは、単なる「ルーティングだけの薄いラッパー」ではなく、Web アプリ開発で後から欲しくなりやすい部品を最初から同じ流儀で扱えるところにある。
公式 README でも、設定、Context、パラメータ・クエリ、フォーム、静的ファイル、ミドルウェア、Cookie、起動・終了イベント、URL 生成、エラーハンドラに加えて、I18n、Basic Authentication、最小限の OpenAPI、WebSocket、CORS、Validation、Session、Cache、CSRF、Clickjacking Protection などが列挙されている。
つまり、学習を始めた直後は簡単な Web サーバーとして使え、慣れてきたらそのまま本格機能へ寄せやすい。

ルーティング機能が豊富なのも大きい。
単純な固定パスだけでなく、/hello/{name} のようなパラメータ付きルート、*$ を使ったワイルドカードや greedy マッチ、正規表現ルート、pattern() を使ったパターン定義、newGroup() を使ったグループルーティングまで扱える。
API を /api/v1/... のように整理したいときにも向いている。

また、Context を中心に設計されているため、クエリ文字列、POST データ、フォームデータ、アップロードファイル、セッション、レスポンス操作が同じ感覚で触れる。
getQueryParamsgetPathParamsgetFormParamsgetUploadFile などの名前がかなり素直で、初心者でも「どこから何を取るのか」を追いやすい。

一方で、テンプレートエンジンはフレームワーク本体に内蔵していない。これは弱点にも見えるが、見方を変えれば役割分担が明確ということでもある。
公式ドキュメントでは Karax を推奨しており、サーバーサイドレンダリングにも向くと案内している。
そのため、Prologue は「バックエンドの骨組みはしっかり持ちたいが、ビュー層は自分で選びたい」という人に合う。

使いどころ

まず向いているのは、学習用の小さな Web アプリである。
Hello World の段階では newApp()addRoute()run() だけで始められるので、Nim で Web サーバーがどう動くかを掴みやすい。
それでいて、あとからフォーム処理や静的ファイル配信を足せるため、単発の練習で終わりにくい。

次に向いているのは、Web API や管理画面のような「画面はそこまで派手でなくてよいが、ルーティングや入力処理はきちんとしたい」ケースである。
Prologue にはルートグループ、クエリ・パス・フォーム取得、バリデーション、最小限の OpenAPI ドキュメント公開があるので、JSON API や社内ツールの基盤としてまとめやすい。

ログイン状態を持つ小規模アプリにも向く。
セッション機能があり、sessionMiddleware を通せばハンドラから ctx.session を扱える。
簡易な会員機能、管理者ログイン、設定保存のような処理を実装しやすい。
ただし、公式ドキュメントでも signed cookie ベースのセッションは機密情報保存に安全ではないと明記されているので、そこは設計判断が必要である。

ファイルアップロードや簡単なリアルタイム処理が必要なときにも候補になる。
getUploadFilesave でアップロードを受け取り、WebSocket サポートで簡単な echo サーバーも書ける。
チャット、通知、進捗表示のような機能を Nim でまとめたい場合には相性がよい。

使い方

最初は最小構成を一度動かすのがよい。
公式ドキュメントの基本形は、Context を受け取る非同期ハンドラを書き、resp で本文を返し、newApp()addRoute() で登録して run() する形である。
これが Prologue の土台になる。

                    
# app.nim
import prologue

proc hello(ctx: Context) {.async.} =
  resp "<h1>Hello, Prologue!</h1>"

var app = newApp()
app.addRoute("/", hello)
app.run()
                    
                

これを nim r app.nim で動かすと、http://localhost:8080/ にアクセスしたときに Hello, Prologue! と表示される。
まずはこの流れを掴むことが重要である。

ここから一歩進めて、クエリ文字列、パスパラメータ、フォーム、セッション、静的ファイルをまとめて触るサンプルを書くと、Prologue の全体像がかなり見える。
公式ドキュメントでは getQueryParamsgetPathParamsgetFormParamssessionMiddlewarestaticFileMiddlewareredirect などがそれぞれ案内されているので、それを初心者向けに一つへまとめると次のようになる。

                    
import prologue
import prologue/middlewares/staticfile
import prologue/middlewares/sessions/signedcookiesession

proc index(ctx: Context) {.async.} =
  let name = ctx.getQueryParams("name", "Guest")
  resp "<h1>Hello, " & name & "</h1><p><a href='/login'>login</a></p>"

proc userPage(ctx: Context) {.async.} =
  let id = ctx.getPathParams("id", 0)
  resp "<h1>User ID: " & $id & "</h1>"

proc login(ctx: Context) {.async.} =
  if ctx.request.reqMethod == HttpGet:
    resp """
    <h1>Login</h1>
    <form method="post" action="/login">
      <input type="text" name="name" placeholder="your name">
      <button type="submit">Login</button>
    </form>
    """
  else:
    let name = ctx.getFormParams("name", "Anonymous")
    ctx.session["name"] = name
    resp redirect("/mypage")

proc mypage(ctx: Context) {.async.} =
  let name = ctx.session.getOrDefault("name", "未ログイン")
  resp "<h1>My Page</h1><p>name = " & name & "</p>"

let settings = newSettings(
  appName = "PrologueSample",
  port = Port(8080),
  debug = true
)

var app = newApp(settings = settings)

app.use(staticFileMiddleware("public"))
app.use(sessionMiddleware(settings))

app.get("/", index, name = "index")
app.get("/users/{id}", userPage)
app.all("/login", login)
app.get("/mypage", mypage)

app.run()
                    
                

このサンプルは、ひとことで言えば「簡単なWebアプリの最小実用版」である。
ただ文字列を返すだけではなく、次の4つをまとめて扱っている。

各 proc は「ページ1枚分の処理」だと思えばよい

index の役割

index は、クエリ文字列から name を取ってきて、Hello, name と返す処理である。
つまり、URL に ?name=Alice とつけてアクセスすると、Hello, Alice と表示される。
クエリ文字列がないときは Guest になるようにしている。

userPage の役割

userPage は、/users/10 のような URL から id を取ってきて、User ID: 10 と返す処理である。
つまり、URL に /users/10 とアクセスすると、User ID: 10 と表示される。
パスパラメータがないときは 0 になるようにしている。

login の役割

login は、GET と POST の両方を受け取る処理である。
GET のときはログインフォームを返し、POST のときはフォームから name を取ってきてセッションに保存し、/mypage にリダイレクトする。
つまり、/login にアクセスしてフォームに名前を入れて送信すると、その名前がセッションに保存されて /mypage に飛ぶ。

mypage の役割

mypage は、セッションから name を取ってきて、My Page と一緒に表示する処理である。
つまり、/mypage にアクセスすると、My Page と表示され、その下に name = (保存された名前) と出る。
もしセッションに名前が保存されていないときは、name = 未ログイン と表示される。

settings の役割

settings は、アプリ全体の設定をまとめるためのものである。
ここでは、アプリ名、ポート番号、デバッグモードのオンオフを設定している。
これを newApp() に渡すことで、アプリ全体でこの設定が使えるようになる。

app を作る部分

ここでは、まず newApp() でアプリケーション本体を作り、use() でミドルウェアを追加し、get()all() でルートを登録している。
最後に run() でサーバーを起動する。
つまり、ここでは「どんな機能を使うか」「どんなURLがあるか」をまとめている。

ミドルウェアを追加する部分

Prologue では、追加機能をミドルウェアとして app に組み込むことが多い。
ここでは、静的ファイル配信とセッション管理のミドルウェアを追加している。

staticFileMiddleware("public") は、public ディレクトリにあるファイルを静的に配信するミドルウェアである。
これを追加すると、/style.css のような URL で public/style.css をアクセスできるようになる。
つまり、静的ファイルを置く場所と URL の対応を作る役割がある。

sessionMiddleware(settings) は、セッション管理のミドルウェアである。
これを追加すると、ハンドラの中で ctx.session を使ってセッションデータを読み書きできるようになる。
つまり、ユーザーごとの状態を保存するための機能を提供する役割がある。

ルート登録

ここでは、どの URL にアクセスされたら、どの proc を呼ぶかを登録している。

app.get("/", index, name = "index") は、/ にアクセスされたときに index を呼ぶルートを登録している。
つまり、トップページにアクセスされたときの処理を定義している。

app.get("/users/{id}", userPage) は、/users/10 のような URL にアクセスされたときに userPage を呼ぶルートを登録している。
つまり、ユーザーページにアクセスされたときの処理を定義している。

app.all("/login", login) は、all を使っているので、GET でも POST でも同じ login proc が呼ばれる。
そして /login にアクセスされたときに login を呼ぶルートを登録している。
つまり、ログインページにアクセスされたときの処理を定義している。
この書き方の利点は、/login に関する表示と送信処理を1か所にまとめられることだ。
初心者には少し見やすい。
ただし、もっと大きなアプリでは GET 用 proc と POST 用 proc を分けることも多い。

app.get("/mypage", mypage) は、/mypage にアクセスされたときに mypage を呼ぶルートを登録している。
つまり、マイページにアクセスされたときの処理を定義している。

実行するとどう動くか

まず http://localhost:8080/ にアクセスすると、index が呼ばれる。
ここではクエリ文字列がなければ Guest が表示される。
http://localhost:8080/?name=Alice なら Hello, Alice になる。

次に、ページ内の login リンクを押して /login へ行く。
これは GET なので、login proc の前半が動き、フォーム画面が表示される。

そこで名前を入力して送信すると、今度は POST /login になる。
すると login proc の後半が動き、ctx.getFormParams("name", "Anonymous") で入力値を取り出し、それを ctx.session["name"] に保存する。
そのあと /mypage へリダイレクトされる。
つまり、セッションに名前が保存された状態で /mypage にアクセスすることになる。

/mypage では mypage proc が動き、セッションから "name" を読んで表示する。 そのため、さっき入力した名前が見える。

この一連の流れで、Prologue の基本である「ルート」「フォーム」「セッション」「レスポンス」が全部つながる。


静的ファイルを使いたい場合は、たとえば public/style.css を置いて、HTML 側で /style.css のように絶対パスで読む。
公式ドキュメントでは静的ファイルはミドルウェアで配信し、パスはバイナリ位置からの相対になると説明されている。
開発中はこれで十分だが、本番ではリバースプロキシ側に任せる設計が勧められている。

設定をコード直書きではなく外へ出したいなら、newSettings(...) に直接値を書く方法のほか、.env を読む方法や JSON 設定ファイルを使う方法がある。
大きくなる予定のアプリなら、早めに設定ファイル化したほうが整理しやすい。
newAppQueryEnv() を使うと、PROLOGUE 環境変数に応じて .config/config.json や .config/config.production.json のようなファイルを自動で読み分けられる。

もしルーティングが増えてきたら、newGroup() でグループ化するとよい。
/api/v1 のような共通接頭辞を持つ API をまとめられるため、管理しやすくなる。
URL を名前から逆引きしたいときは urlFor() も使える。
画面遷移やリンク生成が増えてきた段階で特に有効である。

HTML をもっと見通しよく書きたい場合、Prologue 本体にテンプレートエンジンはない。
その代わり、公式ドキュメントでは Karax を推奨している。
最初は文字列で十分だが、ページ数が増えたら Karax で部品化すると保守しやすくなる。

注意点

このコードで詰まりやすいところも補足しておく。

最初に気をつけるべきなのは、logue コマンドが見つからない問題である。
これは Prologue 自体の不具合というより、~/.nimble/bin が PATH に入っていないことが原因になりやすい。
インストール後に logue init ... が動かないなら、まずそこを確認する。

設定ファイルを使う場合は、secretKey が空だと例外になる。
公式の設定ドキュメントでも secretKey は必須で、空にしてはいけないとされている。
Core API には EmptySecretKeyError も定義されている。
したがって、JSON 設定や .env を使うなら、開発中でも適当な固定値を必ず入れておくべきである。

フォーム処理では、getPostParamsOptiongetFormParamsOption を混同しやすい。
公式ドキュメントでは getPostParamsOptionapplication/x-www-form-urlencoded だけを対象にし、getFormParamsform-urlencodedmultipart/form-data の両方を扱えると説明されている。
つまり、ファイルアップロードを含むフォームなのに getPostParams 系を使うと、期待通りに取れないことがある。
アップロードが絡むなら getFormParamsgetUploadFile を使うほうが安全である。

ルーティングでは RouteError と重複ルートの問題に注意が必要である。
公式ドキュメントでは greedy 記号 $ は URL の末尾でしか使えず、途中に置くと RouteError になる。
また Core API には DuplicatedRouteError も定義されている。
ルートが増えてきたら、似たパスを何本も無造作に生やさず、グループ化や命名を早めに行うほうがよい。

静的ファイルは「動く環境」と「配置場所」の食い違いで詰まりやすい。
公式ミドルウェア解説では、静的ファイル用のパスはバイナリ位置からの相対で解釈されるとされている。
つまり、ローカルでは見えるのに、実行ファイルの置き場所が変わった本番環境では 404 になることがある。
また、HTML 内の JS や CSS はフルパスを使うよう FAQ でも案内されている。
見つからないときは、まず配置パスと参照パスを疑うべきである。

セッションも注意が必要である。
公式セッション解説では、signed cookie ベースのセッションは安全ではなく、テスト用途以外で機密情報を保存してはいけないと明記されている。
ユーザー ID 程度の軽い識別情報ならともかく、認証トークンや重要データをそのまま詰める設計は避けるべきである。

本番運用では debug = true のままにしないほうがよい。
QuickStart と FAQ の両方で、debug はログや例外表示に役立つ一方、遅くなり、リリースやベンチマーク時には false にすべきと案内されている。
特に 500 系エラー時、debug 有効だと例外情報がブラウザへ見えることがあるため、公開環境では切るのが基本である。

以上が Prologue を使うときに初心者が詰まりやすいポイントである。
これらを知っておくと、学習の途中で「なぜ動かないのか?」と悩む時間が減るはずである。

型・関数・API

ここでは、Prologue の主要な型や関数、API をまとめて紹介する。
公式ドキュメントの API リファレンスを初心者向けに整理したものである。

初心者が最初に覚えるべき主要 API は、Context を中心に、その周辺の RequestResponseSettingsSession、ルーティング系、起動系である。
以下は公式 Core API Index と各解説ページをもとに、最初の学習で優先度が高いものへ絞った一覧である。

主要な型

主要な関数・マクロ

上の一覧のうち、serveDocs() は OpenAPI 解説ページで紹介されている機能であり、Core API Index の主要項目と合わせて覚えると理解しやすい。
特に newAppnewSettingsaddRouteuserunrespgetPathParamsgetQueryParamsgetFormParamsgetUploadFile あたりが最初の重要ポイントになる。

列挙型

Prologue には、設定やルーティング、フラッシュメッセージなどの区別に使う列挙型もいくつか定義されている。
これらは、コードの中で特定の種類を表すために使われる。

ConfigFileExt には Core API Index で Json、Toml、Yaml が確認できる。
PatternMatchingType には ptrnTextptrnParamptrnWildcard があり、FlashLevel には InfoWarningErrorFault がある。
列挙型まで読み始めると、Prologue が見た目よりかなりきちんと整理されたフレームワークだとわかる。

例外・エラー系で知っておくとよいもの

これらは最初から全部覚える必要はないが、設定が読めない、ルートが競合する、パターンがおかしい、といった場面で名前を見ても慌てにくくなる。

参考文献

以下は本記事を執筆するにあたり参考にした文献である。

トップへ戻る


お問い合わせ