DEV Community

Cover image for Develop Frontend Apps Faster with OpenAPI 3.0 and Prism Mock Server
Raphael Jambalos
Raphael Jambalos

Posted on

Develop Frontend Apps Faster with OpenAPI 3.0 and Prism Mock Server

One aspect of dev work that usually has friction is between API engineers and frontend developers. The frontend developer usually has to wait for an API endpoint to be finished before he can integrate it into his work. Even after he has done so, the API engineer might change the endpoint and the frontend developer has to revisit the integration. This leads to wasted time re-coding and more frustrated frontend developers.

Recap

In the previous post, we looked at the API-First Design process and applied the concepts that we learned to our simple loyalty application. We also produced an API sketch that listed each endpoint and top-level details about them.

Alt Text

This Post

In this post, we take our API sketch and turn it into an OpenAPI 3.0 definition. This document describes in finer detail how each of our endpoints operates: what the properties should be in the requestBody/responseBody, should there be query or path parameters, etc.

By having both API engineers and frontend developers start with planning the API, a lot of thought about the frontend-backend interaction has been done upfront. The API definition serves as a "contract" between both developers on how the API should operate. By working on this "contract" before any development, we minimize changes to the API and it guides how both developers implement their projects.

[1] Let's dive right in! 🌊

Before we get bogged down in too much concept, let's get our hands dirty writing OpenAPI definitions. On a separate tab, open up the Swagger editor in your browser. The swagger editor allows you to conveniently create OpenAPI definitions using your browser.

(1.1)

Empty the default contents of the left side of the Swagger editor and place this snippet instead:

openapi: 3.0.0 
info:
  title: Loyalty Card API
  version: "0.1"
paths: {}
Enter fullscreen mode Exit fullscreen mode

The snippet declares the version of the OpenAPI definition and some top-level information about your API. We intentionally left the paths key to have an empty value so we don't have an error.

As you type the visual documentation on the right updates. It also informs you of syntax errors in your API definition. Right now, it should look like this:

Alt Text

(1.2)

Next, let's define schemas for our API. As we learned in the previous post, standard endpoints define a consistent interface centered around resources in your API (i.e Create a Transaction, Read one transaction, Read all transactions, Edit transaction, etc).

In the schema section, we define how these resources look like: what properties it has and what data type each property is. At this point, it might be tempting to say that the resource should look identical to the database schema. It is not. We can omit some attributes (i.e we opt not to show each transaction's approval_code) or even have the transaction resource not represented by a transaction table altogether.

While at the start the transaction API and the transaction database table might be similar, over time the divergence between them can be quite huge.

components:
  schemas:
    Transaction:
      type: object
      properties:
        id:
          type: string
        amount:
          type: number
        equivalent_points:
          type: number
        card_id:
          type: string
        partner_id:
          type: string
Enter fullscreen mode Exit fullscreen mode

Your API should now look like this:

Alt Text

(1.3)

Now, let's get to the meat 🥩 of the API definitions: paths. Paths define the URL paths of our API endpoints. Let's replace the line paths: {} with the code snippet below.

# remember to replace paths: {}
paths:
  /transactions:
    post:
      summary: create transaction
      tags:
        - Transactions
      requestBody:
        content:
          application/json:
            schema: 
              $ref: '#/components/schemas/Transaction'
      responses:
        '201':
          description: Created user
          headers:
            Location:
              schema:
                type: string
Enter fullscreen mode Exit fullscreen mode

In the code snippet, we defined a path: POST /transactions to create a transaction.

  • summary - A short info about the endpoint
  • tags - The documentation of the right-side side
  • requestBody - For each type of content, we can define a different schema. In this example, we have the application/json content
  • responses - For each type of response type (HTTP 201 is "object created", HTTP 500 is "internal server error", and so on), we can define different response formats. In this example, if the requests results in HTTP 201, we return an empty response with the header "Location"

(1.4)

Next, we define the GET /transactions path. Make sure to add this snippet right below the code snippet in 1.3.

# add this under paths
  # add this under /transactions
    get:
      summary: get all transactions
      tags:
        - Transactions
      parameters:
        - name: "partner_id"
          in: "query"
          description: ""
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Transaction'
Enter fullscreen mode Exit fullscreen mode

This code snippet is a bit different. Instead of defining a request body, we defined a query parameter instead: /transactions?partner_id=10. The query parameter allows your frontend to get all transactions posted by a specific partner.

Since this is a GET request, we expect to get something in return. In the responses, we see that if the request is successful (HTTP 200), we return an array of transaction objects.

(1.5)

Once we can create transactions (1.3) and view all transactions (1.4), we need to have a way to view individual transactions. In the code snippet below, that's exactly what we are doing:

# add this under paths
  /transactions/{id}:
    parameters:
      - schema:
          type: integer
        name: id
        in: path
        required: true
    get:
      summary: View Transaction
      tags:
        - Transactions
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Transaction'
Enter fullscreen mode Exit fullscreen mode

In this snippet, we used a path parameter instead to define the id of the transaction we are viewing. The response is similar to 1.4 except that it instead returns a single object instead of an array of objects.

The right side of our editor should now look like this:

Alt Text

At this point, you might be wondering why I didn't include all the other resources in the API sketch. That would make the blog post very large. I chose to instead focus on the transaction resource. I leave creating the API definition for the other resources as a homework for you. If you get stuck or just want a quick reference, the full Open API 3.0 definition for the simple loyalty application is found at this Github Gist

Alt Text

[2] Mock Server with Prism

In this step, we will use the API definition we created in step 1 to run a mock server locally. For this, we will use Prism, a NodeJS CLI utility to run mock servers.

(2.1)

First, install prism.

npm install --global @stoplight/prism-cli
Enter fullscreen mode Exit fullscreen mode

(2.2)

Save the file locally as "blog_api.oas.yml" and cd to that directory in your command line. We added the .oas extension to signify that it is a YAML file in the OpenAPI format.

(2.3)

Now, let's run the Prism mock server:

prism mock -p 8080 ./blog_api.oas.yml
Enter fullscreen mode Exit fullscreen mode

Your CLI should look like this:

Alt Text

Now, trying typing this on your browser: http://127.0.0.1:8080/transactions/495. You should see a very basic response:

Alt Text

You now have a fully operational mock server at your disposal. No need to wait for the API engineer to develop the business logic: your frontend developer can get started coding the frontend right away!

[3] Additional Perks

If that wasn't enough to convince you to do API-First Design, here are a few more perks. These perks are features of the Swagger editor that we used in the first 2 steps.

[1] Pre-generate your backend

API engineers can pre-generate their whole API backend system using just the API definition. This saves a lot of time setting up paths and routing for the application.

Alt Text

The generated application does not come with business logic. It is composed of stubbed routes that return sample responses. But this scaffolding goes a long way in getting you up and running quickly

[2] Pre-generate an SDK

Your API engineers can also pre-generate an SDK (Software Development Kit). SDKs help your end-users interact with your system by providing a software package instead of directly calling your API endpoints. This saves them the boilerplate work of validating request/responses, handling error codes, etc.

Alt Text

The SDK package generated will not be ready for use by your end-users. You will still have to do some more coding. But at least it gets you halfway there already.

Finish!

With our OpenAPI 3.0 Definition, we have created a mock server that allows our frontend developer to integrate API endpoints without waiting for the API engineer to finish the business logic. The dependency between them is minimized and harmony is restored with the team!

We also looked at code pre-generation as a way to help the API engineers create the API backend and SDK faster.

Special Thanks

Special thanks to Allen for making my posts more coherent. This blog post is also made possible by the authors below who have made learning APIs a joy.

Photo by Julia Joppien on Unsplash

Top comments (0)