OpenAPI: Design a reusable schema definition for GET, PUT, POST

Viewed 449

I am using OpenAPI 3.0 to design and implement a simple API to allow basic CRUD operations on database entities.

Let's assume an entity Pet with some client-given properties (name & age) and some generated properties (id & created):

components:
  schemas:
    Pet:
      type: object
      properties:
        id:
          type: string
        created:
          type: string
          format: date-time
        name:
          type: string
        age:
          type: integer

I want to specify REST endpoints to POST, GET, PUT (and if possible PATCH) a pet:

paths:

  /pets:
    post:
      operationId: createPet
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
      responses:
        "200":
          content:
            application/json:
              schema:
                type: boolean
 
  /pets/{id}:
    parameters:
      - name: id
        schema:
          type: string
        in: path
        required: true

    get:
      operationId: getPet
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"

    put:
      operationId: updatePet
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
      responses:
        "200":
          content:
            application/json:
              schema:
                type: boolean

    patch:
      operationId: alterPet
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
      responses:
        "200":
          content:
            application/json:
              schema:
                type: boolean

From a logical point of view, the following properties are required per endpoint:

  • GET: id, created, name, age
  • POST: name, age
  • PUT: name, age
  • PATCH: name or age (or none depending on implementation)

I see two major approaches here:

Approach 1: Leave all properties optional

The Pet DTO is used as a shell where all properties are optional as defined above. It is left to the server & client to check if the required properties are filled on an endpoint call. If non-required properties are set in a POST/PUT/PATCH request body, they will be ignored.

pros:

  • Single, simple, and symmetric DTO schema definition in the API specification.
  • Smooth integration of the "GET -> modify -> PUT" workflow on the client side (e.g., a React frontend with Formik).
  • Supports the PATCH endpoint out-of-the-box.

cons:

  • All properties are optional and it is very tedious to handle these string | undefined types in Typescript. This is especially annoying in the GET direction as we know that all properties will be filled as long as we retrieve the Pet DTO.
  • The implementational "pain" is spread over the server and all possible clients.

Approach 2: Define separate DTO schemas for all operations

We introduce a GetPet, PutPet, PostPet, and PatchPet with corresponging required lists in the API specification. Yes, PutPet and PostPet can be identical, but maybe we want to allow a modified GetPet to be used as PutPet to streamline the "GET -> modify -> PUT" workflow for the client.

Imagine the Pet entity having 5+ generated properties and 20+ client-given properties. We do not want to flat define 4 variations of each entity, but use some kind of inheritance instead. Is there a way to introduce a BasePet bearing all properties (set to optional) and let the 4 operation-specific DTOs extend it with just overriding the required list?

pros:

  • Easier implementation for client & server as optionality is clarified for all properties.
  • The "pain" is handled in the API specification.

cons:

  • The API specification grows in complexity. It is not clear to me if the desired inhertance is possible and how it is specified.
  • Scalability & maintainability of the API may suffer.

So my question on this topic: How can the inhertiance of the DTOs be specified for OpenAPI 3.0? Are there better alternatives? I am happy for all suggestions concerning these thoughts on an API for basic CRUD operations.

0 Answers
Related