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

Controllers

Controllerは、送られてくるリクエストを処理し、クライアントへレスポンスを返す責務を持ちます。

Defining Controllers

controllerは@Controller()でデコレートされたクラスです。このデコレータはパスプレフィックスを受け取り、controller内で定義される全てのルートの先頭に付加されます。

import { Controller, Get, Post, response } from '@zeltjs/core';
import { request } from '@zeltjs/core';
import * as v from 'valibot';

const CreateUserBody = v.object({
  name: v.string(),
  email: v.pipe(v.string(), v.email()),
});

@Controller('/users')
export class UserController {
  @Get('/')
  findAll() {
    return { users: [] };
  }

  @Get('/:id')
  findOne(req = request()) {
    const id = req.pathParam('id');
    return { id, name: 'John Doe' };
  }

  @Post('/')
  async create(req = request(CreateUserBody), res = response()) {
    const body = await req.body();
    return res.json({ id: '1', ...body }, 201);
  }
}

Route Path Rules

@Controllerのプレフィックスとメソッドデコレータのパスは結合されて最終的なルートになります。末尾のスラッシュは取り除かれ、メソッドパスの先頭のスラッシュは省略可能です。

Controller PrefixMethod PathFinal Route
'/users''/'/users
'/users''/:id'/users/:id
'/api''/users'/api/users
'/''/hello'/hello
'/api/v1''/users/:id'/api/v1/users/:id
ヒント

@Get('/items')@Get('items')はどちらも同じ結果になります — 先頭のスラッシュが省略されていれば自動で付加されます。

HTTP Method Decorators

Zeltは全ての標準HTTPメソッドに対応するデコレータを提供します:

DecoratorHTTP Method
@Get()GET
@Post()POST
@Put()PUT
@Patch()PATCH
@Delete()DELETE
@Controller('/items')
export class ItemController {
  @Get('/')
  findAll() { /* ... */ }

  @Get('/:id')
  findOne(req = request()) {
    const id = req.pathParam('id');
    /* ... */
  }

  @Post('/')
  async create(req = request(schema)) {
    const body = await req.body();
    /* ... */
  }

  @Put('/:id')
  async update(req = request(schema)) {
    const id = req.pathParam('id');
    const body = await req.body();
    /* ... */
  }

  @Patch('/:id')
  async patch(req = request(schema)) {
    const id = req.pathParam('id');
    const body = await req.body();
    /* ... */
  }

  @Delete('/:id')
  remove(req = request()) {
    const id = req.pathParam('id');
    /* ... */
  }
}

Route Parameters

request()をハンドラのパラメータとして注入し、req.pathParam()でルートパラメータを取り出します:

@Controller('/items')
class ItemController {
  @Get('/:category/:id')
  findOne(req = request()) {
    const category = req.pathParam('category');
    const id = req.pathParam('id');
    return { category, id };
  }
}

Request Body

Valibot schemaを渡したrequest()を使うことで、リクエストボディをバリデーションし型付けできます:

const CreatePostBody = v.object({
  title: v.pipe(v.string(), v.minLength(1), v.maxLength(100)),
  content: v.string(),
  tags: v.optional(v.array(v.string())),
});

@Controller('/posts')
class PostController {
  @Post('/')
  async create(req = request(CreatePostBody)) {
    const body = await req.body();
    // bodyは { title: string; content: string; tags?: string[] } として完全に型付けされる
    return { id: '1', ...body };
  }
}

バリデーションが失敗すると、Zeltは詳細なエラー情報とともに自動的に400レスポンスを返します。

Without Validation

バリデーションが不要なケース(任意のJSONを受け入れる場合など)では、request()を注入してreq.body()を使います:

@Controller('/webhooks')
class WebhookController {
  @Post('/github')
  async handleGithubWebhook(req = request()) {
    const payload = await req.body();
    // payloadはunknown型として扱われる
    return { received: true };
  }
}

request()やその他のリクエストヘルパーの詳細はRequest & Response Primitivesを参照してください。

Returning Responses

Controllerメソッドは2種類の戻り方をサポートします:

値をそのまま返すだけで、Zeltは自動的にステータス200のJSONとしてシリアライズします:

@Controller('/users')
class UserController {
  @Get('/')
  findAll() {
    return { users: [] }; // → 200 OK, Content-Type: application/jsonを返す
  }

  @Get('/health')
  health() {
    return 'OK'; // → 200 OK, Content-Type: text/plainを返す
  }
}

response() (For Custom Status Codes or Headers)

200以外のステータスコード、カスタムヘッダー、リダイレクトが必要な場合はresponse()を使います:

@Controller('/users')
class UserController {
  @Post('/')
  async create(req = request(schema), res = response()) {
    const body = await req.body();
    return res.json({ id: '1', ...body }, 201); // 201 Createdを返す
  }

  @Delete('/:id')
  remove(req = request()) {
    const id = req.pathParam('id');
    return new Response(null, { status: 204 }); // 204 No Contentを返す
  }
}

When to Use Which

ScenarioApproach
200でJSONを返すreturn { data }
カスタムステータス(201、204など)で返すresponse().json(data, status)
カスタムヘッダーを設定するresponse().header(name, value).json(data)
リダイレクトするresponse().redirect(url)
Cookieを設定するresponse().setCookie(name, value).json(data)
レスポンスをストリームするresponse().stream(cb) / response().sse(cb)

response()の完全なAPIはRequest & Response Primitivesを参照してください。

Custom Response Status

HTTPステータスコードを制御するにはresponse()を使います:

@Controller('/items')
class ItemController {
  @Post('/')
  async create(req = request(schema), res = response()) {
    const body = await req.body();
    const created = { id: '1', ...body };
    return res.json(created, 201); // 201 Createdを返す
  }

  @Delete('/:id')
  remove(req = request()) {
    const id = req.pathParam('id');
    // 削除処理を実行する
    return new Response(null, { status: 204 }); // 204 No Contentを返す
  }
}

Registering Controllers

ControllerはcreateApp()に登録する必要があります:

export const app = createApp([http({
    controllers: [UserController, PostController],
  })]);

Next Steps

  • リクエスト/レスポンス処理のためのMiddlewareについて学ぶ