Request structure guidelines

Introduction

This document defines the Request/Response structure specifically for projects using a service-oriented architecture (SOA). For projects built on other architectural styles (e.g., MSA), the structure may vary, although the general principles remain consistent. The purpose of this document is to establish a unified, standardized approach to organizing requests and responses across Enovate projects. This standardization aims to ensure consistency, enhance maintainability, and support predictable integration patterns across teams and services. Although some examples may resemble structures used in existing services, the guidelines presented here are not bound to any particular project.

Objectives

  1. Establish a unified structure for incoming requests and outgoing responses.
  2. Improve code readability, maintainability, and long-term project scalability.
  3. Ensure that all developers follow consistent patterns when designing endpoints.
  4. Support clear API behaviour, predictable error handling, and forward compatibility.

General principles

1. Consistency across all services

Every API should follow the same structural approach for request and response formatting. This helps ensure:

  • Predictable behaviour for consumers,
  • Simpler onboarding for new developers.

2. Clear separation of concerns

Requests should focus only on the required input data. Responses should focus only on returned values and execution results. No business logic should leak into either.

3. Strong typing and validation

Each request must be validated, and each response must maintain a predictable and strict schema. This prevents runtime inconsistencies and unexpected behaviours.

Request structure

Requests across all services should follow a clearly defined pattern.

1. Header (mandatory)

Every request must include a header object containing metadata about the request. Fields:

  1. id - unique request identifier.
  2. version - API version.
  3. service - Name of the service handling the request.
  4. method - The method being invoked.
  5. token - Authentication key.

Example:

{
  "header":{
    "id":"1b7665f09cf71be154cf9",
    "version":"0.1.1",
    "service":"service-name",
    "method":"methodName",
   "token":"88b6d9105883dd5235ae1b7665f09cf71be154cf9508304284b13e84aba9669c07fce8f45e3dffd944b6217f9f2db4fcf3fe24bd6b5db703683996f56b792c07"
  }
}

2. Data (mandatory)

This is the main payload of the request. Every endpoint must define a clear schema for the data object.

Example:

{
  "data": {
    "email": "user@example.com",
    "password": "password123"
  }
}

3. Metadata (optional)

Used when additional context is needed, language preferences for example.

Example:

{
  "meta": {
    "locale": "en-US",
  }
}

Validation rules

All incoming requests must be validated using server-side validation. The rules should be tightly defined and should reject malformed or incomplete data. This is achieved with the help of:

  1. Centralising request validation logic.
  2. Providing a uniform validation error response.
  3. Implementing JSON schema-based validations for consistency.

Response structure

Every response - successful or erroneous - should follow a consistent structure.

1. Success response structure

Base format:

{
  "success": true,
  "data": {},
  "meta": {}
}

Fields:

  1. success - always a boolean. Always true for successful responses.
  2. data - the actual payload returned by the system. Should never be null unless explicitly required.
  3. meta - optional additional information (processing time, environment flags, etc.).

Example:

{
  "success": true,
  "data": {
    "id": "e501c2bf",
    "status": "created"
  },
  "meta": {
    "timestamp": "2025-02-10T12:45:00Z"
  }
}

2. Error response structure

Error responses must follow the same pattern as success responses, with predictable fields.

Base format:

{
  "success": false,
  "error": {
    "code": "string",
    "message": "string",
    "details": {}
  },
  "meta": {}
}

Fields:

  1. success - always false.
  2. error.code - a short, consistent identifier (e.g., VALIDATION_ERROR, NOT_FOUND, UNAUTHORIZED).
  3. error.message - human-readable explanation.
  4. error.details - optional field containing validation details or additional context.
  5. meta - same use as in success responses.

Example:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The provided data is invalid.",
    "details": {
      "email": "Invalid email format"
    }
  },
  "meta": {
    "timestamp": "2025-02-10T12:45:00Z"
  }
}

Each project must have a unified list of error codes with descriptions to ensure consistent usage across all services.

Standardised pagination, sorting, and date filtering schemas

To ensure uniform behaviour across all services, projects should use standardised schemas for pagination, sorting, and date-based filtering. These schemas are reusable, type-safe, and prevent inconsistencies between endpoints.

Pagination schema:

export const paginationSchema = {
 type: "object",
 description: "Pagination parameters in API requests.",
 properties: {
   page: {
     type: "integer",
     minimum: 1,
     default: 1,
     description: "To calculate the starting index (offset) for the data query.",
   },
   pageSize: {
     type: "integer",
     minimum: 1,
     maximum: 100,
     default: 20,
     description: "The number of items to return.",
   },
 },
 required: ["page", "pageSize"],
 additionalProperties: false,
};

Sorting schema:

export const sortSchema = {
 type: "object",
 description: "Sorting parameters in API requests.",
 properties: {
   field: {
     type: "string",
     minLength: 1,
     maxLength: 32,
     description: "The field name to sort by.",
     default: "createdAt",
   },
   direction: {
     type: "string",
     enum: ["asc", "desc"],
     default: "desc",
     description: "The sort direction.",
   },
 },
 required: ["field", "direction"],
 additionalProperties: false,
};

Date filter schema:

export const dateFilterSchema = {
 type: "object",
 description: "Filter by dates (period)",
 properties: {
   dateFrom: { type: "string", format: "date-time" },
   dateTo: { type: "string", format: "date-time" },
 },
 additionalProperties: false,
};

Use ISO-8601 for all timestamps.

Pagination structure

For all endpoints returning collections, the response should include a standard pagination structure.

Example:

{
  "success": true,
  "data": {
    "items": [
      { "id": 1 },
      { "id": 2 }
    ],
    "page": 1,
    "pageSize": 25,
    "total": 48,
    "totalPages": 2
  },
  "meta": {}
}

Naming and structural conventions

  1. All keys must follow camelCase formatting.
  2. Limit nesting depth wherever possible.
  3. Avoid mixing unrelated concerns in a single response.
  4. Maintain strict typing across the entire structure.

Summary

By following the structure outlined in this document, all teams within Enovate will benefit from predictable API behavior, improved developer experience, and easier long-term maintenance. These guidelines are intended to evolve, and contributions or suggestions for improvement are encouraged.