Fsharp初心者です。
普段、Expressで書いているAPIをFsharpで書くとどうなるのか試してみました。
github copilotとbing AIくんに頼りつつ、結局自分でSaturnのドキュメント読みました。
TypeScriptの例
/* 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
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にアクセスしたとき
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
open Saturn
open V1Router
let port = 3004
let app =
application {
use_router appRouter
url $"http://localhost:{port}/"
}
run app
特筆すべき点は特にないかなぁ……。
module V1Router
open usersRouter
open Saturn
let appRouter = router { forward "/v1/users" usersRouter }
これまた特筆すべき点はございませんな。
強いて言えばforwardかな。これに気づくまで少しはまった。
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をいろいろ見て勉強しなさいってとこですな。