Deno(ディーノ)を使ってみよう!【Node.jsの時代は終わる?】
IT技術
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######################################################### 100.0%
2Archive: /app/.deno/bin/deno.zip
3 inflating: deno
4Deno was installed successfully to /app/.deno/bin/deno
5Manually add the directory to your $HOME/.bash_profile (or similar)
6 export DENO_INSTALL="/app/.deno"
7 export PATH="$DENO_INSTALL/bin:$PATH"
8Run '/app/.deno/bin/deno --help' to get started
パスを実行
親切にパスを通す方法が書いてありますので、そのとおりに実行しましょう。
1$ export DENO_INSTALL="/app/.deno"
2$ export PATH="$DENO_INSTALL/bin:$PATH"
Deno を実行できるか確認
Deno を実行できるか確認してみましょう。
次のコマンドを実行します。
1$ deno -V
Deno のバージョンが表示されたら環境作りは成功です。
次は、簡単なスクリプトを実行してみましょう。
Hello World
実際に、簡単なコードを動かしてみます。
hello.ts を作成
次の内容のファイル「hello.ts」を作りましょう。
1console.log('Hello World!');
文字列を標準出力するだけのスクリプトです。
Node.js と同様に console.log で標準出力が可能です。
コマンドで実行
deno run コマンドによって実行します。
1$ deno run -q hello.ts
実行結果
実行結果を確認しましょう。
次のような結果が出力されれば成功です。
1Compile file:///app/hello.ts
2Hello World!
初回のみコンパイルを実行し、1行目に "Compile {ファイル名}" といったメッセージを表示します。
2回目以降の実行では、2行目の "Hello World!" のみ出力します。
次のように、-q オプションを付けることにより、コンパイルのメッセージを表示させないことも可能です。
1$ deno run -q hello.ts
コマンドライン引数の取得
実行時に指定したコマンドライン引数を、受け取ってみましょう。
コマンドライン引数を取得するには、配列 Deno.args を使います。
argsTest.ts を作る
次の内容のファイル「argsTest.ts」を作りましょう。
1const message: string = 'arg[0]:' + Deno.args[0];
2console.log(message);
取得したいのは、次の例でいう「testです」になります。
1$ deno run argsTest.ts testです
実行結果
実行結果を示します(以降、コンパイルメッセージは省略します)。
1arg[0]:testです
正常に取得できています。
特に難しいことはありませんが、Node.js の process.argv に慣れていた人は、配列添え字に注意が必要です。
Node.jsのコマンドライン引数
参考までに、Node.js での process.argv を使ったコマンドライン引数取得について説明します。
こちらも配列ですが、最初の要素が「node コマンド自体」、次が「ファイル名」となり、「コマンドライン引数」は 3 番目以降に格納されます。
「testです」を取得したい場合
例えば、次のコマンドを実行した場合の「testです」を取得したいとします。
1$ node argsTest.js testです
Node.js のコードは次のようになります。
1console.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」を作りましょう。
1import {writeFileStr} from 'https://deno.land/std/fs/write_file_str.ts';
2const message: string = '祇園精舎の鐘の声\n諸行無常の響きあり\n何とかかんとかの花の色\nどうたらこうたらの理をあらわす';
3await writeFileStr('sample.txt', message);
4console.log('done'); // 終了時に表示する文字列
ファイル読み込み用のモジュールを読み込み、変数 message に文字列を格納、最後にその内容を「sample.txt」に書き出しています。
Node.js でいうところの「npm」に相当する仕組みは無く、URL(またはパス)によってモジュールを指定します。
https://deno.land/std/fs を開くと、配下にどのようなモジュールがあるか確認できます。
まとめてインポートしたいときは、モジュールのエントリーファイルである「mod.ts」を指定すればよいでしょう。
ここでは「write_file_str.ts」だけで事足りるため、個別にインポートしました。
実行
では実行してみましょう。
1$ deno run writeFileTest.ts
2error: Uncaught PermissionDenied: write access to "/app/sample.txt", run again with the --allow-write flag
3 at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
4 at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10)
5 at async Object.open ($deno$/files.ts:37:15)
6 at async Object.writeFile ($deno$/write_file.ts:60:16)
7 at async writeFileStr (https://deno.land/std/fs/write_file_str.ts:27:3)
8 at async file:///app/e02_filewrite.ts:3:16
エラーが発生しました。
「ファイルに対して書き込みアクセスを行うならば --allow-write フラグとともに実行してください」と書いてあります。
エラーを修正し実行
言われたとおり、--allow-write フラグを追加して実行してみましょう。
1$ deno run --allow-write e02_filewrite.ts
2done
今度は実行できました。
「ls」コマンドなどで確認し、「sample.txt」が作られていれば成功です。
これが Deno の公式サイトに書いている「ファイル・ネットワーク・環境変数へのアクセスには明示的な許可が必要」ということです。
「ファイルへの書き込みアクセスを許可する」と明示する必要
今回実行したのは、ファイル出力を含んだコードでしたので、実行時にそれを明示的に許可する必要がありました。
たとえば、どこかからコードを拝借し、中身を確認せずに実行しても「ファイルへの書き込みアクセスを許可する」と明示しない限りはファイル出力は行われません。
ファイルを読み込む
書き出しに成功しましたので、次は出力したファイルを読み込んでみましょう。
次の内容のファイル「readFileTest.ts」を作りましょう。
1import {readFileStr} from 'https://deno.land/std/fs/read_file_str.ts';
2const message: string = await readFileStr('sample.txt'); // sample.txt の中身を取得
3console.log(message);
ファイル読み込み用のモジュールを読み込み、変数 message にファイルの内容を格納、message を標準出力するという流れです。
実行
実行時には --allow-read フラグによって読み込みを許可する必要があります。
1$ deno run --allow-read readFileTest.ts
2祇園精舎の鐘の声
3諸行無常の響きあり
4何とかかんとかの花の色
5どうたらこうたらの理をあらわす
正常にファイルの内容が取得できました。
「トップレベル await」
書き込み/読み込みともに、「async」で宣言した関数内ではないのに await を使っています。
Deno は、最上位の階層で await を使うことが可能です。
これは「トップレベル await」と呼ばれます。
await を使うために、async 関数を作る必要はありません。
Node.js も v14.3.0 から「トップレベル await」が可能になっています。
環境変数へのアクセス
テストと本番で使う値を変えるために、環境変数を使うのが便利です。
「.env」 というファイルを作成し、次の内容を記述します。
1GREETING=hello
では Deno で、これらの値を取得してみます。
値を取得する
サードパーティー製の「dotenvモジュール」を使用します。
「envTest.ts」として、次の内容を記述します。
1console.log(Deno.env.get('GREETING')); // 環境変数 GREETING を取得
実行
実行には --allow-env で環境変数へのアクセスを許可して実行します。
1$ deno run --allow-env envTest.ts
2hello
環境変数 GREETING の値が取得できました。
他の記述の仕方
環境変数へのアクセスは、次のようにも記述できます。
1import {config} from 'https://deno.land/x/dotenv/dotenv.ts';
2const env:any = config();
3console.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」を作りましょう。
1import {MongoClient} from 'https://deno.land/x/mongo/mod.ts';
2
3// 接続先 URI
4const mongouri:string = 'mongodb+srv://'+Deno.env.get('USER')+':'+Deno.env.get('PASS')+'@'+Deno.env.get('MONGOHOST');
5
6// 接続
7const client = new MongoClient();
8client.connectWithUri(mongouri);
9
10const db = client.database('Hanson'); // データベース
11const col = db.collection('users'); // コレクション
12
13const user = await col.findOne({_id:{ $oid: '5ed7778c12dafe384cc0eaee' }}); // 指定した OID にヒットする1件を取得
14console.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」があるものとします。
1import {serve} from 'https://deno.land/std/http/server.ts';
2import {readFileStr} from 'https://deno.land/std/fs/read_file_str.ts';
3
4const s = serve({port: 3000}); // 3000 番号ポートで受け付ける
5
6for await (const req of s) {
7 if(req.method == 'GET' && req.url == '/') {
8 // '/' に対して GET リクエストがあったときだけ index.html を返す
9 if(req.method == 'GET' && req.url == '/') {
10 const body: string = await readFileStr('./views/index.html');
11 req.respond({body:body});
12 }else{
13 req.respond({status:404, headers:new Headers({'content-type':'text/plain'}), body:'not implemented'});
14 }
15}
'/' に対して 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」を作りましょう。
1import {opine, serveStatic} from 'https://deno.land/x/opine@master/mod.ts';
2const app = opine();
3app.use(serveStatic('public')); // 静的なファイルを public 配下に置く
4
5// ファイルの内容を返す
6app.get('/', function (req, res) {
7 res.sendFile('./views/index.html');
8});
9
10// 文字列を返す
11app.get('/hello', function(req, res) {
12 res.send('Hello Deno!');
13});
14
15// ステータスを返す
16app.get('/status', function(req, res){
17 res.sendStatus(200);
18});
19
20// JSON を返す
21app.get('/json', function(req, res){
22 res.json({greeting:'hello'});
23});
24
25app.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// パスが '/foo'、'/fooooo' などの GET リクエストにマッチ
2app.get('/fo+', function (req, res) {
3 res.send('fo+')
4});
5
6// パスが '/yusuke'、'/daisuke' などの GET リクエストにマッチ
7app.get(/.*suke$/, function (req, res) {
8 res.send('/.*suke$/')
9});
10
11// req.params.userId、req.params.pageId が取得可能
12app.get('/:userId/:pageId', function(req, res){
13 res.json(req.params);
14});
これについても 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」ファイルを作りましょう。
1const url:string = 'https://example.com/';
2
3getData(url)
4 .then(data => console.log(data))
5 .catch(error => console.error(error));
6
7function getData(url:string) {
8 // 既定のオプションには * が付いています
9 return fetch(url, {
10 method: 'GET',
11 cache: 'no-cache',
12 referrer: 'no-referrer'
13 })
14 .then(response => response.text());
15}
1行目の URL はダミーですので、送信したいリクエスト先に適宜書き換えてください。
あとは適当なオプションを指定して GET リクエストを送信し、レスポンスを取得するだけです。
実行
--allow-net フラグを付けて実行します。
1$ deno run --allow-net fetchTest.ts
リクエスト先が存在すれば、これでレスポンスが取得できます。
Fetch API さえ知っていれば、新しい知識がなくても HTTP 通信が可能です。
Denoのその他の特徴
最後に Deno の特徴を他にも少しだけ紹介します。
標準機能でテストが可能
はじめから Deno にはテストの仕組みが備わっています。
外部モジュールを使用しなくても、Deno.test によってテストを行うことが可能です。
次のような「testTest.ts」を用意します。
1import {assertEquals} from 'https://deno.land/std/testing/asserts.ts';
2Deno.test('assertion', () => {
3 const num1: number = 100;
4 assertEquals(num1, 100);
5});
deno test によって実行
このスクリプトを、deno run ではなく、deno test によって実行します。
1$ deno test testTest.ts
実行結果
実行結果は次のとおりです。
1running 1 tests
2test assertion!!! ... ok (4ms)
3
4test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (4ms)
変数の値が期待どおりであることが確認できました。
Uncaught Exceptionでは処理を終了する
Node.js では、process にイベントを定義することにより、Uncaught Exception が発生した際の挙動を制御できました。
例えば次のように記述します。
1process.on('uncaughtException', function (err) {
2 console.error(err.name);
3 console.error(err.message);
4 process.exit(-1);
5});
このときに process.exit を実行しなければ処理を継続することが可能です。
ですが、Deno ではその機能が無く、処理は終了してしまうようです。
モジュールのバージョン指定
モジュールをインポートする際には、次のように書きました。
1import {MongoClient} from 'https://deno.land/x/mongo/mod.ts';
このとき、使用するモジュールを特定のバージョンに限定したい場合は、"@" を使って次のように記述することができます。
1import {MongoClient} from 'https://deno.land/x/mongo@v0.7.0/mod.ts';
さいごに
Deno の機能について、取り留めなく書き連ねましたが、どのような印象を持たれたでしょうか。
個人的な感想としては、現時点でもかなり使えそうだと思いました。
Node.js に慣れていれば、それほど違和感なく使えるはずです。
一部の機能については、Node.js よりも優れているとも感じました。
もしかすると Deno の時代は、案外早く訪れるのかもしれません。
ぜひ皆さんも、Deno の使い勝手を確かめてみてください!
こちらの記事もオススメ!
2020.08.07JavaScript 特集知識編JavaScriptを使ってできることをわかりやすく解説!JavaScriptの歴史【紆余曲折を経たプログラミン...
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit
おすすめ記事
immichを知ってほしい
2024.10.31