Deno/TypeScript入門〜HTTPレスポンスをCSV出力してみる編〜

# まえがき

最近私はツールを開発する環境として「[Deno](https://deno.land/)」が気に入っています。
シンプルで、軽くて、ランタイムAPIや標準ライブラリが充実していて、開発体験が良いと感じています。
私のところではDenoを本番で採用しようとすると諸々ハードルがありますが、ツールの開発であれば選択肢として大いにアリです。

この記事では、Deno/TypeScriptの概要と、サンプルとして「HTTPレスポンス(JSON)をCSVファイルに出力するツール」の開発手順を解説します。

私自身も初学者なので、間違い等があればコメントでご教示ください。

# 前提環境

Deno 1.20.5

# Denoとは

## 概要

[Wikipedia](https://ja.wikipedia.org/wiki/Deno)より引用：

>Denoは、V8 JavaScriptエンジン及びRustプログラミング言語に基づいた、JavaScript及びTypeScriptのランタイム環境である。
>
>Node.jsの作者であるライアン・ダールによって作成され、セキュリティと生産性に焦点を当てている。
>ライアン・ダールが2018年に行った講演「Node.jsに関する10の反省点」で発表された。

## 主な特徴

## TypeScriptを標準サポート

- トランスパイラを内蔵しており、Babelなどのトランスパイラ環境がなくても実行できます。

[TypeScript Deep Dive 日本語版](https://typescript-jp.gitbook.io/deep-dive/)より引用：
> TypeScriptはJavaScriptを拡張して作られたプログラミング言語です。
> トレンドが示すとおり、TypeScriptはJavaScriptに代わって第一に選択される言語になりました。
> TypeScriptが提供する静的型システムは、コードの保守性と可読性を大幅に向上させます。
> またブラウザ等の互換性を心配することなく、モダンで便利なJavaScriptの機能を利用できます。

TypeScriptのダウンロード動向：
![Screen Shot 2022-02-04 at 22.03.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/435fb682-0c12-718c-4ac1-3453ac0ac4cc.png)

## パッケージ（モジュール）管理の仕組みがシンプル

- `npm`, `node_modules`, `package.json` といったツールや設定ファイルがありません。
- 外部モジュールを使う場合は`import`にURLを渡すだけ。さらに`require`も不要です。
```
import { format, parse } from "https://deno.land/std@0.138.0/datetime/mod.ts";
```
- ダウンロードは実行時に行われ、結果はキャッシュされます。
- すなわち、Denoはランタイム環境であるだけでなくパッケージ管理ツールの役割も持っています。

その他にも、素晴らしい特徴がいろいろあります。さらに詳しく知りたい方は文末のリンク集をご参照ください。

# Denoのインストール

公式ドキュメントを参照するのが確実です。

https://deno.land/manual/getting_started/installation

# Visual Studio Code (VSCode) の環境構築

テキストエディタでも開発は可能ですが、統合開発環境（IDE）の方が、コード補完や静的チェックが効きますので開発が捗ります。

Denoでは皆さんVSCodeを使っていらっしゃるようです。
以下の記事に素晴らしくまとまっています。

https://qiita.com/SuzuTomo2001/items/3d85eb92fcfff255ce46

あれこれ調べるのがめんどくさかったら（笑）、
拡張機能「Deno for Visual Studio Code」をインストールして、
作業フォルダ配下に`.vscode`フォルダを作成し、
以下のファイルを格納すれば、とりあえず開発を始められると思います。

```json: settings.json
{
    "debug.javascript.unmapMissingSources": true,
    "deno.enable": true,
    "deno.lint": true,
    "deno.unstable": true,
    "deno.suggest.imports.hosts": {
        "https://deno.land": true
    }
}
```

VSCodeのデバッグ実行を使いたい場合は`launch.json`も格納します。

<details><summary>launch.json</summary>

```json: launch.json
{
    "version": "0.2.0",
    "configurations": [
      {
        "name": "Deno",
        "type": "pwa-node",
        "request": "launch",
        "cwd": "${workspaceFolder}",
        "runtimeExecutable": "deno",
        "runtimeArgs": ["run", "--inspect-brk", "-A", "./app.ts"],
        "attachSimplePort": 9229
      }
    ]
}
```

※app.tsはアプリケーションのエントリーポイントとなるファイル名ですので、適宜書き変えてください。

</details>

# 「HTTPレスポンス(JSON)をCSVファイルに出力するツール」の開発

## APIについて

APIキーもログインも不要で無料で使える天気予報API「[Open-Meteo](https://open-meteo.com/en/docs)」を使ってみます。
（※商用利用はNGのようです。ライセンス要件をご確認ください。）

Open-Meteoはパラメータによって様々なデータを取得できますが、今回は「東京の1週間分の気温」をGETします。
URLは以下で、ブラウザでも試すことが出来ます。
https://api.open-meteo.com/v1/forecast?latitude=35.6785&longitude=139.6823&hourly=temperature_2m&timezone=Asia%2FTokyo

<details><summary>レスポンスJSONのサンプル</summary>

```json
{
  utc_offset_seconds: 0,
  hourly: {
    temperature_2m: [
      20.5,   21, 21.3, 21.3, 21.6, 21.4, 19.8, 18.3, 17.6,   17,
      16.7, 16.7, 16.5, 16.3, 16.1, 16.1, 16.1,   16, 15.9, 15.9,
      15.5, 14.5,   14, 14.4, 15.3, 15.7, 15.3,   15, 15.1, 14.8,
      14.2, 13.8, 13.4,   13, 12.7, 12.6, 12.5, 12.5, 12.4, 12.4,
      12.4, 12.4, 12.3, 12.2, 12.1, 12.6, 14.1, 15.8, 17.6, 18.7,
      20.6,   22,   22, 21.4, 20.9,   20, 19.1, 18.1, 17.3, 16.9,
      16.5, 16.2,   16,   16,   16, 15.9, 15.9, 15.9, 15.9, 15.9,
        16, 16.3, 16.3, 16.8, 17.4, 18.1, 18.3, 18.4, 18.3, 18.2,
        18, 17.8, 17.7, 17.6, 17.5, 17.4, 17.3, 17.1,   17, 16.9,
      16.8, 16.7, 16.7, 16.8, 17.3, 17.9, 18.8, 19.6, 20.5, 21.5,
      ... 68 more items
    ],
    time: [
      "2022-05-08T00:00", "2022-05-08T01:00", "2022-05-08T02:00",
      "2022-05-08T03:00", "2022-05-08T04:00", "2022-05-08T05:00",
      "2022-05-08T06:00", "2022-05-08T07:00", "2022-05-08T08:00",
      "2022-05-08T09:00", "2022-05-08T10:00", "2022-05-08T11:00",
      "2022-05-08T12:00", "2022-05-08T13:00", "2022-05-08T14:00",
      "2022-05-08T15:00", "2022-05-08T16:00", "2022-05-08T17:00",
      "2022-05-08T18:00", "2022-05-08T19:00", "2022-05-08T20:00",
      "2022-05-08T21:00", "2022-05-08T22:00", "2022-05-08T23:00",
      "2022-05-09T00:00", "2022-05-09T01:00", "2022-05-09T02:00",
      "2022-05-09T03:00", "2022-05-09T04:00", "2022-05-09T05:00",
      "2022-05-09T06:00", "2022-05-09T07:00", "2022-05-09T08:00",
      "2022-05-09T09:00", "2022-05-09T10:00", "2022-05-09T11:00",
      "2022-05-09T12:00", "2022-05-09T13:00", "2022-05-09T14:00",
      "2022-05-09T15:00", "2022-05-09T16:00", "2022-05-09T17:00",
      "2022-05-09T18:00", "2022-05-09T19:00", "2022-05-09T20:00",
      "2022-05-09T21:00", "2022-05-09T22:00", "2022-05-09T23:00",
      "2022-05-10T00:00", "2022-05-10T01:00", "2022-05-10T02:00",
      "2022-05-10T03:00", "2022-05-10T04:00", "2022-05-10T05:00",
      "2022-05-10T06:00", "2022-05-10T07:00", "2022-05-10T08:00",
      "2022-05-10T09:00", "2022-05-10T10:00", "2022-05-10T11:00",
      "2022-05-10T12:00", "2022-05-10T13:00", "2022-05-10T14:00",
      "2022-05-10T15:00", "2022-05-10T16:00", "2022-05-10T17:00",
      "2022-05-10T18:00", "2022-05-10T19:00", "2022-05-10T20:00",
      "2022-05-10T21:00", "2022-05-10T22:00", "2022-05-10T23:00",
      "2022-05-11T00:00", "2022-05-11T01:00", "2022-05-11T02:00",
      "2022-05-11T03:00", "2022-05-11T04:00", "2022-05-11T05:00",
      "2022-05-11T06:00", "2022-05-11T07:00", "2022-05-11T08:00",
      "2022-05-11T09:00", "2022-05-11T10:00", "2022-05-11T11:00",
      "2022-05-11T12:00", "2022-05-11T13:00", "2022-05-11T14:00",
      "2022-05-11T15:00", "2022-05-11T16:00", "2022-05-11T17:00",
      "2022-05-11T18:00", "2022-05-11T19:00", "2022-05-11T20:00",
      "2022-05-11T21:00", "2022-05-11T22:00", "2022-05-11T23:00",
      "2022-05-12T00:00", "2022-05-12T01:00", "2022-05-12T02:00",
      "2022-05-12T03:00",
      ... 68 more items
    ]
  },
  hourly_units: { temperature_2m: "°C", time: "iso8601" },
  longitude: 139.625,
  generationtime_ms: 2.048015594482422,
  elevation: 20.84375,
  latitude: 35.625
}
```

</details>


## CSV出力結果のサンプル

```text:temperature.csv
2022/05/08 00:00,16.8
2022/05/08 01:00,16.4
2022/05/08 02:00,16.2
2022/05/08 03:00,16.5
2022/05/08 04:00,16.4
（以下略）
```

## コード

```typescript: app.ts
import { format, parse } from "https://deno.land/std@0.138.0/datetime/mod.ts";

let output = "";
let temperatureList: number[] = [];
let dateTimeList: string[] = []; // e.g."2022-05-08T21:00"

// GETとparse
try {
  // 東京の1週間分の気温をGET
  const response = await fetch(
    "https://api.open-meteo.com/v1/forecast?latitude=35.6785&longitude=139.6823&hourly=temperature_2m&timezone=Asia%2FTokyo",
  );
  const responseJson = await response.json();
  console.log(responseJson);
  temperatureList = responseJson["hourly"]["temperature_2m"] as number[];
  dateTimeList = responseJson["hourly"]["time"] as string[];
} catch (e) {
  console.log(`API error: ${e.message}`);
  Deno.exit();
}

// CSV出力用文字列に整形
temperatureList.forEach((temperature, i) => {
  // 2件め以降の場合は改行を挿入
  if (i > 0) {
    output += "\n";
  }
  // 日時フォーマット
  const outputDateTime = format(
    parse(dateTimeList[i], "yyyy-MM-dd'T'HH:mm"),
    "yyyy/MM/dd HH:mm",
  );
  // CSV出力用文字列に追加
  output += `${outputDateTime},${temperature}`;
});

// CSV書き出し
try {
  Deno.writeTextFileSync("./temperature.csv", output);
  console.log(`Written to ./temperature.csv`);
} catch (e) {
  console.log(`Output error: ${e.message}`);
  Deno.exit();
}
```

## 実行

Denoのセキュリティ機構として、ネットワークを許可するための`--allow-net`と、ファイル書き込みを許可するための`--allow-write`オプションを付けて実行します。

```
$ deno run --allow-net --allow-write app.ts
```

## コードのちょっとした解説

### Denoの標準APIについて

Denoはブラウザのように`fetch`などのWeb標準APIを実装しています。
[fetch_data](https://deno.land/manual/examples/fetch_data)

また、DenoのランタイムAPIは、テキストファイルの読み書きのために `Deno.readTextFile` と `Deno.writeTextFile` 非同期関数を提供します。
同等の同期関数 `Deno.readTextFileSync` と `Deno.writeTextFileSync` もあります。
[read_write_files](https://deno.land/manual/examples/read_write_files)

### Denoの標準ライブラリについて

今回使用した`datetime`のような、Deno本体との連携が保証された標準ライブラリが提供されています。
標準ライブラリは、サードパーティ・ライブラリと同じく`import`して使用します。
また、`import`する際には上のコードの`/std@0.138.0`のようにバージョン番号をpathで指定します。

### 型について

なんと言っても、型を定義できることがTypeScriptの強みです。
型の間違いは、VSCodeでリアルタイムにチェックが働きます。
![スクリーンショット 2022-05-08 10.13.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/1a51073b-9dff-0478-56ce-ebde2375da12.png)
[TypeScript Deep Dive 日本語版](https://typescript-jp.gitbook.io/deep-dive/) > [TypeScriptの型システム](https://typescript-jp.gitbook.io/deep-dive/type-system)

# さらに詳細な情報を得られるお勧めリンク集

## Deno

https://deno.land/

https://zenn.dev/uki00a/books/effective-deno

## TypeScript

https://typescript-jp.gitbook.io/deep-dive/

# あとがき

後続の「スタブ・サーバーを作ってみる編」もぜひ。。。

https://qiita.com/y-some/items/49181842059c9fe7d41b