Platform
    • RTK Query
    • Mock Server (MSW)
    • Data Converters
    • OpenAPI Codegen
Projects
  • API Connections & Tools
  1. Documentation
  2. Data Converters

Backend and Frontend Converters (Mappers)

Guide on decoupling API contracts and internal application data models using mappings (DTO -> Domain Model).

Data Mapping Architecture

Benefits: Converters isolate the UI from backend changes, allow data formatting (dates, enums), and guarantee layer-level type safety.

Entity Converters File Structure

Naming Conventions

  • Converters:

    Located in the converters/ folder. Files are named following the [entity-name].converters.ts pattern. They use types from the sibling folder for data transformation.

  • Types and Interfaces:

    Located in the types folder. Must be divided into *-backend.interface.ts (raw DTOs as T*Backend) and *.interface.ts (UI models as I*). All types are exported via index.ts.

01Mapping to Frontend (Domain Model)

Transforming raw backend data (snake_case, strings instead of dates) into camelCase and typed objects for the UI. Use operation-specific names like mapEntityToFrontend / mapEntitiesToFrontend — never a literal mapToFrontend.

converters/booking-order.converters.ts
1import { formatDate } from "@/shared/lib/utils"; 2import type { IPaginationResponse } from "@/shared/types"; 3import type { TEntityBackend, IEntity, TEntitiesBackend } from "../types"; 4 5export const mapEntityToFrontend = ( 6 data: TEntityBackend 7): IEntity => ({ 8 id: data.id, 9 fullName: data.full_name, 10 dateCreated: formatDate(data.created_at), 11 status: data.status_code 12}); 13 14/** Backend list envelope → unified { data, total } */ 15export const mapEntitiesToFrontend = ( 16 response: TEntitiesBackend 17): IPaginationResponse<IEntity> => ({ 18 data: response.items.map(mapEntityToFrontend), 19 total: response.total_count 20});

02Mapping to Backend (DTO)

Preparing data before sending. Used when creating or updating entities to return to the DTO format expected by the API.

converters/booking-order.converters.ts
1import type { TEntityBackend, IEntity } from "../types"; 2 3export const mapEntityToBackend = ( 4 data: Partial<IEntity> 5): Partial<TEntityBackend> => ({ 6 id: data.id, 7 full_name: data.fullName, 8 created_at: data.dateCreated, 9 status_code: data.status 10});

03Filter Conversion

A special case of mapping search filter state into query parameters. Allows convenient array joining, handling empty values, and API-specific formats.

converters/booking-order.converters.ts
1import type { IPaginationRequest } from "@/shared/types"; 2import type { TEntitiesQueryBackend } from "../types"; 3 4/** IPaginationRequest → API-specific query fields (names stay here) */ 5export const mapEntitiesQueryToBackend = ( 6 filters: IPaginationRequest 7): TEntitiesQueryBackend => ({ 8 page: filters.page, 9 elements_on_page: filters.limit, 10 search: filters.search || undefined, 11 status_code: filters.status 12});

04RTK Query Integration

Converters are injected directly into endpoint definitions: transformResponse for data retrieval and query (params/body) for data submission. Importing converters into pages/widgets/features is forbidden — UI already gets frontend models.

api/[entity-name].service.ts
1import { baseApi } from "@/shared/api/backend"; 2import type { IPaginationRequest, IPaginationResponse } from "@/shared/types"; 3import { 4 mapEntitiesQueryToBackend, 5 mapEntitiesToFrontend, 6 mapEntityToBackend 7} from "../converters"; 8import type { TEntitiesBackend, IEntity } from "../types"; 9 10/** Only place converters are imported — never in pages/widgets/features/ui */ 11export const entityApi = baseApi.injectEndpoints({ 12 endpoints: (builder) => ({ 13 getEntities: builder.query< 14 IPaginationResponse<IEntity>, 15 IPaginationRequest 16 >({ 17 query: (filters) => ({ 18 url: "/entity/list", 19 params: mapEntitiesQueryToBackend(filters) 20 }), 21 transformResponse: (response: TEntitiesBackend) => 22 mapEntitiesToFrontend(response) 23 }), 24 updateEntity: builder.mutation<void, IEntity>({ 25 query: (body) => ({ 26 url: "/entity/update", 27 method: "POST", 28 body: mapEntityToBackend(body) 29 }) 30 }) 31 }) 32});

05ESLint Isolation (do not leave the entity)

Enforce with no-restricted-imports (block @/entities/*/converters) and eslint-plugin-boundaries (only same-entity api/ may import converters). Relative ../converters from api/ stays allowed.
eslint.config.js (converters isolation)
1import boundaries from "eslint-plugin-boundaries"; 2 3export default [ 4 { 5 plugins: { boundaries }, 6 settings: { 7 "boundaries/elements": [ 8 { 9 type: "entity-converters", 10 pattern: "src/entities/*/converters/**", 11 capture: ["entity"] 12 }, 13 { 14 type: "entity-api", 15 pattern: "src/entities/*/api/**", 16 capture: ["entity"] 17 }, 18 { type: "pages", pattern: "src/pages/**" }, 19 { type: "widgets", pattern: "src/widgets/**" }, 20 { type: "features", pattern: "src/features/**" }, 21 { type: "entities", pattern: "src/entities/**" }, 22 { type: "shared", pattern: "src/shared/**" } 23 ], 24 "boundaries/ignore": ["**/*.{test,spec}.{ts,tsx}"] 25 }, 26 rules: { 27 // 1) Block public/alias imports of converters (api uses relative ../converters) 28 "no-restricted-imports": [ 29 "error", 30 { 31 patterns: [ 32 { 33 group: [ 34 "**/entities/*/converters", 35 "**/entities/*/converters/**", 36 "@/entities/*/converters", 37 "@/entities/*/converters/**" 38 ], 39 message: 40 "Converters are private to the entity. Import only from entities/<name>/api via relative '../converters'. UI must use frontend models from RTK hooks." 41 } 42 ] 43 } 44 ], 45 // 2) Only same-entity api may depend on converters segment 46 "boundaries/element-types": [ 47 "error", 48 { 49 default: "disallow", 50 rules: [ 51 { 52 from: { type: "entity-api", capture: ["entity"] }, 53 allow: [ 54 { type: "entity-converters", capture: ["entity"] }, 55 "entities", 56 "shared" 57 ] 58 }, 59 { 60 from: "entity-converters", 61 allow: ["entities", "shared"] 62 }, 63 { 64 from: "pages", 65 allow: ["widgets", "features", "entities", "shared"] 66 }, 67 { 68 from: "widgets", 69 allow: ["features", "entities", "shared"] 70 }, 71 { 72 from: "features", 73 allow: ["entities", "shared", "features"] 74 }, 75 { 76 from: "entities", 77 allow: ["entities", "shared"] 78 }, 79 { 80 from: "shared", 81 allow: ["shared", "entities"] 82 } 83 ] 84 } 85 ], 86 "boundaries/no-private": "error" 87 } 88 } 89];