DEV Community

Yos Riady
Yos Riady

Posted on • Edited on

Schema-First API Design

Find more interesting articles at yos.io

This guide introduces you to the realm of Schema-First API design and how to get started with the OpenAPI ecosystem.

You’re building an API.

You develop a backend service with a few endpoints and deploy it to production. You publish several official language-specific API clients as well as an API documentation. The day ends on a happy note.

The following day, a new feature is being added the API. You have to:

  • Update the server implementation to support the new feature.
  • Update all client libraries (one SDK for each supported platform and language.)
  • Update the documentation.
  • All the above must be consistent with each other.
  • Also, the frontend team is blocked until your backend API is complete.

You let out a heavy sigh.

Is there a better way to do this?

For each change made to your API, several steps must be performed to keep all development artifacts consistent with each other. Officially supported API client SDKs must support the latest HTTP API endpoints. The documentation must also be consistent and true to the actual implementation.

Each of these steps are labour intensive and error prone. Thus, it makes sense for us to automate these tasks away so we can focus on actually building features. The question is: how?

Instead of going straight to development, we start with an API specification.

Schema-first API design

The Schema-first API design approach advocates for writing your API definition first in one of many API Specification languages before writing any code. Writing an API specification has several benefits:

Improved API design

API specifications represent a contract for an API.

With an API specification and tools such as Swagger UI, you can visualize your API so that other developers and stakeholders can learn and give design feedback early on. You can even run a mock service based on an API spec that other teams can immediately interact with.

Identifying issues in the design before writing any code is a much more efficient and streamlined approach than doing so after an implementation is already in place.

Iterate faster across multiple teams

With an API specification, development projects that involve multiple teams (backend, web, mobile, etc.) can proceed much faster than traditional methods would allow.

Frontend teams can immediately start building components regardless of whether or not the backend components are ready, because we can generate and run mock services based on an API specification. Teams can work in parallel, without being blocked by another team's progress.

Create tests for your API

Your API specification provides a contract that describes what a successful response will look like when someone calls your API. This contract can be re-purposed to generated test cases which can drastically decrease the amount of effort needed for testing your APIs. You can create functional tests and run mock services from your API spec.

Generate code and documentation

A schema-first approach creates a single source of truth that can be used to generate all kinds of development artifacts. Well-defined API specs can be used to automatically scaffold an API, generate an API reference, and client SDKs that communicate with the API.

Summary

Adopting schema-first API design has a small initial cost, but the benefits gained from it are significant.

API Specification Languages

API Specification Languages defines a language-agnostic, standard representation of your REST APIs. Examples of API specification languages include OpenAPI, API Blueprint, and RAML.

In the next section, we'll look at the OpenAPI specification language and the tooling surrounding it.

The OpenAPI Specification Language

Overview

APIs form the connecting glue between modern applications. Nearly every application uses APIs to connect with corporate data sources, third party data services or other applications. Creating an open description format for API services that is vendor neutral, portable and open is critical to accelerating the vision of a truly connected world.

OpenAPI is a JSON/YAML-based API specification language and framework for describing, producing, consuming, and visualizing RESTful web services. The overarching goal of OpenAPI is to enable client and documentation systems to update at the same pace as the server.

What's the difference between OpenAPI and Swagger?

OpenAPI is the official name of the specification. The development of the specification is fostered by the OpenAPI Initiative, which involves more the 30 organizations from different areas of the tech world — including Microsoft, Google, IBM, and CapitalOne. Smartbear Software, which is the company that leads the development of the Swagger tools, is also a member of the OpenAPI Initiative, helping lead the evolution of the specification.

Swagger is the name associated with some of the most well-known, and widely used tools for implementing the OpenAPI specification. The OpenAPI 3.0 specification is based on the Swagger 2.0 specification. The Swagger toolset includes a mix of open source, free, and commercial tools, which can be used at different stages of the API lifecycle.

OpenAPI offers more than just a specification language, other tools in the ecosystem include:

  • Swagger Editor: Swagger Editor lets you edit OpenAPI specifications in YAML inside your browser and to preview documentations in real time.
  • Swagger UI: Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from an OAS-compliant API.
  • Swagger Codegen: Allows generation of API client libraries (SDK generation), server stubs and documentation automatically given an OpenAPI Spec.
  • Swagger Inspector: API Inspection tool that lets you generate OpenAPI definitions from existing API

Getting Started

An OpenAPI Specification allow you to describe an API including (among other things):

  • General information about the API
  • Available paths (e.g. /resources)
  • Available operations on each path (e.g. GET /resources/{id})
  • Input/Output for each operation

Here is a minimal OpenAPI spec in YAML:

openapi: 3.0.0

info:
  title: Sample API
  description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
  version: 0.1.9

servers:
  - url: http://api.example.com/v1
    description: Optional server description, e.g. Main (production) server
  - url: http://staging-api.example.com
    description: Optional server description, e.g. Internal staging server for testing

paths:
  /users:
    get:
      summary: Returns a list of users.
      description: Optional extended description in CommonMark or HTML.
      responses:
        '200':    # status code
          description: A JSON array of user names
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
Enter fullscreen mode Exit fullscreen mode

There are 8 sections at the root level in the OpenAPI 3.0 spec. There are many nested objects within these root level objects, but at the root level, there are just these objects:

  • openapi: Required. The semantic version number of the OpenAPI Specification version that the OpenAPI document uses (e.g. 3.0.0.)
  • info: Required. Provides metadata about the API.
  • servers: An array of Server Objects, which provide connectivity information to a target server.
  • components: An element to hold various schemas for the specification.
  • security: A declaration of which security mechanisms can be used across the API.
  • tags: A list of tags used by the specification with additional metadata.
  • externalDocs: Additional external documentation.
  • paths: Required. The available paths and operations for the API.

You can use the online Swagger editor to follow along!

Let's look at each of the above sections briefly.

The info section

The info object provides metadata about the API.

info:
  title: Sample Pet Store App
  version: 1.0.1
  description: This is a sample server for a pet store.
  termsOfService: http://example.com/terms/
  contact:
    name: API Support
    url: http://www.example.com/support
    email: support@example.com
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
Enter fullscreen mode Exit fullscreen mode

Remember to try it on the online Swagger editor!

The servers section

The servers section specifies the API server and base URL. You can define one or several servers, such as production and sandbox.

servers:
  - url: https://my-api.com
    description: Production server
  - url: https://beta.my-api.com
    description: Beta server
  - url: https://some-other.my-api.com
    description: Some other server
Enter fullscreen mode Exit fullscreen mode

On Swagger UI, users will be able to choose the servers to send requests to from this predefined list:

The components section

Components are a set of reusable objects for the rest of the API such as:

Schema objects

JSON-schema based definition of input and output data types.

The global components/schemas section lets you define common data structures used in your API. They can be referenced via $ref whenever a schema is required – in parameters, request bodies, and response bodies.

For example, this JSON object:

{
  "name": "Garfield",
  "type": "Cat"
}
Enter fullscreen mode Exit fullscreen mode

can be represented as:


components:
  schemas: # Schema object definition
    Pet:
      type: object
      properties:
        name:
          type: string
        petType:
          type: string
      required:
      - name
      - petType
Enter fullscreen mode Exit fullscreen mode

and referenced elsewhere with $ref: '#/components/schemas/Pet'.

Learn more about OpenAPI data models.

Response objects

Expected responses for different HTTP response codes of an operation.

components:
  responses:
    ListPetsSuccessResponse:
      description: A an array response with headers
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: '#/components/schemas/Pet'
          headers:
            X-Rate-Limit-Limit:
              description: The number of allowed requests in the current period
              schema:
                type: integer    
Enter fullscreen mode Exit fullscreen mode

Parameter objects

Operations can have parameters passed via URL path (/users/{userId}), query string (/users?role=admin), headers (X-CustomHeader: Value) or cookies (Cookie: debug=0). You can define the parameter data types, format, whether they are required or optional, and other details:

# A path parameter of string value

name: username
in: path
description: username to fetch
required: true
schema:
  type: string
Enter fullscreen mode Exit fullscreen mode
# A header parameter with an array of 64 bit integer numbers:
name: token
in: header
description: token to be passed as a header
required: true
schema:
  type: array
  items:
    type: integer
    format: int64
Enter fullscreen mode Exit fullscreen mode

Example objects

Here is an example of the example keyword in a response body:

responses:
  '200':
    description: A cat object.
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Pet'   # Reference to an object
        example:
          # Example properties of the referenced object
          name: Garfield
          type: Cat
Enter fullscreen mode Exit fullscreen mode

Other component objects

You can also define Security Scheme, Header, Link (hypermedia), and Callback (webhook) objects in the components section.

Components summary

The use of components is entirely optional. It's most useful for storing any frequently used domain objects in a reusable way.

All objects defined within the components object will have no effect on the API unless they are explicitly referenced from properties outside the components object.

The security section

The security object specifies the security or authorization protocol used when submitting requests. OpenAPI supports the following authentication methods:

  • HTTP authentication: Basic, Bearer, and so on.
  • API key as a header or query parameter or in cookies
  • OAuth 2
  • OpenID Connect Discovery

At the root level of your OpenAPI document, add a security object that defines the global method we’re using for security:

security:
- API-Key: []
Enter fullscreen mode Exit fullscreen mode

In the components object, we add a securitySchemes object that defines details about the security scheme we’re using:

components:
  securitySchemes:
    API-Key:
      type: apiKey
      description: "The API authorizes requests through an API key in your header."
      name: X-API-Key
      in: header
Enter fullscreen mode Exit fullscreen mode

The tags and externalDocs section

Imagine if you had an API with 50+ paths to describe. You would want to organize them into logical groups for users to navigate. The tags object provides a way to group the paths (endpoints) in the Swagger UI display.

Define your tags at the root level:

tags:
  - name: Products
    description: Handles product information.
  - name: Orders
    description: Operations for processing purchases and orders.
Enter fullscreen mode Exit fullscreen mode

You can then use any defined tags in individual operations:

paths:
 ...
 /products:
   get:
     ...
     tags:
     - Products
     ...
Enter fullscreen mode Exit fullscreen mode

This will group operations with the same tags in Swagger UI:

Here’s an example of an externalDocs object:

externalDocs:
  description: Product documentation for the API
  url: https://myapi.com/docs
Enter fullscreen mode Exit fullscreen mode

In Swagger UI, this link appears after the API description along with other info about the API.

The paths section

In OpenAPI terms, paths are endpoints (resources), such as /pets or /products/summary/, that your API exposes, and operations are the HTTP methods used to manipulate these paths, such as GET, POST or DELETE.

First, let's list out the paths of our API:

paths:
  /products:
    get:

  /orders:
    post:
Enter fullscreen mode Exit fullscreen mode

Each path object (/products.get, /orders.post) is an operation. Here'a full example:

paths:
  /products/{id}:
    get:
      tags:
        - Products
      summary: Gets a product by ID.
      description: >
        A detailed description of the operation.
        Use markdown for rich text representation,
        such as **bold**, *italic*, and [links](http://swagger.io).
      operationId: getProductById
      parameters:
        - name: id
          in: path
          description: Product ID
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
      externalDocs:
        description: Learn more about product operations provided by this API.
        url: http://api.example.com/docs/product-operations/
Enter fullscreen mode Exit fullscreen mode

Each operation documents any parameters for the operation, the different kinds of responses, and other metadata. You can reference common data structures you've defined in the components section here.

OpenAPI summary

That's all you need to get started! Give it a try!

Once written, an OpenAPI specification and Swagger tools can drive your API development further and save significant amounts of time and effort in the long term:

  • Use Swagger Codegen to generate a server stub for your API. The only thing left is to implement the server logic – and your API is ready to go live!
  • Use Swagger Codegen to generate client libraries for your API in over 40 languages.
  • Use Swagger UI to generate interactive API documentation that lets your users try out the API calls directly in the browser.
  • And more!

In closing

The Schema-first API design approach advocates for writing your API definition first in one of many API Specification languages before writing any code.

Adopting schema-first API design has a small initial investment and learning curve, but the benefits gained from it are significant. Writing an API specification poses many benefits such as improved API design, faster iteration, and automated code & documentation generation.

Appendix

What if I already have an API?

If you already have your API implemented (at least partially), you can make API calls to your API from Swagger Inspector.
to automatically generate the OpenAPI file for any end point you call.

Additional Reading

Here are some recommended resources for further learning:

Find more interesting articles at yos.io

Top comments (1)

Collapse
 
unchase profile image
Chebotov Nickolay

Also you can try to use The Unchase OpenAPI (Swagger) Connected Service is a Visual Studio 2017/2019 extension to generate C# (TypeScript) HttpClient (or C# Controllers) code for OpenAPI (formerly Swagger) web services with NSwag. Simply put, it is like kind old Add Service Reference for WCF or Add Web Reference for WSDL, but for JSON (YAML) API with customization of code generation like in NSwagStudio.