8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【開発効率化】型安全なURLをroutopiaで管理する

Last updated at Posted at 2025-08-28

はじめに

OpenAPIProtobufを導入しているプロジェクトだと、パスとレスポンスの型の管理が比較的容易化と思います。

一方で、そうでないプロジェクトでは、リクエスト毎にパスを手打ちして、レスポンスの型も専用の関数で紐づけて扱うことがあると思います。

今回は、リクエストに使うパスの管理を効率化するための方法に着目して説明します(本来はレスポンスの型の紐づけも併せて効率化したかった)。

  • 対象読者
    • フロントエンド・バックエンド開発に携わる開発者

なお、Next.jsでの内部パス管理のようなパターンは考えていません。そういったユースケースの場合はpathpida↓をオススメします。

この記事の主役: routopia

routopiaは型安全なURLビルダーです(開発者による記事↓)。

このライブラリはURLの構築にのみ関心を持っており、Web APIリクエストのためのパスだけでなく、SPAの画面遷移に使うURLの構築にも使うことができます。

GithubのREADMEから直接ユースケースを引用しますが、次のようにURLの構築が可能です。

import { routes, empty, type } from 'routopia';

const myRoutes = routes({
  "/users": {
    get: empty,
    post: empty,
  },
  "/path/[id]": {
    get: {
      params: {
        id: type as number,
      },
      queries: {
        q: type as string | undefined,
      },
    },
  },
});

これにより、次のような形でURLを安全に取得することができます。

myRoutes["/users"].get();
myRoutes["/users"].post();
// => "/users"

myRoutes["/path/[id]"].get({ params: { id: 123 } });
// => "/path/123"

myRoutes["/path/[id]"].get({ params: { id: 123 }, queries: { q: "query" }  });
// => "/path/123?q=query"

この記事で話したいことはもう8割紹介し終えていますが、これを使ってURLのタイポを減らし、パラメータも型安全にしよう!という魂胆です。

実際にURLを構築してみる

レポジトリのベストプラクティスで紹介されている通り、API_BASE_URLを定義し、routes関数をラップします。

import { routes, empty, type, ExpectedSchema } from 'routopia';

const API_BASE_URL = "https://api.example.com";

export function createMyApiRoutes<T extends ExpectedSchema<T>>(schema: T) {
  return routes(API_BASE_URL, schema);
}

export const schema = { empty, type };
import { createMyApiRoutes, schema } from "../builder/createMyApiRoutes";

export const userRoutes = createMyApiRoutes({
  "/users": {
    get: schema.empty,
  },
  "/users/[id]": {
    get: {
      params: {
        id: schema.type as number,
      },
    },
  },
});

この状態で次のようにパスを呼び出してみます。

import { userRoutes } from './api/routes/userRoutes'

function App() {
  const pathWithGet = userRoutes["/users"].get();
  const pathWithPost = userRoutes["/users"].post();
  const pathWithId = userRoutes["/users/[id]"].get({ params: { id: 123 } });
  //"/users/123"

  const notExistingPath = userRoutes["/user"].get();
  const pathWithQuery = userRoutes["/users/[id]"].get({ params: { id: 123 }, queries: { q: "abc", p: "1234" } })
  // => "/users/123?q=abc#anchor1"

    return (
        <></>
    )
}

export default App

すると、次のようにエディタ上で存在しないパスや定義されていないメソッドについてはエラー表示されるようになります。

パスの例.png

さらに、定義された変数の値も次のようにホバー表示されます。

スクリーンショット 2025-08-06 185829.png

スクリーンショット 2025-08-06 185845.png

開発者体験も良いですね!

さらに、パラメータをconstアサーションで定義しなおしてみると、その値もちゃんと表示されます。

スクリーンショット 2025-08-06 190251.png

上記ではフロントエンドでのパスの参照を考えていますが、言語が同じであれば、バックエンドでのパスの定義も共通化できると思います。

まとめ

OpenAPIProtobufの導入されていないプロジェクトでは、レスポンスの型とパスの管理が必要でありバグの発生源になりやすいと思います。

そこで、そうしたプロジェクトでもパスとレスポンスを紐づけ、かつ一か所で管理できるようになると良いな~と考えていました。

レスポンスの型との紐づけまではできていませんが、今回はひとまずパスの管理コストを下げるという観点でroutopiaを紹介しました。

8
4
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
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?