🦕

Denoでサーバーを建てる方法 2021年11月版

2021/11/12に公開2

https://twitter.com/KawarimiDoll/status/1458583438999449600

Denoでサーバーを建てる方法が以前とは変化しています。 本記事では、Deno 1.16 / std 0.114環境での手法を解説します。

https://zenn.dev/kawarimidoll/articles/0d384d53953525

記事執筆時点での結論:std/httpのserve

まずは結論から。 現在、推奨されているのはstd/httpのserveメソッドを使う方法です。

https://deno.land/std@0.114.0/http

0.114.0時点でのstd/httpのREADMEには説明が追加されていないのですが、JSDocや、Deno 1.16のリリース記事ではこのserveメソッドが使われており、最近まで推奨されていたlistenAndServeメソッドは非推奨となっています。

サンプルを作ってみました。
serveの第一引数はHandler: (request: Request, connInfo: ConnInfo) => Response | Promise<Response>、第二引数はServeInit: { addr?: string; signal?: AbortSignal; }です。

server.ts
import { serve } from "https://deno.land/std@0.114.0/http/server.ts";

const addr = ":8080";
console.log(`HTTP server listening on http://localhost${addr}`)

serve((request: Request) => {
  const { pathname } = new URL(request.url);

  return new Response(`Hello world from ${pathname}`);
}, { addr });

addrが数値でなく文字列なのはhost:portの形式で指定できるためで、この例のように:を最初に持ってくることでport部だけ設定することができます。デフォルト値は"0.0.0.0:8000"です。

最新のAPIドキュメントは https://doc.deno.land/ で見られます。
https://doc.deno.land/https/deno.land%2Fstd%400.114.0%2Fhttp%2Fserver.ts

試しにこの形式で作ったサーバーファイルをDeno Deployに上げてみましたが、問題なく動作しました。
https://cloudy-owl-71.deno.dev/
https://github.com/kawarimidoll/deno-dev-template/commit/20bd0e91bf6474c3c67357fd73fc764d4b83a719

Denoのサーバーの歴史

最初に添付したツイートの通り、これまでDenoでサーバーを建てる方法は複数提案されてきました。
以下ではそれらをざっくりまとめたいと思います。

std/httpのserve 旧版

当初使用されていたものです。
本記事で紹介したstd 0.114.0のserveと同名ですが別物で、現在はserver_legacyに入っているものです。

server_legacy.ts
import { serve } from "https://deno.land/std/http/server_legacy.ts";

const body = "Hello World\n";
const server = serve({ port: 8000 });
for await (const req of server) {
  req.respond({ body });
}

当初はserver_legacy.tsではなくserver.tsから読み込まれていたため、バージョン指定がされていないコードでは現行のserveと区別が付きません。
しばらく前に書かれたDenoの紹介記事はこれを使っていることが多く、「記事のサンプルとインポート元のファイル名もメソッド名も同じなのに引数や使い方がぜんぜん違って動かない…」という非常に厄介な状態になっています。

これが禍根になりそうだったので、今回の記事を書くに至りました。

addEventListener

Deno Deploy Beta 1時代に使用されていた手法です。

beta1_server.ts
addEventListener("fetch", (event) => {
  event.respondWith(new Response("Hello world"));
});

https://deno.com/blog/deploy-beta1/

この頃はDeno Deployと通常のCLIのDenoではサーバーの書き方が全く異なるものでした。

Deno Native HTTP

前述の旧std/httpのサーバーのパフォーマンスが悪かったことにより、Deno自体にHTTPサーバー機能が組み込まれるに至ったものです。
Deno 1.9で導入され、1.13で安定化しました。

https://zenn.dev/magurotuna/articles/deno-release-note-1-13-0#1.-ネイティブ-http-サーバー実装の安定化

native_server.ts
for await (const conn of Deno.listen({ port: 8080 })) {
  (async () => {
    for await (const { respondWith } of Deno.serveHttp(conn)) {
      respondWith(new Response("Hello World"));
    }
  })();
}

Deno 1.13リリース時点で https://deno.land のサーバーにも使用されており、実績を積んでいました。

そして、この記法はDeno Deployでも使えるようになり、CLI側と同一の記述でサーバーを書けるようになりました。

https://zenn.dev/kawarimidoll/articles/38d5c3d82e6882#cliと同じhttpサーバーが追加された

Deno Deploy Beta 2のリリース記事でも、この書き方が使われています(前述のaddEventListenerも混在しています)。

https://deno.com/blog/deploy-beta2/

この1.13時点では、Denoのサーバーはネイティブのものを利用するよう統一され、std/httpは廃止される見通しとなっていました。

std/httpのlistenAndServe

DenoのネイティブHTTPの更新により削除されると思われていたstd/httpですが、Deno 1.14と並行してリリースされたstd 0.107にて、ネイティブHTTPを使用するlistenAndServeが追加され、削除どころか推奨手法に返り咲きました。

https://zenn.dev/magurotuna/articles/deno-release-note-1-14-0#12.-std%2Fhttp-モジュールが高速化

ネイティブHTTPはlistenした結果をfor awaitで囲み、その中で実行するハンドラにもfor awaitが入っている…という少し複雑な構造でしたが、listenAndServeがそのあたりをラップしたことで、高いパフォーマンスを得つつ、より簡単にサーバーを記述できるようになりました。

beta3_server.ts
import { listenAndServe } from "https://deno.land/std@0.107.0/http/server.ts";

listenAndServe(":8000", () => new Response("Hello World\n"));

Deno 1.16の半月前、Deno Deploy Beta 3のリリース記事では、こちらの記法が使われています。

https://deno.com/blog/deploy-beta3/

std/httpのserve 現行版

そして時は現代。
最新の手法が、本記事で紹介しているstd/httpの新serveメソッドです。

Deno 1.16のリリース記事では、fetchメソッドの更新について紹介する部分で、この記法がしれっと使われています。

https://deno.com/blog/v1.16

経緯

このメソッドは以下のPRで提案されています。

https://github.com/denoland/deno_std/pull/1478

import { serve } from "https://deno.land/std/http/server.ts";
serve((req) => new Response("Hello world"));

🐯 「Deno Deploy用のサンプルをきれいにしたい ネーミングには自身がないけど」

で、そこで議論があり、以下のPRにて実装が取り込まれ、同時にlistenAndServe@deprecatedとなりました。

https://github.com/denoland/deno_std/pull/1506

おわりに

以上、Denoのサーバーの書き方と、その歴史でした。

stdも未だ0.xですし、Deno DeployもBetaなので、推奨手法はガンガン変わっていっています。

このため、途中にも書きましたが、「お手本と記述が同じなのに動かない」ことが起こりえます。
公式の最新版以外の資料を参考にする場合は、作成日・更新日を確認のうえ、注意して利用しましょう。

まあ、本記事執筆時点ではDeno Deployトップページに掲載されているサンプルスクリプトも更新されていない状態なんですけどね…。
つまりこの記事は公式より先を走っている

https://deno.com/deploy/

Discussion

jwnrjwnr

まさに std/httpのserve が新しいのか古いのか良くわからない状態になっていました。
いつもdeno関連の最新情報がまとまっていて助かります。

あとは各ライブラリの対応が気になるところです。oak drash等の有名どころは頻繁に更新されているので、きっちり追いついていると信じたい…(ソースを読み込む気力は無い)

kawarimidollkawarimidoll

コメントありがとうございます。
私も読み込むまでは行っていませんが、各ライブラリの現時点の印象としてはこんな感じですね:

この辺の確認には、とりあえずdeps.tsを見るのがオススメです。