Skip to content

ルーティング

Koriアプリケーションでルートを定義し、さまざまなURLパターンを処理する方法を学びます。KoriはHonoのSmartRouterをラップしているため、ルーティング動作は基本的にHonoと同じです。

基本的なルーティング

Koriは一般的なHTTP動詞のための直感的なメソッドを提供します:

typescript
const app = createKori();

// GETルート
app.get('/users', (ctx) => {
  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ];
  return ctx.res.json({ users });
});

// POSTルート
app.post('/users', async (ctx) => {
  const body = await ctx.req.bodyJson();
  const newUser = createUser(body);
  return ctx.res.json({ user: newUser });
});

// PUTルート
app.put('/users/:id', async (ctx) => {
  const { id } = ctx.req.pathParams();
  const body = await ctx.req.bodyJson();
  const user = await updateUser(id, body);
  return ctx.res.json({ user });
});

// DELETEルート
app.delete('/users/:id', async (ctx) => {
  const { id } = ctx.req.pathParams();
  await deleteUser(id);
  return ctx.res.empty();
});

// PATCHルート
app.patch('/users/:id', async (ctx) => {
  const { id } = ctx.req.pathParams();
  const body = await ctx.req.bodyJson();
  const user = await partialUpdateUser(id, body);
  return ctx.res.json({ user });
});

Koriは.head().options()メソッドもサポートしており、HEADとOPTIONSリクエストを処理できます。

パスパラメータ

Koriは、より良いIDE支援のためにコンパイル時にパスパラメータ名を自動的に推論します:

typescript
// 単一パラメータ - TypeScriptは{ id: string }を推論
app.get('/users/:id', (ctx) => {
  // ctx.req.pathParams()は{ id: string }として型付け
  const { id } = ctx.req.pathParams(); // idはstring
  return ctx.res.json({ userId: id });
});

// 複数パラメータ - 自動的に推論
app.get('/users/:userId/posts/:postId', (ctx) => {
  // ctx.req.pathParams()は{ userId: string, postId: string }として型付け
  const { userId, postId } = ctx.req.pathParams();
  return ctx.res.json({
    userId, // string
    postId, // string
    message: `Post ${postId} by user ${userId}`,
  });
});

// オプションパラメータ(?を使用)
app.get('/search/:query/:page?', (ctx) => {
  // ctx.req.pathParams()は{ query: string, page?: string }として型付け
  const { query, page } = ctx.req.pathParams();
  const pageNumber = page ? parseInt(page) : 1;
  return ctx.res.json({ query, page: pageNumber });
});

// カスタム正規表現パターン
app.get('/files/:id{[0-9]+}', (ctx) => {
  // ctx.req.pathParams()は{ id: string }として型付け
  const { id } = ctx.req.pathParams(); // idは数字のみにマッチ
  return ctx.res.json({ fileId: id });
});

型推論は、コンパイル時にルート文字列を解析してパラメータ名を抽出することで動作し、タイプミスを防ぎ、IDE自動補完を改善します。

ルートグループと子ルート

createChild()を使用してモジュラーなルートグループを作成:

typescript
// API v1ルートを作成
const apiV1 = app.createChild({
  prefix: '/api/v1',
  configure: (k) =>
    k
      .get('/status', (ctx) => {
        return ctx.res.json({ version: 'v1', status: 'stable' });
      })
      .get('/users', (ctx) => {
        return ctx.res.json({ users: getUsersV1() });
      }),
});

// 異なる動作を持つAPI v2ルートを作成
const apiV2 = app.createChild({
  prefix: '/api/v2',
  configure: (k) =>
    k
      .onRequest((ctx) => {
        // v2のみのリクエストログ
        ctx.log().info('API v2 request', { path: ctx.req.url().pathname });
        return ctx; // コンテキストを返す必要がある
      })
      .get('/status', (ctx) => {
        return ctx.res.json({
          version: 'v2',
          status: 'beta',
          features: ['enhanced-validation', 'better-errors'],
        });
      })
      .get('/users', (ctx) => {
        return ctx.res.json({ users: getUsersV2() });
      }),
});

// 認証付きの管理者ルート
const adminRoutes = app.createChild({
  prefix: '/admin',
  configure: (k) =>
    k
      .onRequest((ctx) => {
        const token = ctx.req.header('authorization')?.replace('Bearer ', '');
        if (!token || !isValidAdminToken(token)) {
          throw new Error('Unauthorized');
        }
        return ctx.withReq({ isAdmin: true });
      })
      .onError((ctx, err) => {
        if (err instanceof Error && err.message === 'Unauthorized') {
          return ctx.res.unauthorized({ message: 'Admin access required' });
        }
      })
      .get('/dashboard', (ctx) => {
        return ctx.res.json({ dashboard: 'admin data' });
      }),
});

ルート登録順序

ルートは登録された順序でマッチします。より具体的なルートは、より一般的なものよりも前に定義する必要があります:

typescript
// ✅ 正しい:具体的なルートを最初に
app.get('/users/me', (ctx) => {
  return ctx.res.json({ user: getCurrentUser(ctx) });
});

app.get('/users/:id', (ctx) => {
  const { id } = ctx.req.pathParams();
  return ctx.res.json({ user: getUserById(id) });
});

// ❌ 不正:これは決してマッチしない
// app.get('/users/:id', handler);
// app.get('/users/me', handler); // 決してマッチしない!

Released under the MIT License.