
Deno(ディーノ)を使ってみよう!【Node.jsの時代は終わる?】
2021.12.20
目次
Deno(ディーノ)とは
Deno(ディーノ)は、Node.js の開発者である Ryan Dahl によって作られた、新しいJavaScript/TypeScriptランタイムです。
Dahl 氏が、Node.js の欠点を認め、反省点を踏まえて作られたのが Deno です。
v1.0.0 が、2020年5月13日にリリースされたので、Denoで簡単なコードを実行し、使い勝手を確認してみましょう。
Denoの特徴
まずは公式サイトを確認してみましょう。
【Deno 公式サイト】
https://deno.land
Deno のアピールポイントとして、次のような特徴が書かれています。
- デフォルトでセキュアであり、ファイル・ネットワーク・環境変数へのアクセスには明示的な許可が必要
- 標準で TypeScript をサポート
- 単一の実行可能ファイルのみを発行可能
- 依存関係インスペクタ(deno info)やコードフォーマッタ(deno fmt)などの組み込みユーティリティを提供
- Deno での動作が保証されている標準モジュールを提供
ひとまず「そういうもの」と、心の片隅に置いて使ってみましょう。
使っていくうちに、どのようなものか実感できると思います!
Deno をインストール
さっそく Deno をインストールしましょう!
Linux 環境を例に説明します。
公式サイトのトップページにあるとおり、「curl」コマンドでインストールします。
HTTP 通信を使用するため、インターネットに接続している必要があります。
コマンドを実行
ターミナルで次のコマンドを実行しましょう。
1 | $ curl -fsSL https://deno.land/x/install/install.sh | sh |
インストール時のメッセージ
インストールに成功すると、次のようなメッセージが出力されます。
1 2 3 4 5 6 7 8 | ######################################################### 100.0% Archive: /app/.deno/bin/deno.zip inflating: deno Deno was installed successfully to /app/.deno/bin/deno Manually add the directory to your $HOME/.bash_profile (or similar) export DENO_INSTALL="/app/.deno" export PATH="$DENO_INSTALL/bin:$PATH" Run '/app/.deno/bin/deno --help' to get started |
パスを実行
親切にパスを通す方法が書いてありますので、そのとおりに実行しましょう。
1 2 | $ export DENO_INSTALL="/app/.deno" $ export PATH="$DENO_INSTALL/bin:$PATH" |
Deno を実行できるか確認
Deno を実行できるか確認してみましょう。
次のコマンドを実行します。
1 | $ deno -V |
Deno のバージョンが表示されたら環境作りは成功です。
次は、簡単なスクリプトを実行してみましょう。
Hello World
実際に、簡単なコードを動かしてみます。
hello.ts を作成
次の内容のファイル「hello.ts」を作りましょう。
1 | console.log('Hello World!'); |
文字列を標準出力するだけのスクリプトです。
Node.js と同様に console.log で標準出力が可能です。
コマンドで実行
deno run コマンドによって実行します。
1 | $ deno run -q hello.ts |
実行結果
実行結果を確認しましょう。
次のような結果が出力されれば成功です。
1 2 | Compile file:///app/hello.ts Hello World! |
初回のみコンパイルを実行し、1行目に "Compile {ファイル名}" といったメッセージを表示します。
2回目以降の実行では、2行目の "Hello World!" のみ出力します。
次のように、-q オプションを付けることにより、コンパイルのメッセージを表示させないことも可能です。
1 | $ deno run -q hello.ts |
コマンドライン引数の取得
実行時に指定したコマンドライン引数を、受け取ってみましょう。
コマンドライン引数を取得するには、配列 Deno.args を使います。
argsTest.ts を作る
次の内容のファイル「argsTest.ts」を作りましょう。
1 2 | const message: string = 'arg[0]:' + Deno.args[0]; console.log(message); |
取得したいのは、次の例でいう「testです」になります。
1 | $ deno run argsTest.ts testです |
実行結果
実行結果を示します(以降、コンパイルメッセージは省略します)。
1 | arg[0]:testです |
正常に取得できています。
特に難しいことはありませんが、Node.js の process.argv に慣れていた人は、配列添え字に注意が必要です。
Node.jsのコマンドライン引数
参考までに、Node.js での process.argv を使ったコマンドライン引数取得について説明します。
こちらも配列ですが、最初の要素が「node コマンド自体」、次が「ファイル名」となり、「コマンドライン引数」は 3 番目以降に格納されます。
「testです」を取得したい場合
例えば、次のコマンドを実行した場合の「testです」を取得したいとします。
1 | $ node argsTest.js testです |
Node.js のコードは次のようになります。
1 | console.log('process.argv[2]:' + process.argv[2]); |
process が持つ argv[0] は「node」、 argv[1] は「argsTest.js」となり、「testです」が格納されるのは argv[2] となります。
実践的なコードを書いてみよう
Deno のコードを実行する方法は分かりました。
ここからは、少し実践的なコードをいくつか書いてみましょう。
ファイル入出力
標準モジュールでのファイル入出力を試します。
ファイル入出力用モジュールはこちらです。
【Deno 標準ライブラリ】
https://deno.land/std/fs
まずはテキストファイルを出力してみましょう。
writeFileTest.ts を作成
次の内容のファイル「writeFileTest.ts」を作りましょう。
1 2 3 4 | import {writeFileStr} from 'https://deno.land/std/fs/write_file_str.ts'; const message: string = '祇園精舎の鐘の声\n諸行無常の響きあり\n何とかかんとかの花の色\nどうたらこうたらの理をあらわす'; await writeFileStr('sample.txt', message); console.log('done'); // 終了時に表示する文字列 |
ファイル読み込み用のモジュールを読み込み、変数 message に文字列を格納、最後にその内容を「sample.txt」に書き出しています。
Node.js でいうところの「npm」に相当する仕組みは無く、URL(またはパス)によってモジュールを指定します。
https://deno.land/std/fs を開くと、配下にどのようなモジュールがあるか確認できます。
まとめてインポートしたいときは、モジュールのエントリーファイルである「mod.ts」を指定すればよいでしょう。
ここでは「write_file_str.ts」だけで事足りるため、個別にインポートしました。
実行
では実行してみましょう。
1 2 3 4 5 6 7 8 | $ deno run writeFileTest.ts error: Uncaught PermissionDenied: write access to "/app/sample.txt", run again with the --allow-write flag at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11) at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10) at async Object.open ($deno$/files.ts:37:15) at async Object.writeFile ($deno$/write_file.ts:60:16) at async writeFileStr (https://deno.land/std/fs/write_file_str.ts:27:3) at async file:///app/e02_filewrite.ts:3:16 |
エラーが発生しました。
「ファイルに対して書き込みアクセスを行うならば --allow-write フラグとともに実行してください」と書いてあります。
エラーを修正し実行
言われたとおり、--allow-write フラグを追加して実行してみましょう。
1 2 | $ deno run --allow-write e02_filewrite.ts done |
今度は実行できました。
「ls」コマンドなどで確認し、「sample.txt」が作られていれば成功です。
これが Deno の公式サイトに書いている「ファイル・ネットワーク・環境変数へのアクセスには明示的な許可が必要」ということです。
「ファイルへの書き込みアクセスを許可する」と明示する必要
今回実行したのは、ファイル出力を含んだコードでしたので、実行時にそれを明示的に許可する必要がありました。
たとえば、どこかからコードを拝借し、中身を確認せずに実行しても「ファイルへの書き込みアクセスを許可する」と明示しない限りはファイル出力は行われません。
ファイルを読み込む
書き出しに成功しましたので、次は出力したファイルを読み込んでみましょう。
次の内容のファイル「readFileTest.ts」を作りましょう。
1 2 3 | import {readFileStr} from 'https://deno.land/std/fs/read_file_str.ts'; const message: string = await readFileStr('sample.txt'); // sample.txt の中身を取得 console.log(message); |
ファイル読み込み用のモジュールを読み込み、変数 message にファイルの内容を格納、 message を標準出力するという流れです。
実行
実行時には --allow-read フラグによって読み込みを許可する必要があります。
1 2 3 4 5 | $ deno run --allow-read readFileTest.ts 祇園精舎の鐘の声 諸行無常の響きあり 何とかかんとかの花の色 どうたらこうたらの理をあらわす |
正常にファイルの内容が取得できました。
「トップレベル await」
書き込み/読み込みともに、「async」で宣言した関数内ではないのに await を使っています。
Deno は、最上位の階層で await を使うことが可能です。
これは「トップレベル await」と呼ばれます。
await を使うために、async 関数を作る必要はありません。
Node.js も v14.3.0 から「トップレベル await」が可能になっています。
環境変数へのアクセス
テストと本番で使う値を変えるために、環境変数を使うのが便利です。
「.env」 というファイルを作成し、次の内容を記述します。
1 | GREETING=hello |
では Deno で、これらの値を取得してみます。
値を取得する
サードパーティー製の「dotenvモジュール」を使用します。
「envTest.ts」として、次の内容を記述します。
1 | console.log(Deno.env.get('GREETING')); // 環境変数 GREETING を取得 |
実行
実行には --allow-env で環境変数へのアクセスを許可して実行します。
1 2 | $ deno run --allow-env envTest.ts hello |
環境変数 GREETING の値が取得できました。
他の記述の仕方
環境変数へのアクセスは、次のようにも記述できます。
1 2 3 | import {config} from 'https://deno.land/x/dotenv/dotenv.ts'; const env:any = config(); console.log(env.GREETING); |
標準モジュールは、https://deno.land/std にありましたが、サードパーティーのモジュールは https://deno.land/x にあります。
ここでは、https://deno.land/x/dotenv を使いました。
環境変数へのアクセス用に、 env というオブジェクトを作成します。
こちらを実行するには --allow-env ではなく --allow-read フラグが必要になります。
データベースアクセス
データベースから情報を取得してみましょう。
ここでは MongoDB を使います。
dbAccessTest.ts を作成
次の内容のファイル「dbAccessTest.ts」を作りましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import {MongoClient} from 'https://deno.land/x/mongo/mod.ts'; // 接続先 URI const mongouri:string = 'mongodb+srv://'+Deno.env.get('USER')+':'+Deno.env.get('PASS')+'@'+Deno.env.get('MONGOHOST'); // 接続 const client = new MongoClient(); client.connectWithUri(mongouri); const db = client.database('Hanson'); // データベース const col = db.collection('users'); // コレクション const user = await col.findOne({_id:{ $oid: '5ed7778c12dafe384cc0eaee' }}); // 指定した OID にヒットする1件を取得 console.log(user); |
DBユーザ名、パスワード、ホストなどは「.env」に記述されているものとします。
MongoDB はコレクションにドキュメントを保存する際に、"_id" というプロパティに ObjectID を自動的に付与します。
その ObjectID によって保存されているドキュメントを1件取得する例です。
何もフラグを付けずに実行しようとすれば、その都度コンソールにどんな権限が足りないかのエラーメッセージを表示するので、与えるフラグはすぐにわかります。
実行するコマンド
実行するためのコマンドは次のようになります。
1 | $ deno run -q --allow-read --allow-write --allow-plugin --unstable dbAccessTest.ts |
dbAccess.ts 内では Read しか実行していませんが、コレクションに対して、Create、Update、Delete の操作を実行する可能性もあるためか、--allow-write なども必要になるようです。
標準出力
実行すると、 findOne によって検索した結果にヒットする1件を標準出力します。
1 | { _id: { $oid: "5ed7778c12dafe384cc0eaee" }, name: "山田", age: 30 } |
HTTPサーバ化
HTTP リクエストを受け付け、要求されたパスに応じてレスポンスを返してみましょう。
serverTest.ts を作成
次の内容のファイル「serverTest.ts」を作りましょう。
なお、「serverTest.ts」と同じ階層にある「views」フォルダの下に「index.html」があるものとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import {serve} from 'https://deno.land/std/http/server.ts'; import {readFileStr} from 'https://deno.land/std/fs/read_file_str.ts'; const s = serve({port: 3000}); // 3000 番号ポートで受け付ける for await (const req of s) { if(req.method == 'GET' && req.url == '/') { // '/' に対して GET リクエストがあったときだけ index.html を返す if(req.method == 'GET' && req.url == '/') { const body: string = await readFileStr('./views/index.html'); req.respond({body:body}); }else{ req.respond({status:404, headers:new Headers({'content-type':'text/plain'}), body:'not implemented'}); } } |
'/' に対して GET リクエストがあった場合のみ、./views/index.html の内容をレスポンスとして返します。
ネットワークアクセスを行うため、実行時には --allow-net フラグが必要です。
また、./views/index.html ファイルを読み込むため、 --allow-read フラグも必要です。
実行
次のコマンドで実行します。
1 | $ deno run --allow-net --allow-read serverTest.ts |
実行すると、3000 番ポートで HTTP リクエストを受け付けます。
http://localhost:3000/ にブラウザでアクセスすれば、./views/index.html を表示します。
それ以外のパスへのリクエスト、あるいは GET ではないリクエストでは、ステータスコード 404 とともに、"not implemented" というテキストをレスポンスとして返します。
サーバは Ctrl+C で停止させます。
opine によるルーティング
Deno でも「Express」のようなルーティングが可能です。
opine モジュールを利用します。
opineTest.ts を作成
次の内容のファイル「opineTest.ts」を作りましょう。
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 | import {opine, serveStatic} from 'https://deno.land/x/opine@master/mod.ts'; const app = opine(); app.use(serveStatic('public')); // 静的なファイルを public 配下に置く // ファイルの内容を返す app.get('/', function (req, res) { res.sendFile('./views/index.html'); }); // 文字列を返す app.get('/hello', function(req, res) { res.send('Hello Deno!'); }); // ステータスを返す app.get('/status', function(req, res){ res.sendStatus(200); }); // JSON を返す app.get('/json', function(req, res){ res.json({greeting:'hello'}); }); app.listen(3000); |
最初の3行を除けば、「Express」を利用して Node.js で記述するのとほぼ同じです。
「Express」に慣れていれば馴染みやすいでしょう。
実行
次のコマンドで実行します。
1 | $ deno run --allow-net opineTest.ts |
sendFile によってファイルの内容をレスポンスにしていますが、意外なことに --allow-read フラグなしで実行できました。
実行したら、ブラウザでそれぞれのパスにアクセスして挙動を確認してみましょう。
opine で指定できるルート・パス
opine で指定できるルート・パスについて、Express と同じではありますが補足しておきます。
ルート・パスには文字列の他に正規表現が指定可能です。
また、コロンを使ってルートパラメータを取得することも可能です。
opineTest.ts を作成
次の内容のファイル「opineTest.ts」を作りましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // パスが '/foo'、'/fooooo' などの GET リクエストにマッチ app.get('/fo+', function (req, res) { res.send('fo+') }); // パスが '/yusuke'、'/daisuke' などの GET リクエストにマッチ app.get(/.*suke$/, function (req, res) { res.send('/.*suke$/') }); // req.params.userId、req.params.pageId が取得可能 app.get('/:userId/:pageId', function(req, res){ res.json(req.params); }); |
これについても Express と同じです。
fetchによるリクエスト
Deno は、ブラウザで使える Web API のひとつである Fetch API がそのまま利用可能です。
Fetch API については MDN に詳しく記述されています。
【MDN :Fetchを使う】
https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch
これを Deno で試してみます。
fetchTest.ts を作成
次の内容の「fetchTest.ts」ファイルを作りましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | const url:string = 'https://example.com/'; getData(url) .then(data => console.log(data)) .catch(error => console.error(error)); function getData(url:string) { // 既定のオプションには * が付いています return fetch(url, { method: 'GET', cache: 'no-cache', referrer: 'no-referrer' }) .then(response => response.text()); } |
1行目の URL はダミーですので、送信したいリクエスト先に適宜書き換えてください。
あとは適当なオプションを指定して GET リクエストを送信し、レスポンスを取得するだけです。
実行
--allow-net フラグを付けて実行します。
1 | $ deno run --allow-net fetchTest.ts |
リクエスト先が存在すれば、これでレスポンスが取得できます。
Fetch API さえ知っていれば、新しい知識がなくても HTTP 通信が可能です。
Denoのその他の特徴
最後に Deno の特徴を他にも少しだけ紹介します。
標準機能でテストが可能
はじめから Deno にはテストの仕組みが備わっています。
外部モジュールを使用しなくても、Deno.test によってテストを行うことが可能です。
次のような「testTest.ts」を用意します。
1 2 3 4 5 | import {assertEquals} from 'https://deno.land/std/testing/asserts.ts'; Deno.test('assertion', () => { const num1: number = 100; assertEquals(num1, 100); }); |
deno test によって実行
このスクリプトを、deno run ではなく、deno test によって実行します。
1 | $ deno test testTest.ts |
実行結果
実行結果は次のとおりです。
1 2 3 4 | running 1 tests test assertion!!! ... ok (4ms) test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (4ms) |
変数の値が期待どおりであることが確認できました。
Uncaught Exceptionでは処理を終了する
Node.js では、process にイベントを定義することにより、Uncaught Exception が発生した際の挙動を制御できました。
例えば次のように記述します。
1 2 3 4 5 | process.on('uncaughtException', function (err) { console.error(err.name); console.error(err.message); process.exit(-1); }); |
このときに process.exit を実行しなければ処理を継続することが可能です。
ですが、Deno ではその機能が無く、処理は終了してしまうようです。
モジュールのバージョン指定
モジュールをインポートする際には、次のように書きました。
1 | import {MongoClient} from 'https://deno.land/x/mongo/mod.ts'; |
このとき、使用するモジュールを特定のバージョンに限定したい場合は、"@" を使って次のように記述することができます。
1 | import {MongoClient} from 'https://deno.land/x/mongo@v0.7.0/mod.ts'; |
さいごに
Deno の機能について、取り留めなく書き連ねましたが、どのような印象を持たれたでしょうか。
個人的な感想としては、現時点でもかなり使えそうだと思いました。
Node.js に慣れていれば、それほど違和感なく使えるはずです。
一部の機能については、Node.js よりも優れているとも感じました。
もしかすると Deno の時代は、案外早く訪れるのかもしれません。
ぜひ皆さんも、Deno の使い勝手を確かめてみてください!
こちらの記事もオススメ!
書いた人はこんな人

- 「好きなことを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もりは大歓迎!
また、WEBエンジニアとモバイルエンジニアも積極採用中です!
ご応募をお待ちしております!
ITエンタメ2022.06.22IntelliJ IDEAとkotlinを送り出したJetBrains創業物語
ITエンタメ2022.06.15【アタリ創業者】スティーブ・ジョブズを雇った男「ノーラン・ブッシュネル」
ITエンタメ2022.06.13プログラミングに飽きてPHPを開発したラスマス・ラードフ
ITエンタメ2022.06.03【Unity開発秘話】ゲーマーを開発者にしてしまうゲームエンジン