Skip to content
By nestarc
Compatibility

@nestarc/safe-response 0.15.x and NestJS 10/11

The NestJS API Response Format You Won't Regret ​

Your API has 30 endpoints. Some return { data: [...] }, some return arrays directly, some return { result: {...}, status: 'ok' }. The error format? Depends on who wrote the endpoint.

Your frontend team is writing if (response.data) ... else if (response.result) ... everywhere. It's week 3 and they're already frustrated.

The Problem ​

NestJS doesn't enforce a response format. Controllers return whatever shape you give them:

typescript
// Endpoint A returns this
{ id: 1, name: 'Alice' }

// Endpoint B returns this
{ data: { id: 1, name: 'Alice' }, message: 'success' }

// Error from endpoint A
{ statusCode: 404, message: 'Not Found' }

// Error from endpoint B
{ error: true, msg: 'user not found', code: 'USER_NOT_FOUND' }

Every frontend dev has to learn each endpoint's quirks. Every new backend dev invents yet another format.

The Solution: Wrap Once, Everywhere ​

A NestJS interceptor can transform every response into a consistent envelope:

typescript
// Every success response becomes:
{
  "success": true,
  "statusCode": 200,
  "data": { "id": 1, "name": "Alice" }
}

// Every error response becomes:
{
  "success": false,
  "statusCode": 404,
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "User not found"
  }
}

The frontend only needs one type:

typescript
type ApiResponse<T> = {
  success: boolean;
  statusCode: number;
  data?: T;
  error?: { code: string; message: string };
};

Why Do This on Day One? ​

Changing response formats after launch is painful:

  • Every frontend consumer needs to update their parsing logic
  • Mobile apps with older versions can't handle the new format
  • API clients built by third parties break silently

Adding the wrapper before any frontend integration means you never have to migrate.

The Performance Question ​

"Doesn't wrapping every response add overhead?"

We measured it:

ScenarioRaw NestJSWith wrapperDifference
Success (200)0.61ms0.44ms-0.17ms
Error (404)0.39ms0.52ms+0.13ms

The measured differences were sub-millisecond in this local run. The success result should not be interpreted as the wrapper making NestJS faster; benchmark variance can be larger than the measured delta. Re-run the benchmark on your deployment path before setting a latency budget.

What Else Comes Free? ​

Once you have a global interceptor, you can add metadata without changing controllers:

  • Request ID tracking — optional X-Request-Id handling and response metadata
  • Response time — optional meta.responseTime
  • Pagination metadata — { meta: { pagination: { page, totalPages, ... } } }
  • Rate limit info — optional mirroring of X-RateLimit-* headers into meta.rateLimit
  • Deprecation warnings — RFC 9745 headers + response metadata

Each of these would be a separate interceptor if you built them yourself.

Using @nestarc/safe-response ​

@nestarc/safe-response does all of this:

bash
npm install @nestarc/safe-response
typescript
// app.module.ts — enable only the metadata your API contract needs
SafeResponseModule.register({
  requestId: true,
  responseTime: true,
  rateLimit: true,
}),

No controller or service changes are needed for the base envelope. Request IDs, response time, and rate-limit metadata are opt-in; their defaults are disabled.

Includes Swagger schema auto-generation, error code mapping, cursor/offset pagination metadata, i18n adapter, and zero-dependency TypeScript client types.

Documentation · GitHub · Benchmark

Primary framework reference: NestJS interceptors.

Last updated:

Released under the MIT License.