Skip to main content

Request & Response Primitives

Zelt provides a request() primitive for accessing request data and a response() primitive for building responses. request() can be used as a default parameter in controller methods and returns a request accessor.

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 });
  }
}
MethodReturn TypeDescription
req.queryParam(name)string | undefinedGet a single query parameter
req.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 });
  }
}
MethodReturn TypeDescription
req.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 });
  }
}
MethodReturn TypeDescription
req.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"
    });
  }
}
MethodReturn TypeDescription
req.url()stringFull request URL including query string
req.path()stringRequest path without query string
req.method()stringHTTP method (GET, POST, etc.)

Client IP

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

@Controller('/debug')
export class DebugController {
  @Get('/ip')
  clientIp(req = request(), res = response()) {
    const ip = req.ip();
    return res.json({ ip });
  }
}
MethodReturn TypeDescription
req.ip()string | undefinedClient IP address

Request Body

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

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 });
  }
}
request() callawait req.body() typeDescription
request()unknownParsed JSON body with the default any schema
request(schema)schema outputValidated JSON body
request(schema, { target: 'form' })schema outputValidated form data
tip

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');
    // id: string (throws if not defined)
    return res.json({ userId: id });
  }
}
MethodReturn TypeDescription
req.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, 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);
  }
}

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, 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 });
  }
}
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, 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

Both 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')
      .setCookie('download_started', 'true')
      .text('id,name\n1,Alice\n2,Bob');
  }
}