aspida/frourioは何をしているのか。極小プロジェクトに導入して理解するハンズオン
極小というのは local minimum という意味です。
サーバーはたった1ファイルの index.ts のみ、フロントエンドは create-vite のテンプレートの状態から解説します。とても簡単なので是非見ていってください。
Aspida と Frourio
フロントエンドとHTTP APIサーバーを用意 (スキップ可能)
ここは本質ではないため、 git clone https://github.com/frouriojs/learn-aspida-frourio-handson としてしまっても問題ない。ただし、こちらは pnpm を用いているため、事前に npm i -g pnpm などでインストールしておく必要がある。
clone してすすめるかたは #まずはAspidaを導入 まで飛ばしていただいて構わない。
さて、以下はテンプレートと同様に pnpm を使う。これは主に pnpm が提供するモノレポサポートの恩恵を得るためだが、 npm でも問題ない。(その場合は適宜読み替えてほしい)
フロントエンドは create-vite を使って簡単なものを用意する。
mkdir learn-frourio
cd learn-frourio
pnpm init -y
your_editor pnpm-workspace.yaml
pnpm-workspace.yaml は以下のように編集。
packages:
- 'front'
- 'server'
これで learn-frourio/front/ と learn-frourio/server/ がモノレポの対象になる。
次に vite によるフロントエンドをテンプレートから生成。
pnpm create vite -- --template react-ts front
pnpm i
続いて、サーバーを用意。たったの1ファイルなので安心してほしい。
mkdir server
cd server
pnpm init -y
pnpm i fastify fastify-cors
pnpm i typescript ts-node @types/node -w # こちらはルートに追加している
your_editor index.ts
index.ts を以下のように編集する。
import FastifyCors from 'fastify-cors'
import Fastify from 'fastify'
const fastify = Fastify();
fastify.register(FastifyCors, {})
fastify.get('/', (req, reply) => {
reply.send({hello: 'world'})
})
fastify.listen(8888, '0.0.0.0')
pnpm exec ts-node index.ts
# 別のターミナルで
curl localhost:8888
JSONが正しくみれれば成功。
ルーティングが一個だとわかりにくいので、もう一つ実装しておきます。
fastify.get('/', (req, reply) => {
reply.send({hello: 'world'})
})
+ fastify.get('/hi', (req, reply) => {
+ reply.send({hello: 'how are you?'})
+ })
fastify.listen(8888)
また、ブラウザコンソールで fetch("http://localhost:8888") をしておいて到達を確認しておいてください。
まずはAspidaを導入
さて、フロントエンドから先程作ったサーバーにアクセスしたいです。fetch("http://localhost:8888") のように書いていってもいいですが、型がほしいです。そこでAspidaを導入します。
cd ../front
pnpm i aspida @aspida/fetch
開発サーバーを立ち上げておいても良いです。
pnpm run dev -- --host
デフォルトで http://localhost:3000 にアクセスすれば見れると思います。
まず型定義を書きます。 mkdir -p api/hi として、 api/index.ts と api/hi/index.ts を以下の内容に編集します。
export type Methods = {
get: {
resBody: {
hello: string;
}
}
}
pnpm exec aspida
api/$api.ts が生成されます。これには、先程の index.ts の内容が集約されています。
touch ./src/api-client.ts
your_editor ./src/api-client.ts
front/src/api-client.ts を以下のように編集します。
import $api from '../api/$api';
import aspida from '@aspida/fetch';
export const api = $api(aspida(undefined, {
baseURL: 'http://localhost:8888',
}));
aspida に渡した undefined はデフォルト引数( global の fetch オブジェクト)を使わせるためです。fetchを渡しても良いです。
これだけで完了しました。 front/src/api-client.ts の下の方で api. などと書くとパスが補完されると思います。
front/src/App.tsx を以下のように編集してください。
+ import {api} from './api-client'
function App() {
const [count, setCount] = useState(0)
+ const [greeting, setGreeting] = useState('loading...')
+ useEffect(() => {
+ api.$get().then(res => {
+ setGreeting(res.hello);
+ })
}, [])
これで greeting ステートにfetchした値が入ります。
あとはすきなところに表示しましょう。
- <p>Hello Vite + React!</p>
+ <p>{greeting}</p>
サーバーにFrourioを導入
さて、今度はサーバーも先程定義した api/.../index.ts の型を保証した状態で開発したいです。
そこで次は frourio を導入します。 aspida も必要になるので aspida も入れておきます。 (フロントからは消しても良いです、 @aspida/fetch はランタイムとして必要なので残してください。)
cd ../server
pnpm i -D frourio aspida
mkdir api
pnpm exec frourio --watch
# 別のターミナルで
mkdir api/hi
さて、frourio を実行すると$server.tsが、mkdir を実行すると、api/ 配下に index.ts, controller.ts, $relay.ts が出現したと思います。
index.ts は先程の aspida で使ったような型定義を書く場所です。先程のfront/api/.../内の型定義をこちらに書き写しておいてください。その後消して問題ないです。
controller.ts にはサーバーの実装を書きます。次のように書き換えます。
import {defineController} from './$relay'
export default defineController(() => ({
- get: () => (...)
+ get: () => ({status: 200, body: {hello: 'world'}})
}))
先程書いた index.ts の型に違反する形で書くとエラーになります。
api/hi/controller.ts のほうも書き換えておいてください。
最後にaspidaを実行します。これで先程と同じように api/$api.ts が生成されます。
frourio --watch の方は終了しても問題ありません。
index.ts で frourio で書いた定義を使うように書き換えます。
import Fastify from 'fastify'
+ import $server from './$server';
const fastify = Fastify();
fastify.register(FastifyCors, {})
- fastify.get('/', (req, reply) => {
- reply.send({hello: 'world'})
- })
-
- fastify.get('/hi', (req, reply) => {
- reply.send({hello: 'how are you?'})
- })
+ $server(fastify);
fastify.listen(8888, '0.0.0.0')
最後に、front/src/api-client.ts を書き換えます。APIの型定義をおいている位置が変わったためです。
-import $api from '../api/$api';
+import $api from '../../server/api/$api';
さて、 pnpm exec ts-node index.ts を再起動します。
フロントエンドからみて問題なく動いていれば成功です。
PR: この内容で 【frourio LT会】TypeScriptでフルスタックWeb開発!初心者歓迎! に登壇する予定です。そこではさらに、各ファイルのより詳しい説明や frourio の嬉しさについて話すつもりdす。
Discussion