TypeScript でWebアプリ開発

# はじめに
TypeScript はフロントエンド開発とバックエンド開発の両方で利用することができます。
この記事では、フロントエンドとバックエンドの両方で TypeScript を利用したToDoリストのWebアプリを開発します。

# 開発環境
- Windows11
- VSCode
- TypeScript 4.9.5
- React 18.2.0
- React Router 6.10.0
- Vite 4.1.0

# プロジェクトフォルダの作成
まずWebアプリ用に `todo-app` というフォルダを作成し、その中に `frontend` と `backend` というフォルダを作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/2da364e6-128e-7947-711a-84c95d394d86.png)

# バックエンド開発
まずはバックエンドを開発してきます。

## package.jsonの作成
Node.jsでのアプリ開発を行うため、以下のコマンドで `package.json` を作成します。
```
yarn init -y
```
以下の `package.json` ファイルが作成されます。
```package.json
{
  "name": "backend",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}
```

## tsconfig.jsonの作成
TypeScriptを利用できるようにするため、以下2つのパッケージをインストールします。
- typescript
- @types/node

```
yarn add -D typescript @types/node
```

`yarn.lock` や `node_modules` が新たに作成され、`package.json` にも以下の値が追加されます。
```package.json
"devDependencies": {
  "@types/node": "^18.15.5",
  "typescript": "^5.0.2"
}
```

次に `tsconfig.json` を作成するため、以下のコマンドを実行します。
```
yarn tsc --init
```

作成された `tsconfig.json` 内の `target` を `"esnext"` に変更します。

## Expressの導入
Node.jsのフレームワークであるExpressを導入します。
以下のコマンドでExpressをインストールします。
```
yarn add express
```
`package.json` に以下の値が追加されます。
```package.json
"dependencies": {
  "express": "^4.18.2"
}
```

TypeScriptを使うため、以下のコマンドも実行します。
```
yarn add -D @types/express
```
`package.json` の `"devDependencies"` に `"@types/express": "^4.17.17"` が追加されます。

次にバックエンド開発のメインのファイルとなる `server.ts` というファイルを `backend` フォルダ直下に作成します。そして、`package.json` の `"main"` を `"server.js"` に書き換えます。
`server.ts` では、Express を利用できるように以下のコードを書きます。
```backend/server.ts
import express, { Express } from "express";

const app: Express = express();
```

## Expressの起動
実際にExpressを起動して、動作確認をしてみます。
確認用にログを出力するコードを書きます。

```backend/server.ts
import express, { Express, Request, Response } from "express";

const app: Express = express();
const port = 3001;

app.get("/", (req: Request, res: Response) => {
  res.send("Hello World!");
});

app.listen(port, () => {
  console.log(`listening on port ${port}`);
});
```
次にTypeScriptをNode.jsで実行するために **_ts-node_** をインストールします。
```
yarn add -D ts-node
```
そして、ローカルサーバーを起動します。
```
yarn ts-node server.ts
```
ローカルサーバーを起動することができ、ログも表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/84b14836-8dd8-346f-ff94-5ac9bd4c5c19.png)
また、ブラウザで http://localhost:3001 にアクセスすると、「Hello World!」と表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/9303df2d-9cb2-3f13-0bf5-bd37b21536e9.png)

:::note
**_ts-node_** をインストールせずにローカルサーバーを起動すると、エラーが出てしまいます。
```
node server.ts
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/4de4302c-183d-284e-3358-7aa5e5be1e41.png)
:::

## CROS設定
次に CORS[^1] 対応のため、[CORS](https://expressjs.com/en/resources/middleware/cors.html) というミドルウェアをインストールします。TypeScript対応のため、[@types/cors](https://www.npmjs.com/package/@types/cors) も一緒にインストールします。

```
yarn add cors @types/cors
```
次にCORSをインポートして全てのAPIをCORS許可します。
```backend/server.ts
import express, { Express, Request, Response } from "express";
import cors from "cors";

const app: Express = express();
const port = 3001;

app.use(cors());
```

## ToDoリスト取得機能の実装
ToDoリストの取得機能を実装します。

まずはToDoの型を定義します。
```types/index.ts
export type Todo = { id: number; title: string; completed: boolean };
```
次に `"/todo-list"` にアクセスすると、ToDoリストを返すようにします。
```backend/server.ts
import express, { Express, Request, Response } from "express";
import { Todo } from "../types";
import cors from "cors";

const app: Express = express();
const port = 3001;

app.use(cors());

const sampleData = [
  { id: 1, title: "牛乳を買う", completed: false },
  { id: 2, title: "洗濯をする", completed: true },
  { id: 3, title: "請求書を支払う", completed: false },
];

let todoList: Todo[] = sampleData;

app.get("/", (req: Request, res: Response) => {
  res.send("Hello World!");
});

app.get("/todo-list", (req: Request, res: Response) => {
  res.json(todoList);
});

app.listen(port, () => {
  console.log(`listening on port ${port}`);
});

```
動作確認をします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/d9dade04-0257-d1bb-1743-4e50169900f9.png)

これでバックエンドの開発は完了です！

# フロントエンド開発
次はフロントエンドを開発していきます。

## Reactプロジェクトの作成
以下のコマンドでプロジェクトを作成します。
```
yarn create vite . --template react-ts
```
以下のコマンドを実行すると、ローカルサーバーが起動します。
```
yarn
yarn dev
```
ローカルサーバーが起動したことを確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/ba183733-1af0-aa08-fc4b-0c765de6e6e0.png)

:::note
`vite.config.ts` でポートの指定やローカルサーバー起動時にブラウザを自動起動する設定を追加することができます。

```vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    port: 3000,
    open: true,
  },
});
```
:::

## ルーティング作成
次にルーティングを作成します。
今回は [**_React Router_**](https://reactrouter.com/en/main) を利用します。
まずは、以下のコマンドでインストールします。

```
yarn add react-router-dom
```

次にフロントエンドのコードにルーターを追加します。

```frontend/src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";

const router = createBrowserRouter([
  { path: "/", element: <div>ToDoリスト</div> },
]);

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);
```
動作確認をします。ルートにアクセスすると、「ToDoリスト」と表示されることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/a7087a8d-d52e-f1e0-cf8c-853292a5f872.png)

## ToDoリストコンポーネント作成
次は `<div>ToDoリスト</div>` と書いていた箇所をコンポーネント化します。
```frontend/src/routes/Home.tsx
import { FC } from "react";

export const Home: FC = () => {
  return (
    <div>
      <h1>ToDoリスト</h1>
    </div>
  );
};
```
```frontend/src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Home } from "./routes/Home";

const router = createBrowserRouter([{ path: "/", element: <Home /> }]);

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);
```

## バックエンドへアクセス
次はいよいよ先に作成したバックエンド（APIサーバー）へアクセスする機能を実装します。今回は [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) と [SWR](https://swr.vercel.app/) を利用して実装します。

まずは、SWR をインストールします。
```
yarn add swr
```

次に `fetch` をSWRで利用できるようにするため、`fetch` をラップした `fetcher` 関数を作成します。

```frontend/src/api/index.ts
export const fetcher = async (path: string) => {
  try {
    const res = await fetch("http://localhost:3001" + path);
    return res.json();
  } catch (error) {
    alert(error);
  }
};
```
次にSWRを利用して、ToDoリスト取得できるようにします。
```frontend/src/api/index.ts
export const useTodoList = () => useSWR<Todo[]>("/todo-list", fetcher);
```
そして、`Home` コンポーネント内で `useTodoList` をインポートしてAPIを呼び出し、その結果をレンダリングします。
```frontend/src/routes/Home.tsx
import { FC } from "react";
import { useTodoList } from "../api";

export const Home: FC = () => {
  const { data: todoList, error, isLoading } = useTodoList();

  if (error) return <div>failed to load</div>;
  if (isLoading) return <div>loading...</div>;

  return (
    <div>
      <h1>ToDoリスト</h1>
      <ul>
        {todoList?.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              id={String(todo.id)}
              checked={todo.completed}
            />
            <label htmlFor={String(todo.id)}>{todo.title}</label>
          </li>
        ))}
      </ul>
    </div>
  );
};
```
動作確認をします。ToDoリストが取得できました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/a7556507-7ac3-bcc7-5690-d71b49e52695.png)

# 最後に
今回はフロントエンドとバックエンドの両方でTypeScriptを利用したToDoリストのWebアプリを開発しました。両方とも同じ言語だと実装しやすいですね。

# 参考
- [CORSを絶対に理解する](https://zenn.dev/syo_yamamoto/articles/445ce152f05b02)
- [Express](https://expressjs.com/)
- [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
- [SWR](https://swr.vercel.app/)
- [ts-node](https://typestrong.org/ts-node/)
- [Tutorial v6.10.0 | React Router](https://reactrouter.com/en/main/start/tutorial)
- [【TypeScriptあり】Node.jsとReactでWebアプリを作る【Express使います】](https://ralacode.com/blog/post/create-nodejs-react-app-with-typescript/)
- [yarn init --yes/-y](https://chore-update--yarnpkg.netlify.app/ja/docs/cli/init#toc-yarn-init-yes-y)
- [@types/cors - npm](https://www.npmjs.com/package/@types/cors)

[^1]:CORS の説明は、[こちら](https://zenn.dev/syo_yamamoto/articles/445ce152f05b02)の記事がとてもわかりやすかったです。