メインコンテンツまでスキップ

ユーザーコンテキスト

Zeltはリクエストスコープの関数を提供し、認証済みユーザーにアクセス・管理できます。

コア関数

関数説明
setUser(user, roles)認証済みユーザーを設定(ミドルウェアで呼び出す)
currentUser()現在のユーザーを取得(未認証の場合はundefined
currentRoles()現在のユーザーのロールを取得(未認証の場合は[]

ユーザーの設定

認証情報を検証した後、認証ミドルウェアでsetUser()を呼び出します:

import type { FunctionMiddleware } from '@zeltjs/core';
import { setUser } from '@zeltjs/core';

export const authMiddleware: FunctionMiddleware = async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '');

if (token) {
const payload = await verifyToken(token);
setUser(
{ id: payload.sub, name: payload.name, email: payload.email },
payload.roles
);
}

await next();
};

パラメータ

  • user — 認証済みユーザーを表す任意のオブジェクト
  • roles — ロール文字列の配列(例: ['admin', 'user']

ユーザーへのアクセス

ルートハンドラー内で

currentUser()を使用して認証済みユーザーにアクセス:

import { Controller, Get, currentUser, currentRoles } from '@zeltjs/core';

@Controller('/profile')
class ProfileController {
@Get('/me')
me() {
const user = currentUser();
const roles = currentRoles();

if (!user) {
throw new HTTPException(401, { message: '認証されていません' });
}

return { user, roles, isAdmin: roles.includes('admin') };
}
}

デフォルトパラメータで

ハンドラーシグネチャを簡潔にするには、デフォルトパラメータを使用:

@Controller('/profile')
class ProfileController {
@Get('/me')
me(user = currentUser()) {
return user;
}
}

型安全なユーザーコンテキスト

デフォルトではcurrentUser()unknownを返します。RequestContextSchemaを拡張して完全な型安全性を得られます:

declare module '@zeltjs/core' {
interface RequestContextSchema {
user: {
id: string;
name: string;
email: string;
};
authRoles: ('admin' | 'editor' | 'user')[];
}
}

これですべてのユーザー関連関数が型付けされます:

const user = currentUser();
// TypeScriptが認識: user?.id, user?.name, user?.email

const roles = currentRoles();
// TypeScriptが認識: roles は ('admin' | 'editor' | 'user')[]

setUser(
{ id: '123', name: 'Alice', email: 'alice@example.com' },
['admin', 'user']
);
// RequestContextSchemaに対して型チェック

型宣言の配置場所

プロジェクトにtypes/zelt.d.tsファイルを作成:

// types/zelt.d.ts
declare module '@zeltjs/core' {
interface RequestContextSchema {
user: {
id: string;
name: string;
email: string;
avatarUrl?: string;
};
authRoles: ('admin' | 'moderator' | 'user')[];
}
}

export {};

tsconfig.jsonにこのファイルが含まれることを確認:

{
"include": ["src/**/*", "types/**/*"]
}

ユーザー設計のベストプラクティス

最小限に保つ

ハンドラーで必要なフィールドのみを含める。データベースレコード全体をコピーしない:

// ✅ 良い — 最小限のコンテキスト
interface RequestContextSchema {
user: {
id: string;
name: string;
};
}

// ❌ 避ける — データが多すぎる
interface RequestContextSchema {
user: {
id: string;
name: string;
email: string;
passwordHash: string; // 機密データは絶対に含めない
createdAt: Date;
updatedAt: Date;
preferences: object;
// ... さらに20フィールド
};
}

必要時に追加データを取得

ユーザーIDを使用して特定のハンドラーでより多くのデータを取得:

@Controller('/settings')
class SettingsController {
@Authorized()
@Get('/')
async getSettings(user = currentUser()) {
const fullUser = await db.users.findById(user.id);
return { preferences: fullUser.preferences };
}
}

ロールの粒度を検討

ロールはシンプルな文字列にすべき。複雑な権限ロジックはサービスに:

// ✅ 良い — シンプルなロール
authRoles: ('admin' | 'editor' | 'viewer')[];

// ❌ 避ける — 過度に具体的なロール
authRoles: ('can_edit_posts' | 'can_delete_posts' | 'can_view_analytics' | ...)[];

きめ細かい権限については、サービスレイヤーでロールをチェック:

class PostService {
canEdit(post: Post, user = currentUser(), roles = currentRoles()): boolean {
if (roles.includes('admin')) return true;
if (roles.includes('editor') && post.authorId === user.id) return true;
return false;
}
}