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

Request & Response Primitives

Zelt provides functional primitives for accessing request data and building responses. These primitives can be used as default parameters in controller methods.

Request Primitives

Query Parameters

import { Controller, Get, request, response } from '@zeltjs/core';

@Controller('/search')
export class SearchController {
  @Get('/')
  search(req = request(), res = response()) {
    const q = req.queryParam('q');
    const tags = req.queryParams('tag');
    // q: string | undefined
    // tags: string[] (empty array if not provided)
    return res.json({ query: q, tags });
  }
}
FunctionReturn TypeDescription
queryParam(name)string | undefinedGet a single query parameter
queryParams(name)string[]Get all values for a query parameter

Headers

import { Controller, Get, request, response } from '@zeltjs/core';

@Controller('/api')
export class ApiController {
  @Get('/info')
  info(req = request(), res = response()) {
    const userAgent = req.header('User-Agent');
    const acceptLanguage = req.header('Accept-Language');
    return res.json({ userAgent, acceptLanguage });
  }
}
FunctionReturn TypeDescription
header(name)string | undefinedGet a request header value

Cookies

import { Controller, Get, request, response } from '@zeltjs/core';

@Controller('/session')
export class SessionController {
  @Get('/')
  getSession(req = request(), res = response()) {
    const sessionId = req.cookie('session_id');
    return res.json({ sessionId });
  }
}
FunctionReturn TypeDescription
cookie(name)string | undefinedGet a cookie value

URL & Path

import { Controller, Get, request, response } from '@zeltjs/core';

@Controller('/debug')
export class DebugController {
  @Get('/request')
  requestInfo(req = request(), res = response()) {
    const fullUrl = req.url();
    const requestPath = req.path();
    const httpMethod = req.method();
    return res.json({
      url: fullUrl,      // "http://localhost:3000/debug/request?foo=bar"
      path: requestPath, // "/debug/request"
      method: httpMethod // "GET"
    });
  }
}
FunctionReturn TypeDescription
url()stringFull request URL including query string
path()stringRequest path without query string
method()stringHTTP method (GET, POST, etc.)

Request Body

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

const FormSchema = v.record(v.string(), v.unknown());

@Controller('/upload')
export class UploadController {
  @Post('/json')
  async uploadJson(req = request(), res = response()) {
    const data = await req.body();
    return res.json({ received: data });
  }

  @Post('/form')
  async uploadForm(req = request(FormSchema, { target: 'form' }), res = response()) {
    const formData = await req.body();
    return res.json({ fields: formData });
  }
}

The request body target is configured when calling request(). When no schema is passed, request() uses an internal any schema and the default json target.

TypeReturn TypeDescription
request()unknownParsed JSON body with the default any schema
request(schema)schema outputValidated JSON body
request(schema, { target: 'form' })schema outputValidated form data
ヒント

For validated request bodies with automatic type inference, use request() with a schema instead.

Path Parameters

import { Controller, Get, request, response } from '@zeltjs/core';

@Controller('/users')
export class UserController {
  @Get('/:id')
  getUser(req = request(), res = response()) {
    const id = req.pathParam('id');
    return res.json({ userId: id });
  }
}
FunctionReturn TypeDescription
pathParam(name)stringGet a path parameter (throws if undefined)

Response Primitives

response()

The response() primitive returns a builder for constructing HTTP responses:

import { Controller, Get, request, response } from '@zeltjs/core';

@Controller('/users')
export class UserController {
  @Get('/:id')
  getUser(req = request(), res = response()) {
    const id = req.pathParam('id');
    // id: string (throws if not defined)
    return res.json({ userId: id });
  }
}

Response Methods

MethodDescription
json(data, status?, headers?)JSON response with optional status code and headers
text(data, status?)Plain text response
redirect(url, status?)HTTP redirect (default: 302)
body(data, status?)Raw body response
header(name, value)Set a response header (chainable)
stream(cb, onError?)Stream binary data
streamText(cb, onError?)Stream text data
sse(cb, onError?)Server-Sent Events stream

Setting Cookies

import { Controller, Get, Post, response } from '@zeltjs/core';

@Controller('/api')
export class ApiController {
  @Get('/data')
  getData(res = response()) {
    return res.json({ message: 'Hello' });
  }

  @Get('/redirect')
  redirect(res = response()) {
    return res.redirect('/new-location', 302);
  }

  @Get('/text')
  getText(res = response()) {
    return res.text('Plain text response');
  }

  @Post('/created')
  create(res = response()) {
    return res.json({ id: '123' }, 201);
  }
}
OptionTypeDescription
domainstringCookie domain
expiresDateExpiration date
httpOnlybooleanHTTP-only flag
maxAgenumberMax age in seconds
pathstringCookie path
securebooleanSecure flag
sameSite'Strict' | 'Lax' | 'None'SameSite attribute

Streaming Responses

Zelt provides streaming capabilities for real-time data delivery.

Basic Streaming

Use stream() for binary data or streamText() for text data:

import { Controller, Post, response } from '@zeltjs/core';

@Controller('/auth')
export class AuthController {
  @Post('/login')
  login(res = response()) {
    return res
      .setCookie('session_id', 'abc123', {
        httpOnly: true,
        secure: true,
        sameSite: 'Strict',
        maxAge: 60 * 60 * 24, // 1 day
      })
      .json({ success: true });
  }

  @Post('/logout')
  logout(res = response()) {
    return res
      .deleteCookie('session_id')
      .json({ success: true });
  }
}

Streaming Responses

Zelt provides streaming capabilities for real-time data delivery.

Basic Streaming

Use stream() for binary data or streamText() for text data:

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

@Controller('/stream')
export class StreamController {
  @Get('/data')
  streamData(res = response()) {
    return res.stream(async (stream) => {
      await stream.write('chunk 1');
      await stream.sleep(100);
      await stream.write('chunk 2');
      await stream.close();
    });
  }

  @Get('/lines')
  streamLines(res = response()) {
    return res.streamText(async (stream) => {
      await stream.writeln('line 1');
      await stream.writeln('line 2');
      await stream.close();
    });
  }
}

Server-Sent Events (SSE)

Use sse() for Server-Sent Events:

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

@Controller('/events')
export class EventController {
  @Get('/updates')
  streamUpdates(res = response()) {
    return res.sse(async (stream) => {
      await stream.writeSSE({ data: 'connected', event: 'open' });

      for (let i = 0; i < 5; i++) {
        await stream.sleep(1000);
        await stream.writeSSE({
          data: JSON.stringify({ count: i }),
          event: 'update',
          id: String(i),
        });
      }

      await stream.close();
    });
  }
}

Stream Writer Methods

MethodDescription
write(input)Write Uint8Array or string to stream
writeln(input)Write string with newline
writeSSE(message)Write SSE message (SSE streams only)
sleep(ms)Pause for specified milliseconds
pipe(body)Pipe a ReadableStream
close()Close the stream
abort()Abort the stream
onAbort(listener)Register abort handler

SSE Message Format

type SSEMessage = {
  data: string | Promise<string>;
  event?: string;
  id?: string;
  retry?: number;
};

Error Handling

Streaming methods accept an optional error handler:

@Controller('/stream')
class StreamController {
  @Get('/data')
  streamData(res = response()) {
    return res.stream(
      async (stream) => {
        // ... stream logic
      },
      async (error, stream) => {
        await stream.write(`Error: ${error.message}`);
        await stream.close();
      }
    );
  }
}

Chaining Response Methods

Response methods that modify state (header, setCookie, deleteCookie) return the builder, allowing method chaining:

@Controller('/files')
class FileController {
  @Get('/download')
  download(res = response()) {
    return res
      .header('Content-Disposition', 'attachment; filename="report.csv"')
      .header('Cache-Control', 'no-cache')
      .body('id,name\n1,Alice', 200);
  }
}