Skip to main content

DTOs

Installโ€‹

npm install @basketry/typescript-dtos

Basic Usageโ€‹

basketry.config.json
{
"source": "petstore.json",
"parser": "@basketry/openapi-3",
"generators": ["@basketry/typescript", "@basketry/typescript-dtos"],
"options": {
"dtos": { "role": "server" }
},
"output": "src"
}

File Structureโ€‹

This generator will create mappers.ts, types.ts, and a README.md file that contain Data Transfer Objects (DTOs), mappers, and related documentation generated for a server or client.

my-project/
โ”œโ”€โ”€ node_modules/
โ”œโ”€โ”€ src/
โ”‚ โ”œโ”€โ”€ v1/
โ”‚ โ”‚ โ”œโ”€โ”€ dtos <-- generated
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ mappers.ts <-- generated
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ README.md <-- generated
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ types.ts <-- generated
โ”‚ โ”‚ โ””โ”€โ”€ types.ts
โ”‚ โ””โ”€โ”€ index.ts
โ”œโ”€โ”€ .gitignore
โ”œโ”€โ”€ basketry.config.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ petstore.json
โ””โ”€โ”€ README.md

Optionsโ€‹

This generator depends on the @basketry/typescript generator and all of its applied options will also apply to files emitted by this generator.

See:

This generator depends on files generated by the @basketry/typescript, @basketry/typescript-dtos, and @basketry/zod generators. In most cases, those files will exist within the same directory as the generated http-client.ts file.

By default, the generated code will import its dependencies as follows:

src/v1/dtosmappers.ts
import type * as types from "../types";

If those modules are located in a different directory, you can use the following options to specify custom import paths. These paths can be any string that you would normally require or import. This can be useful when generated dependencies are distributed as part of a library or package.

typesImportPathโ€‹

  • Type: string - specifies the path for the types import.
basketry.config.json
{
"source": "petstore.json",
"parser": "@basketry/openapi-3",
"generators": ["@basketry/typescript", "@basketry/typescript-dtos"],
"output": "src",
"options": {
"dtos": {
"typesImportPath": "@petstore/sdk/lib/types",
"role": "server"
}
}
}

This option as configured will now emit the following code at the top of the generated file:

src/v1/http-client.ts
import * as mappers from "@petstore/sdk/lib/dtos/types";

roleโ€‹

  • Type: client or server - determines how the corresponding mappers are generated.
basketry.config.json
{
"source": "petstore.json",
"parser": "@basketry/openapi-3",
"generators": ["@basketry/typescript", "@basketry/typescript-dtos"],
"output": "src",
"options": {
"dtos": { "role": "server" }
}
}

Data Transfer Objects (DTOs)โ€‹

In the generated code, you will find two distinct sets of types: Business Object Types and DTO (Data Transfer Object) types. These types serve different purposes and are essential for maintaining a clear separation of concerns between internal data structures and the external API contract.

Why Two Sets of Types?โ€‹

  • Business Object Types are written in a way that is idiomatic to TypeScript. While they are generated from the API contract, they follow a naming and casing convention consistent with the rest of the codebase.

  • DTO Types represent the over-the-wire format defined by the API contract. These types are used to interop with other applications over the network, ensuring consistency in the structure and casing of the data being exposed or accepted over the wire. These types may have different naming conventions (e.g., snake_case for JSON fields) and might not always align one-to-one with our internal types.

When to Use Business Object Types vs. DTO Typesโ€‹

Use Business Object Types when you are working within an application and need to interact with the domain objects. The vast majority of hand-written code will use these types. Examples of this type of code include UI such as forms that interact with the business logic imlemented in the API. When in doubt, use Business Object Types.

Use DTO Types when interacting with external APIs through an application. This includes:

  • Response Serialization: Transforming internal Business Object Types into DTO Types before sending them in API responses. In most cases, this is handled by the generated mappers.
  • Custom Response handlers: Each service method has a generated response handler that runs the appropriate service method and serializes the response into a DTO. You can override this behavior by providing a custom response handler.

Mappersโ€‹

The mappers module exports generated mapper functions. These functions are responsible for mapping between Business Object Types and DTO (Data Transfer Object) types, both of which are generated from the API contract. The mapper functions guarantee correct transformations between these two sets of types, maintaining consistency between consumers and the external API contract.

Why Use Generated Mapper Functions?โ€‹

  • Consistency: Manually mapping between Business Object Types and DTO Types can lead to errors and inconsistencies. By generating the mapper types, we eliminate human error and ensure that the mappings always follow the API contract.

  • Maintainability: As the API evolves, regenerating the mapper types ensures that mappings between types are updated automatically. This significantly reduces the amount of manual work required when the API changes.

When to Use the Mapper Typesโ€‹

The generated client code contains default implementations for HTTP client services than can be used as-is. However, if you decide to hand-write custom network calls, there are several scenarios where you may need to interact with the mapper types directly:

  • Custom Request Builders: When manually creating API requests (e.g., outside of an autogenerated client or beyond simple fetch calls), use the mapper types to convert Business Object Types into the DTO (Data Transfer Object) types expected by the API.

  • Response Serialization: After receiving a response from the API, use the mapper types to convert DTO Types into Business Object Types that are easier to work with in your application logic.

  • Integration Testing: When testing the client-side code that integrates with the API, mapper types can help validate that outgoing and incoming data structures are correctly transformed, making it easier to verify behavior and catch mismatches.