Features
Docs
CLI
Benchmarks
Examples

© 2024 MoroJs

Response Helpers API

Complete reference for every response helper in MoroJS. Three layers of helpers give you full control — from one-liner convenience methods to fluent builders for complex scenarios.

Overview

MoroJS provides three layers of response helpers. Pick the one that fits your needs — they all produce the same consistent response shape.

LayerSets HTTP Status?Sends Response?Import
res.* Yes YesAvailable on every res object
response.* No Noimport { response } from '@morojs/moro'
ResponseBuilder No Noimport { ResponseBuilder } from '@morojs/moro'
Success Shape
{
  success: true,
  data: T,
  message?: string
}
Error Shape
{
  success: false,
  error: string,
  code?: string,
  message?: string
}

Types

All handler signatures use HttpRequest and HttpResponse from @morojs/moro.

Import

typescript

import { HttpRequest, HttpResponse } from '@morojs/moro';

HttpRequest

Extends Node's IncomingMessage with parsed properties:

HttpRequest Interface

typescript

1interface HttpRequest extends IncomingMessage {
2  params: Record<string, string>;
3  query: Record<string, string>;
4  body: any;
5  path: string;
6  headers: Record<string, string>;
7  ip: string;
8  requestId: string;
9  cookies?: Record<string, string>;
10  files?: Record<string, any>;
11  [key: string]: any;
12}

HttpResponse

Extends Node's ServerResponse with all the methods documented below:

HttpResponse Type

typescript

type HttpResponse = ServerResponse & MoroResponseMethods;

Handler Signatures

Handler & Middleware Types

typescript

1type HttpHandler = (
2  req: HttpRequest,
3  res: HttpResponse
4) => Promise<void> | void;
5
6type Middleware = (
7  req: HttpRequest,
8  res: HttpResponse,
9  next: () => void
10) => Promise<void> | void;

CookieOptions

CookieOptions Interface

typescript

1interface CookieOptions {
2  maxAge?: number;
3  expires?: Date;
4  httpOnly?: boolean;
5  secure?: boolean;
6  sameSite?: 'strict' | 'lax' | 'none';
7  domain?: string;
8  path?: string;
9  critical?: boolean;
10  throwOnLateSet?: boolean;
11}

ResponseState

ResponseState Interface

typescript

1interface ResponseState {
2  headersSent: boolean;
3  statusCode: number;
4  headers: Record<string, any>;
5  finished: boolean;
6  writable: boolean;
7}

Core Methods

Foundational methods on every res object for sending data, setting status codes, managing cookies, and more.

res.json(data)

Serializes data as JSON and sends it. Sets Content-Type: application/json.

Usage

typescript

1handler: (req, res) => {
2  res.json({ hello: 'world' });
3}

res.status(code)

Sets the HTTP status code. Returns res for chaining.

Usage

typescript

1handler: (req, res) => {
2  res.status(201).json({ success: true, data: newUser });
3}

res.send(data)

Sends a string or Buffer response.

Usage

typescript

1handler: (req, res) => {
2  res.send('Hello, world');
3}

res.cookie(name, value, options?)

Sets a cookie on the response. Returns res for chaining. See CookieOptions above for available options.

Usage

typescript

1handler: (req, res) => {
2  res.cookie('session', token, { httpOnly: true, secure: true, sameSite: 'strict' })
3     .json({ success: true });
4}

res.clearCookie(name, options?)

Clears a cookie. Returns res for chaining.

Usage

typescript

1handler: (req, res) => {
2  res.clearCookie('session').json({ success: true });
3}

res.redirect(url, status?)

Redirects to the given URL. Defaults to 302.

Usage

typescript

1handler: (req, res) => {
2  res.redirect('/login');
3  // or with a specific status
4  res.redirect('/new-location', 301);
5}

res.sendFile(filePath)

Streams a file as the response. Returns a Promise<void>.

Usage

typescript

1handler: async (req, res) => {
2  await res.sendFile('/uploads/report.pdf');
3}

res.render(template, data?)

Renders a template (if a template engine is configured). Returns a Promise<void>.

Usage

typescript

1handler: async (req, res) => {
2  await res.render('dashboard', { user: req.user });
3}

Success & Error Responses

These methods set the HTTP status code and send the response in a single call. After calling any of these, the response is finished.

Success Responses

res.success(data, message?)

200 OK

Usage

typescript

1handler: (req, res) => {
2  const users = await getUsers();
3  res.success(users);
4}
5
6// With message
7handler: (req, res) => {
8  const user = await createUser(req.body);
9  res.success(user, 'User created successfully');
10}

Response Body

json

1{
2  "success": true,
3  "data": { "id": 1, "name": "Alice" },
4  "message": "User created successfully"
5}

res.created(data, location?)

201 Created

Optionally sets the Location header.

Usage

typescript

1handler: (req, res) => {
2  const user = await createUser(req.body);
3  res.created(user, `/api/users/${user.id}`);
4}

Response Body

json

1{ "success": true, "data": { "id": 1, "name": "Alice" } }
2
3// Headers: Location: /api/users/1 (when provided)

res.noContent()

204 No Content

Sends an empty body. Perfect for delete operations.

Usage

typescript

1handler: (req, res) => {
2  await deleteUser(req.params.id);
3  res.noContent();
4}

res.paginated(data, pagination)

200 OK

Pagination metadata (totalPages, hasNext, hasPrev) is calculated automatically.

ParameterType
dataT[]
pagination{ page: number; limit: number; total: number }

Usage

typescript

1handler: (req, res) => {
2  const { users, total } = await getUsers({ page: 1, limit: 20 });
3  res.paginated(users, { page: 1, limit: 20, total });
4}

Response Body

json

1{
2  "success": true,
3  "data": [{ "id": 1, "name": "Alice" }],
4  "pagination": {
5    "page": 1,
6    "limit": 20,
7    "total": 100,
8    "totalPages": 5,
9    "hasNext": true,
10    "hasPrev": false
11  }
12}

Error Responses

res.error(error, code?, message?)

200

Sends a 200 OK response (does not set a non-200 status code) with an error body. Use res.status() beforehand if you need a specific HTTP code, or use one of the specific error helpers below.

Usage

typescript

1handler: (req, res) => {
2  res.status(500).json({ success: false, error: 'Something went wrong' });
3  // or
4  res.error('Something went wrong', 'CUSTOM_ERROR', 'Additional details');
5}

res.badRequest(message?)

400
Default message: 'Invalid request'

Usage

typescript

1handler: (req, res) => {
2  if (!req.body.email) {
3    res.badRequest('Email is required');
4    return;
5  }
6}

Response Body

json

1{
2  "success": false,
3  "error": "Bad Request",
4  "code": "BAD_REQUEST",
5  "message": "Email is required"
6}

res.unauthorized(message?)

401
Default message: 'Authentication required'

Usage

typescript

1handler: (req, res) => {
2  if (!req.user) {
3    res.unauthorized();
4    return;
5  }
6}

Response Body

json

1{
2  "success": false,
3  "error": "Unauthorized",
4  "code": "UNAUTHORIZED",
5  "message": "Authentication required"
6}

res.forbidden(message?)

403
Default message: 'Insufficient permissions'

Usage

typescript

1handler: (req, res) => {
2  if (!req.user.roles.includes('admin')) {
3    res.forbidden('Admin access required');
4    return;
5  }
6}

Response Body

json

1{
2  "success": false,
3  "error": "Forbidden",
4  "code": "FORBIDDEN",
5  "message": "Admin access required"
6}

res.notFound(resource?)

404
Default resource: 'Resource'

Usage

typescript

1handler: (req, res) => {
2  const user = await getUser(req.params.id);
3  if (!user) {
4    res.notFound('User');
5    return;
6  }
7}

Response Body

json

1{
2  "success": false,
3  "error": "Not Found",
4  "code": "NOT_FOUND",
5  "message": "User not found"
6}

res.conflict(message)

409
Message is required

Usage

typescript

1handler: (req, res) => {
2  const existing = await getUserByEmail(req.body.email);
3  if (existing) {
4    res.conflict('Email already in use');
5    return;
6  }
7}

Response Body

json

1{
2  "success": false,
3  "error": "Conflict",
4  "code": "CONFLICT",
5  "message": "Email already in use"
6}

res.validationError(errors)

422

Sends field-level error details. Each error has field, message, and optional code.

Usage

typescript

1handler: (req, res) => {
2  const errors = validateUser(req.body);
3  if (errors.length > 0) {
4    res.validationError([
5      { field: 'email', message: 'Invalid email format', code: 'INVALID_FORMAT' },
6      { field: 'age', message: 'Must be at least 18' },
7    ]);
8    return;
9  }
10}

Response Body

json

1{
2  "success": false,
3  "error": "Validation Failed",
4  "code": "VALIDATION_ERROR",
5  "errors": [
6    { "field": "email", "message": "Invalid email format", "code": "INVALID_FORMAT" },
7    { "field": "age", "message": "Must be at least 18" }
8  ]
9}

res.rateLimited(retryAfter?)

429

When retryAfter (seconds) is provided, the Retry-After header is set automatically.

Usage

typescript

1handler: (req, res) => {
2  const limited = await checkRateLimit(req.ip);
3  if (limited) {
4    res.rateLimited(60);
5    return;
6  }
7}

Response Body

json

1{
2  "success": false,
3  "error": "Rate Limit Exceeded",
4  "code": "RATE_LIMITED",
5  "message": "Too many requests. Retry after 60 seconds.",
6  "retryAfter": 60
7}
8
9// Headers: Retry-After: 60

res.internalError(message?)

500
Default message: 'Internal server error'

Usage

typescript

1handler: (req, res) => {
2  try {
3    const data = await fetchExternalService();
4    res.success(data);
5  } catch (err) {
6    res.internalError('Failed to fetch data from external service');
7  }
8}

Response Body

json

1{
2  "success": false,
3  "error": "Internal Server Error",
4  "code": "INTERNAL_ERROR",
5  "message": "Failed to fetch data from external service"
6}

Header & State Utilities

Low-level utilities for inspecting and manipulating headers and response state.

res.hasHeader(name)

Returns true if the given header has been set.

Usage

typescript

1if (!res.hasHeader('X-Request-Id')) {
2  res.setHeader('X-Request-Id', req.requestId);
3}

res.setBulkHeaders(headers)

Sets multiple headers at once. Returns res for chaining.

Usage

typescript

1res.setBulkHeaders({
2  'X-Request-Id': req.requestId,
3  'X-Response-Time': 42,
4  'Cache-Control': 'no-store',
5}).json(data);

res.appendHeader(name, value)

Appends a value to an existing header (or creates it). Returns res for chaining.

Usage

typescript

1res.appendHeader('Set-Cookie', 'a=1')
2   .appendHeader('Set-Cookie', 'b=2');

res.canSetHeaders()

Returns true if headers have not been sent yet and can still be modified.

Usage

typescript

1if (res.canSetHeaders()) {
2  res.setHeader('X-Custom', 'value');
3}

res.getResponseState()

Returns the current ResponseState object.

Usage

typescript

1const state = res.getResponseState();
2// { headersSent: false, statusCode: 200, headers: {...}, finished: false, writable: true }

Standalone Helper Functions

These functions build response body objects only. They do not set HTTP status codes or send the response. Use them with res.status().json() for full control, or return them from a handler (which sends 200 by default).

Import

typescript

import { response } from '@morojs/moro';

Quick Reference

FunctionReturns
response.success(data, message?){ success: true, data, message? }
response.error(error, code?, message?){ success: false, error, code?, message? }
response.validationError(details, message?){ success: false, error: "Validation failed", ... }
response.unauthorized(message?){ success: false, error: "Unauthorized", ... }
response.forbidden(message?){ success: false, error: "Forbidden", ... }
response.notFound(resource?){ success: false, error: "Not Found", ... }
response.conflict(message){ success: false, error: "Conflict", ... }
response.badRequest(message?){ success: false, error: "Bad Request", ... }
response.internalError(message?){ success: false, error: "Internal Server Error", ... }
response.rateLimited(retryAfter?){ success: false, error: "Too Many Requests", ... }

Return directly (sends 200 with body)

typescript

1import { response } from '@morojs/moro';
2
3handler: (req, res) => {
4  const users = await getUsers();
5  return response.success(users);
6}

Pair with res.status() for proper HTTP codes

typescript

1import { response } from '@morojs/moro';
2
3handler: (req, res) => {
4  const user = await getUser(req.params.id);
5  if (!user) {
6    return res.status(404).json(response.notFound('User'));
7  }
8  return response.success(user);
9}

ResponseBuilder

A fluent API for building complex responses. Returns body objects only (no HTTP status or send). Use method chaining to compose responses step by step.

Import

typescript

import { ResponseBuilder } from '@morojs/moro';

Builder Methods

MethodDescription
ResponseBuilder.success(data)Start a success response
ResponseBuilder.error(error, code?)Start an error response
.message(msg)Add a message
.details(details)Add details (error responses)
.code(code)Add an error code
.build()Return the final response object

Success with ResponseBuilder

typescript

1handler: (req, res) => {
2  const users = await getUsers();
3  return ResponseBuilder.success(users)
4    .message('Retrieved all active users')
5    .build();
6}

Error with ResponseBuilder

typescript

1handler: (req, res) => {
2  return res.status(404).json(
3    ResponseBuilder.error('User not found', 'USER_NOT_FOUND')
4      .details({ id: req.params.id, searched: ['db', 'cache'] })
5      .build()
6  );
7}

Important: Return Values vs HTTP Status Codes

When a handler returns a plain object, the framework serializes it as JSON with a 200 OK status. A status field in the JSON body does not affect the HTTP status code.

The Pitfall — This sends HTTP 200, NOT 403

typescript

1// THIS SENDS HTTP 200 -- the "status" field is just data in the JSON body
2handler: (req, res) => {
3  return { error: 'Forbidden', status: 403 };
4}

To send an actual HTTP 403, use one of these:

Option 1: res.* helper (Recommended)

1handler: (req, res) => {
2  res.forbidden('You do not have access');
3  return;
4}

Option 2: res.status().json() with standalone helper

1handler: (req, res) => {
2  return res.status(403).json(response.forbidden('You do not have access'));
3}

Option 3: res.status().json() manually

1handler: (req, res) => {
2  return res.status(403).json({ success: false, error: 'Forbidden' });
3}

Rule of thumb: If you need an HTTP status other than 200, always use a res.* method or res.status() explicitly.

Quick Reference — All res.* Methods

Core

  • res.json(data)
  • res.status(code)
  • res.send(data)
  • res.cookie(name, val, opts?)
  • res.clearCookie(name, opts?)
  • res.redirect(url, status?)
  • res.sendFile(path)
  • res.render(tpl, data?)

Success

  • res.success(data, msg?)— 200
  • res.created(data, loc?)— 201
  • res.noContent()— 204
  • res.paginated(data, pg)— 200

Utilities

  • res.hasHeader(name)
  • res.setBulkHeaders(hdrs)
  • res.appendHeader(name, val)
  • res.canSetHeaders()
  • res.getResponseState()

Errors

  • res.error(err, code?, msg?)— 200
  • res.badRequest(msg?)— 400
  • res.unauthorized(msg?)— 401
  • res.forbidden(msg?)— 403
  • res.notFound(resource?)— 404
  • res.conflict(msg)— 409
  • res.validationError(errs)— 422
  • res.rateLimited(retry?)— 429
  • res.internalError(msg?)— 500

Related