TypeScriptとFsharpで簡単なAPIを作り比べてみた

Fsharp初心者です。  
普段、Expressで書いているAPIをFsharpで書くとどうなるのか試してみました。  
github copilotとbing AIくんに頼りつつ、結局自分でSaturnのドキュメント読みました。

## TypeScriptの例

```TypeScript:main.ts
/* expressのサーバーをつくる */
import express from 'express';
import { createServer } from 'http';
import { v1Router } from './routes/v1/v1Router';

/* app */
export const app = express();
const server = createServer(app);

/* ポート番号 */
const port = 3003;

/* ルーティング */

/* v1Routing */
v1Router(app);

app.get('/', (req, res) => {
  res.send('Hello World!');
});

/* サーバーを起動 */
server.listen(port, () => {
  console.log(`http://localhost:${port}`);
});

```

ごくごく普通のExpress。本当は適宜corsとか足してあげるとよろしいでしょうな。


お次はrouter
```TypeScript:v1Router.ts
import type { Express } from 'express';
import { usersRouter } from './users/users';

/* appによってrouterをつくる */
export const v1Router = (app: Express): void => {
  app.use('/v1/users', usersRouter);
};

```
今回はルート1個だけなので要らないが、まあ今後増えたとき用。


最後に/v1/userにアクセスしたとき

```TypeScript:users.ts
import { Router } from 'express';
import { isNil } from 'ramda';
import { User } from '../../../types/type';

export const usersRouter = Router();

const users: User[] = [
  { id: 1, name: 'TANAKA' },
  { id: 2, name: 'YAMADA' }
];

/* 全ユーザーの配列が返る */

type UsersSuccess = {
  type: 'SUCCESS';
  users: User[];
};

usersRouter.get('/', (req, res) => {
  const result: UsersSuccess = { type: 'SUCCESS', users };
  res.send(result);
});

type UserSuccess = {
  type: 'SUCCESS';
  user: User;
};
type UserError = {
  type: 'ERROR';
  message: string;
};

/* idをgetしたとき、そのidに合致したJSONが返る */
usersRouter.get('/:id', (req, res) => {
  const id = Number(req.params.id); //copilotが勝手にNumberにしたが、仕事ならsafeParseIntとかするな……。
  const user = users.find((x) => x.id === id);
  if (isNil(user)) {
    const errorResult: UserError = { type: 'ERROR', message: 'User Not Found' };
    res.send(errorResult);
    return;
  }
  const result: UserSuccess = { type: 'SUCCESS', user };
  res.send(result);
});
```

コメントしてるし解説するまでもないけれど
http://localhost:3003/v1/users
ここにアクセスすると下記のようなJSONが返る

```JSON
{
  "type": "SUCCESS",
  "users": [
    {
      "id": 1,
      "name": "TANAKA"
    },
    {
      "id": 2,
      "name": "YAMADA"
    }
  ]
}
```


http://localhost:3003/v1/users/1
ここにアクセスすると下記のようなJSONが返る

```JSON
{
  "type": "SUCCESS",
  "user": {
    "id": 1,
    "name": "TANAKA"
  }
}
```

http://localhost:3003/v1/users/3
ここにアクセスすると、ID3のユーザーは存在しないため、下記のようなJSONが返る

```JSON
{
  "type": "ERROR",
  "message": "User Not Found"
}
```

以上がTS側。expressでの実装。

## Fsharp

```fsharp:Program.fs
open Saturn
open V1Router

let port = 3004


let app =
    application {
        use_router appRouter
        url $"http://localhost:{port}/"
    }

run app

```
特筆すべき点は特にないかなぁ……。

```fsharp:v1Router.fs
module V1Router

open usersRouter
open Saturn

let appRouter = router { forward "/v1/users" usersRouter }
```
これまた特筆すべき点はございませんな。  
強いて言えばforwardかな。これに気づくまで少しはまった。  

```fsharp:usersRouter
module usersRouter

open Saturn
open Giraffe

(* ユーザーのtypeを作成する *)
type User = { id: int; name: string }

(* ユーザーのリストを作成する *)
let users =
    [ { id = 1; name = "John" }
      { id = 2; name = "Jane" } ]


(* Listからfindして、optionで返す関数 *)
let findUserByIdOption id =
    List.tryFind (fun user -> user.id = id) users

type userResult = { ``type``: string; users: User list }

let userResult users =
    json { ``type`` = "SUCCESS"; users = users }


type ResultSuccess = { ``type``: string; user: User }
type ResultError = { ``type``: string; message: string }

type ApiResult =
    | ResultSuccess of ResultSuccess
    | ResultError of ResultError

(* ResultSuccessをjsonで返す関数 *)
let getResultSuccess user =
    json { ``type`` = "SUCCESS"; user = user }

(* ResultErrorをjsonで返す関数 *)
let getResultError message =
    json
        { ``type`` = "ERROR"
          message = message }


(* idをもとにuserを返す関数 *)
let getUser id =
    match findUserByIdOption id with
    (* SomeのときはResultSuccessを返す *)
    | Some user -> getResultSuccess user
    (* NoneのときはResultErrorを返す *)
    | None -> getResultError (sprintf "User with id %i not found" id)


let usersRouter =
    router {
        get "" (userResult users)
        getf "/%i" (fun id -> (getUser id))
    }
```

ここはいろいろハマりポイントがあった。
まず、 get ""で/v1/usersになること。
expressだと app.get("/")で/v1/usersになるんだな。ちゃんとドキュメント読もうね、というお話。

次に```getf /%i```を勝手にcopilotが%dにしたのでint32と64ではまった。
TypeScriptを書いていると全部numberなので意識しないとダメですね。

あと、Recordのkeyに普通に```type```というワードを使おうとしてはまった。  
予約語なのね。「バッククオーテーション2個で囲わないとだめっぽい。

他にもmysqlにknexで接続したりとかfsharp.dapper使ったりとかしたいけれど、また今度だな。
typescriptだとtypesとかにtypeだけ書き出したりするけど、そういうのってfsharpでもしていいんかいのぅ。
まあちゃんとFsharpのOSSをいろいろ見て勉強しなさいってとこですな。