This is the site's logo
This is the site's logo
Published on

Simple API Design with OpenAPI - 2

Authors

image1

The OpenAPI Specification is a standardized language for describing HTTP APIs, enabling developers to define their APIs in a consistent and easily understandable manner. It serves as a powerful tool that allows users to comprehend API functionalities, configure infrastructure, generate client code, and create test cases effectively. By adopting OpenAPI, you gain control over your APIs, facilitating seamless communication within and beyond your organization while comprehending the entire API lifecycle.

Moreover, OpenAPI forms an extensive ecosystem of tools that contribute to the development of robust and interactive RESTful API services. Building upon the foundation laid in the previous article of this series, 'Simple API Design With OpenAPI,' we now proceed to implement our users' service. In this article, we will delve into the user service model and explore its implementation further.

Following the previous article of the this serie of Simple API Design With OpenAPI in this article we are going to implement our users service. Let's take a look at our users service Model

Users Model

AML specifications can sometimes be overwhelming due to the numerous properties they may contain, making it challenging to remember all of them. However, YAML files have become the preferred choice for writing specifications in a declarative manner nowadays. They are widely used in popular software tools like Kubernetes, Ansible, Docker Compose, and more.

Thankfully, there is always documentation available to guide us on how to write these files effectively. This documentation proves to be invaluable when navigating through the complexities of YAML specifications.

To begin with our OpenAPI specification, let's start by creating the basic structure. I often use comments to enhance the file's understandability and to serve as a helpful guide while working on it. If you get lost somewhere or want to look at the final file this is the GitHub repo https://github.com/Grifo89/users-openapi

OAS Skeleton 🦴🦴

user.oas.yml
openapi: 3.0.3

#-------------------------------
#          METADATA
#-------------------------------
info:
servers:
tags:

#-------------------------------
#          COMPONENTS
#-------------------------------
components:

#-------------------------------
#             PATHS
#-------------------------------
paths:

#-------------------------------
#           SECURITY
#-------------------------------
security:

Those are the principal item object of our spec, first of all let's write the Metadata.

Metadata 📝

user.oas.yml
openapi: 3.0.3

#-------------------------------
#          METADATA
#-------------------------------
info:
    title: User API design
    description: Users API service specification
    version: 1.0.0

servers:
    -   url: http://localhost:8000
        description: Local development server

tags:
    -   name: Users
        description: User service

The above item properties are the ones I called metadata, this includes the info property where we specified the title, we give a description and the API version. This section is mandatory for a valid OAS (OpenAPI Specification).

Components 🧩🧩

Next, it's a good practice to follow for the components section. In this section we're going to declare reusable components whithin our OAS like endpoints `, schemas, errors, request bodies, securitySchemes and examples. These are the ones we are going to declare but there are many other you can find at the documentation

Components section could be one of the lagest in the OAS so we are going to break it down.

schemas

user.oas.yml
#-------------------------------
#          COMPONENTS
#-------------------------------
components:
#-------------------------------
  schemas:
    Error:
      type: object
      properties:
        message:
          type: string
      required:
        - message
    User:
      type: object
      required:
        - name
        - emailAdress
        - roles
      properties:
        name:
          type: string
        emailAdress:
          type: string
        role:
          type: array
          items:
            type: string
            enum:
            - admin
            - customer
            - developer
    userCreated:
      type: object
      allOf:
        - $ref: "#/components/schemas/User"
      properties:
        id:
          type: string
          format: uuid
#-------------------------------

The format to specify a schema in the components sections is the following although you can find more item properties at the documentation

schemas:
  schemaName:
    type: object # Usually
    properties:
      property1:
        type: property1Type
      property2:
        type: property2Type

Responses

user.oas.yml
#-------------------------------
#          COMPONENTS
#-------------------------------
components:
#-------------------------------
  NotFound:
    description: The specified resource was not found.
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Error'
  UnprocessableEntity:
    description: The payload contains invalid values.
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Error'

The format is the following:

responseName:
  description: Description of this reponse
  content:
    application/json: # There are many types of `, in this case is a json
      schema:
        # This follows the schema object we reviwed before.

Request Bodies

user.oas.yml
#-------------------------------
#          COMPONENTS
#-------------------------------
components:
#-------------------------------
  requestBodies:
      UserCreate:
      description: Registers a new user
      required: true
      content:
          application/json:
          schema:
              allOf:
              - $ref: "#/components/schemas/User"
              - type: object
              properties:
              password:
                  type: string
                  format: password
      UserUpdate:
      description: Updates an active user
      required: true
      content:
          application/json:
          schema:
              $ref: "#/components/schemas/User"
#-------------------------------

The format is the same as the responses one.

Security schemes

user.oas.yml
#-------------------------------
#          COMPONENTS
#-------------------------------
components:
#-------------------------------
  securitySchemes:
    openId:
      type: openIdConnect
      openIdConnectUrl: https://coffeemesh-dev.eu.auth0.com/.well-known/openid-configuration
    oauth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://coffeemesh-dev.eu.auth0.com/oauth/token
          scopes: {}
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

Security schemes are specific to each authentication and authorization flow, but in the lastest version the securitySchemes object follow a pattern; the above is an example but if you want more information you can relay on the documentation.

Parameters

user.oas.yml
#-------------------------------
#          COMPONENTS
#-------------------------------
components:
#-------------------------------
  parameters:
    UserId:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid

The item object within the paramaters that defines the parameter type is the item object in which could receives either, path, query or cookie

Finally to finish the component object the last item object is examples, this porperty is required in order to make funtional our mock server that we're going to run afterwards to implement our TDD (Test Driven Design) and work in parallel with the Front-End team.

user.oas.yml
#-------------------------------
#          COMPONENTS
#-------------------------------
components:
#-------------------------------
  examples:
      arrayOfusers:
        summary: Array of users
        value:
        - id: "82195850-cec4-42bd-b216-3cfa598aa4af"
          name: "user1"
          emailAdress: "test@email.com"
          roles: ["admin"]
        - id: "82195850-cec4-42bd-b216-3cfa598aa4af"
          name: "user2"
          emailAdress: "test2@email.com"
          roles: ["customer"]
        - id: "82195850-cec4-42bd-b216-3cfa598aa4af"
          name: "user3"
          emailAdress: "test3@email.com"
          roles: ["admin"]
        - id: "82195850-cec4-42bd-b216-3cfa598aa4af"
          name: "user4"
          emailAdress: "test4@email.com"
          roles: ["developer"]
        - id: "82195850-cec4-42bd-b216-3cfa598aa4af"
          name: "user5"
          emailAdress: "test5@email.com"
          roles: ["developer"]
      sigleUser:
        summary: Single User
        value:
          id: "82195850-cec4-42bd-b216-3cfa598aa4af"
          name: "user5"
          emailAdress: "test5@email.com"
          roles: ["developer"]

Huug! it was a lot, as you can notice there are a lot of properties but don't be afraid, you don't need to remember each one, you just need to know the general structure and de minimum required structure.

Paths 🛣️

paths is the object where we specified the endpoints URIs, each URI is a sub item object and each URI has a parameters, tags, summary, and examples.

user.oas.yml
#-------------------------------
#             PATHS
#-------------------------------
paths:
#-------------------------------
  /users:
    get:
      tags:
      - Users
      operationId: getUsers
      summary: List of users
      responses:
        '200':
          description: Successfull operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
              examples:
                arrayOfusers:
                  $ref: "#/components/examples/arrayOfusers"
        '404':
          $ref: '#/components/`/NotFound'
        '422':
          $ref: '#/components/`/UnprocessableEntity'

    post:
      tags:
        - Users
      operationId: createUser
      summary: Creates a new User
      requestBody:
        required: true
        $ref: "#/components/requestBodies/UserCreate"
      responses:
        '201':
          description: User Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/userCreated'
              examples:
                singleUsers:
                  $ref: "#/components/examples/sigleUser"
        '404':
          $ref: '#/components/`/NotFound'
        '422':
          $ref: '#/components/`/UnprocessableEntity'
#-------------------------------
  /users/{id}:
    get:
      parameters:
        - $ref: '#/components/parameters/UserId'
      tags:
        - Users
      operationId: getUser
      summary: Fetch user by id
      responses:
        '200':
          description: Ok
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/userCreated'
              examples:
                singleUsers:
                  $ref: "#/components/examples/sigleUser"
        '404':
          $ref: '#/components/`/NotFound'
        '422':
          $ref: '#/components/`/UnprocessableEntity'

    put:
      parameters:
        - $ref: '#/components/parameters/UserId'
      tags:
        - Users
      summary: Update active user
      operationId: updateUser
      requestBody:
        required: true
        $ref: "#/components/requestBodies/UserCreate"
      responses:
        '201':
          description: User Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/userCreated'
              examples:
                singleUsers:
                  $ref: "#/components/examples/sigleUser"
        '404':
          $ref: '#/components/`/NotFound'
        '422':
          $ref: '#/components/`/UnprocessableEntity'

    delete:
      parameters:
        - $ref: '#/components/parameters/UserId'
      tags:
        - Users
      operationId: deleteUser
      responses:
        '204':
          description: The resource was deleted successfully

The operationId is a property that adds an alias to each endpoint to point to later in other objects like security.

Security 🔒🔐

user.oas.yml
#-------------------------------
#           SECURITY
#-------------------------------
security:
  - oauth2:
    - deleteUser
    - updateUser
    - getUser
    - getUsers
    - createUser
  - openId:
    - deleteUser
    - updateUser
    - getUser
    - getUsers
    - createUser

Conclusion 🌟🔚🎉

I'm sure you're thinking it's been a lot just for one API service, but this OAS is supposed to be specify for each service and this spec was thought taking into account microservice architecture, that means, each service could be isolated to each other.

In conclusion, implementing the OpenAPI Specification (OAS) for our service has been a transformative experience. The comprehensive documentation and standardized interface have not only streamlined our development process but also fostered better collaboration between teams. With a clear understanding of the API contract, both internal and external stakeholders can now communicate effectively, leading to faster integration and reduced time-to-market for new features. As we embrace the benefits of this well-defined specification, we are confident in our ability to adapt and scale our service to meet the ever-changing needs of our users and industry. By adhering to the OAS principles, we ensure a robust and future-proof foundation for our service, enabling us to deliver high-quality solutions and exceptional experiences to our customers. Moving forward, we remain committed to refining and expanding our OAS to drive continuous improvement and innovation in our product offerings. 🌟🚀