TypeScriptでexpressを使ってAPIサーバを作る

初期設定

とりえあえず、TypeScriptとexpressとexpressの型定義をインストールする。

npm i express
npm i -D typescript
npm i -D @types/express

はじめの一歩

expressを用いたWebアプリでは、expressのオブジェクトを作り、そのオブジェクトにどのメソッドでどのURLに対してどんなリクエストが来たらどんなレスポンスを返すのか、をひたすら実装していく。

import express from "express";
const app = express();
const port: number = 3000;
app.get("/", (Req: express.Request, res: express.Response) => {
  res.json({ message: "ok" });
  // res.status(500).json({ message: "error"}) // エラーレスポンスはこんな感じ
});
app.listen(port, () => console.log("ok, port =", port));

パスパラメータ

パスを指定する場所で、たとえば:user_idのように、コロンに続けて変数名を指定する。例として、ユーザIDを受け取るような状況を考える。

import express from "express";
const app = express();
const port: number = 3000;
app.get("/:user_id", (req: express.Request, res: express.Response) => {
  res.json({ message: "ok", user_id: req.params.user_id });
});
app.listen(port, () => console.log("ok, port =", port));

このように、req.paramsに続けて受け取った変数名を書くことで、パスを用いて渡された変数の値を取得することができるので、例えば~/1などにアクセスすることができるようになる。

POSTされたJSONの受け取り方

const port: number = 3000;
import express from "express";
const app = express();
import bodyParser from "body-parser";
app.use(
  bodyParser.urlencoded({
    extended: true,
  })
);
app.use(bodyParser.json());
app.post("/:user_id", (req: express.Request, res: express.Response) => {
  res.json({ message: "ok", user_id: req.params.user_id, data: req.body });
});
app.listen(port, () => console.log("ok, port =", port));

ルート

ルートを使うとあるパス配下の処理をまとめて扱うことができる。またルート単位に処理を割り当てて、そのルート配下であればリクエストがくるたびに割り当てた処理を実行させることができる。 例えば、/api/v0云々というパスに対するリクエストに来たら同じ処理をさせる場合は、以下のようなかたちになる。

import express from "express";
const app = express();
const port: number = 3000;

const apiRouter = express.Router();  // ルータを作る
app.use("/api/v0", apiRouter);  // ルータをパスに割り当てる
apiRouter.use(  // このルータで使うミドルウェアを設定する。
  async (
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) => {
    // 例えばログインしている場合はユーザ情報を取得するとかその手のことをやる。
    next(); // これを呼ぶと次の関数が実行される、呼ばないと実行されない。
  }
);
apiRouter.get("/", (req: express.Request, res: express.Response) => {
  res.json({ message: "ok" });
});
app.listen(port, () => console.log("ok, port =", port));

なお、このように割り当てる処理のことをミドルウェアと呼ぶ。

JWT

APIサーバでの認証方法のひとつとしてトークンを用いるという方式があり、トークンの仕様のひとつとしてIETFによりRFC7519で定められたJWTがある。

https://datatracker.ietf.org/doc/html/rfc7519

これを使えばどうにかできるだろう。

https://github.com/auth0/node-jsonwebtoken

CORS

APIサーバなので、別のサーバが配信したHTMLからたたかれることを想定しなくてはならないこともあるだろう。

import express from "express";
import cors from "cors";
const app = express();
app.use(cors());

入力値のバリデーション

express-validatorを使えばよさそう。

https://express-validator.github.io/docs/

しめのあいさつ

なにか書き足したほうがよさそうなことを思いついたら順次追記していきます。

TypeScriptの基本

TypeScriptとは

JavaScriptの世界に静的型付けを持ち込んだイメージ。JavaScriptのゆるふわ感を緩和してくれる。TypeScriptでかいたソースコードコンパイラ(トランスパイラ)で変換するとJavaScriptのコードが生成されるので、そのソースコードをnode.jsなどで実行する。なお、Denoを使うとTypeScriptのソースを直接実行することができる。

以下、node.jsで触ってみる。

準備

適当なディレクトリを作ってからそのディレクトリ内に移動して、初期化。

npm init -y

-yをつけないとインタラクティブにいろいろ聞かれるが、-yをつけるとすべてデフォルト値で一気に設定ファイル(package.json)を生成してくれる。

TypeScriptのインストール

このプロジェクトにTypeScriptをインストールする。

npmでのパッケージのインストールには、

npm install パッケージ名

と入力する。installはiと一文字だけ入力することでも同じ結果になる。つまりこんな感じ。

npm i パッケージ名

また開発専用のパッケージをインストールする場合は、オプションで--save-devあるいは-Dオプションを付与する。-Dのほうを使うなら、こんな感じ。

npm i -D パッケージ名

TypeScriptは開発中しか使わないので、このオプションを付与してインストールする。

npm i -D typescript

これにより、./node_modules/.bin/tscという実行ファイルがインストールされる。たぶんTypeScript Compilerの略。

TypeScriptの設定ファイルを作る

TypeScriptの設定値はやまほどあるが、以下のコマンドで丁寧な説明がコメントで記載されている設定ファイルが生成されるので、これを編集すればよい。

./node_modules/.bin/tsc --init

コマンドを実行したディレクトリに設定ファイルであるtsconfig.jsonが生成されているはず。

例えば、こんな行がある。

    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */

コメントに、出力先のフォルダを指定するんだよ的なことが書いてあるので、コメントアウトを外してからJavaScriptのコードを出力するフォルダ名を書く。

    // "outDir": "./dist",

型定義の書き方

こんな感じ。変数名のうしろにコロンを書いて、さらにその後ろに型の名前を書く。

let n: number;
let s: string;
let a1: number[]; // 配列
let t: [number, string, string]; // これはタプル

オブジェクトの場合、オブジェクトリテラルを使う。

let c: {d: number} ={
  d: 1234;
}

オブジェクトリテラルにおいて、読み取り専用の変数にはreadonly、あってもなくてもよい変数には?を付与する。

let e: {
  readonly f: string;
  g?: number;
}

エイリアスを使うとオブジェクトを生成する都度オブジェクトリテラルを書く必要がなくなり簡潔なコードにすることができる。

type A = {
  h: string;
  i: number;
}

関数での型指定

関数の引数と戻り値ならこう

function sonomamakaesu(a : number) : number {
  return a;
}

型定義同様、?を使って省略できることを示すことができる。

function f(a: number, b?: number) {
  console.log(a);
  console.log(b); // 省略された場合はundefinedと出力される
}

値を書くことで省略時のデフォルト値を指定することができる。

function f(s = "default parameter") {
  console.log(s);
}

レストパラメータを使えば可変長引数も実現できる。

function f(...nums: number[]) {
  nums.map((x: number) => {
    console.log(x);
  });
}

関数自身がどんな型なのかを指定することもできる。

function f0(a: number): number {
  return a * a;
}

function f1(f: (x: number) => number): void {  // ここの引数に注目
  console.log(f(10));
}

f1(f0);

コーディング時に型を特定できない場合はジェネリック型を使うことができる。 始まりのカッコの直前に<と>でくくって型を指し示す文字列を書いておいて、関数定義内ではその文字列を型を記すべき箇所に書いていく。

function f<T>(x: T) {
  console.log(x);
}

f(1); // 省略することもできるし、
f<string>("asdf");  // 明示することもできる。

はじめてのJSX

ReactではHTMLを組み立てる上でJSXというJavaScriptの拡張構文を用いる。

関数コンポーネントを使う場合、そのコンポーネントを表す関数でJSXをreturnする。

例えばこんな感じ

export const Abc = () => {
    return <p>abac</p>;
}

JSXのなかでは{と}でくくることでJavaScriptの式を書くことができる。

条件分岐は3項演算子を使う。

import { useState } from "react";
export const Bbs = () => {
  const [flag] = useState<boolean>(true);

  return <div>{flag ? <></> : <></>}</div>;
};

ループにはmapを使う。returnを忘れないように。

return (<ul> {
  ary.map((item: string, key: number)=>{
     return <li key={key}>{ item }</li>
   });}</ul>);

ReactRouterでSwitchが使えなくなった

react-router-domがバージョン6になってから従来のSwitchが使えなくなったが、代わりにRoutesを使えばよい。

function App() {
  return(
    <BrowserRouter>
      <Routes>
        <Route path="login" element={<Login />} />
        <Route path="about" element={<About />} />
        <Route path="/" element={<TopPage/>} />
      </Routes>
    </BrowserRouter>
  );
}

ちょっとの修正で対処できるのにあえてバージョン5を使い続ける必要はない。

TypeScriptを使うReactのプロジェクトを作る

事前にnpmのインストールが必要

npx create-react-app プロジェクト名 --template typescript

あとから変更するのはめちゃくちゃ大変らしいのではじめっからTypeScriptを使うぞと心に誓っておいたほうがよさそうだ。