2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

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

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

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にアクセスしたとき

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が返る

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

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

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

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

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

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

Fsharp

Program.fs
open Saturn
open V1Router

let port = 3004


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

run app

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

v1Router.fs
module V1Router

open usersRouter
open Saturn

let appRouter = router { forward "/v1/users" usersRouter }

これまた特筆すべき点はございませんな。
強いて言えばforwardかな。これに気づくまで少しはまった。

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をいろいろ見て勉強しなさいってとこですな。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?