.../articles/
Running a Mock Server with OpenAPI × Stoplight Prism

Running a Mock Server with OpenAPI × Stoplight Prism

2020.10.23

Making the Most of OpenAPI

We've been using OpenAPI for a while now, both for testing server-side APIs and for auto-generating frontend client code.

The Current Problem

Even with auto-generated client code in hand, it's still inconvenient to move forward with markup and integration work if there's no data to return. We've already generated types from the schema, so it feels wasteful to build things with placeholder data and then fix everything up later to match the real data.

There's also the common situation where, especially in the early stages of a project, the backend and frontend developers are each working in their own local environments. In that case, asking the frontend developer to "just run the API server locally" can be a high bar to clear. Sure, you could set up the programming language and framework locally, or use Docker, but having to do that kind of setup and verification every single time isn't ideal.

Since implementing the API tends to block frontend integration work no matter what, we think it would be ideal if the backend and frontend could start development at the same time, as soon as the OpenAPI schema is defined.

From OpenAPI to a Mock Server

One way to decouple backend and frontend implementation is to use a mock server.

As mentioned above, spinning up a local API server and preparing test data during the early markup and integration phase is a heavy burden, and preparing separate data just for a mock server isn't very efficient either. Ideally, we'd want something that works with just an OpenAPI definition file and a single command, with no complex setup required.

If we take "the OpenAPI schema is the source of truth" as a premise, the backend can implement to satisfy the schema, while the frontend can proceed with integration on the assumption that it will receive data based on that schema.

The tool that makes this possible is Stoplight's Prism.

Note: Stoplight develops several tools for streamlining API development, and Stoplight Studio, which lets you edit OpenAPI schemas via a GUI, is also worth checking out.

With Prism, you can easily spin up a mock server that validates requests and returns dummy responses, all based on an OpenAPI schema.

Using Prism, once the OpenAPI schema is ready, the frontend team could simply spin up a mock server with Prism and start implementing right away.


Trying Out Prism

Installation

Prism has several installation methods, but for frontend engineers, npm or yarn is probably the easiest to follow.

# Install however you prefer
$ npm install -g @stoplight/prism-cli
# The version we tested with was 4.1.0
$ prism mock --version
4.1.0

Starting the Mock Server

Let's start by running Prism against a sample OpenAPI schema file included in the repository. When we did, a list of endpoints appeared and the mock server started up successfully. The port number and other settings can be changed via options.

$ prism mock https://raw.githubusercontent.com/stoplightio/prism/master/examples/petstore.oas3.yaml
[18:26:51] › [CLI] …  awaiting  Starting Prism…
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/no_auth/pets?name=saepe
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/no_auth/pets
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/no_auth/pets/findByStatus?status=sold&status=available&status=pending&status=pending&status=sold&status=available&status=available&status=available&status=pending&status=pending&status=sold&status=sold&status=available&status=available&status=sold&status=pending&status=available&status=pending&status=pending&status=sold
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/no_auth/pets/findByTags?tags=aut&tags=id&tags=ut&tags=sapiente&tags=molestiae&tags=nostrum&tags=magni&tags=facere&tags=enim&tags=vero&tags=sit&tags=beatae&tags=consequatur&tags=facere&tags=unde&tags=eveniet&tags=aut&tags=et&tags=rem&tags=sint
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/no_auth/pets/421
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/no_auth/pets/801
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/pets
[18:26:52] › [CLI] ℹ  info      PUT        http://127.0.0.1:4010/pets
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/pets/findByStatus?status=available&status=available&status=available&status=sold&status=pending&status=pending&status=available&status=available&status=pending&status=available&status=available&status=sold&status=pending&status=sold&status=pending&status=available&status=available&status=available&status=sold&status=available
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/pets/findByTags?tags=modi&tags=laudantium&tags=ea&tags=nihil&tags=ab&tags=quod&tags=quis&tags=voluptatem&tags=quia&tags=et&tags=cum&tags=et&tags=dolor&tags=odit&tags=deleniti&tags=est&tags=voluptate&tags=molestias&tags=illo&tags=id
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/pets/785
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/pets/863
[18:26:52] › [CLI] ℹ  info      DELETE     http://127.0.0.1:4010/pets/973
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/pets/939/uploadImage
# Some lines omitted
[18:26:52] › [CLI] ▶  start     Prism is listening on http://127.0.0.1:4010

Let's hit a few endpoints and check the behavior.

  • GET http://127.0.0.1:4010/no_auth/pets?name=saepe

Accessing this endpoint returns the response defined in the schema's example.

$ curl -i 'http://127.0.0.1:4010/no_auth/pets?name=saepe'
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Content-type: application/json
Content-Length: 34
Date: Fri, 23 Oct 2020 09:39:42 GMT
Connection: keep-alive
Keep-Alive: timeout=5

[{"name":"a_name","photoUrls":[]}]

By the way, this endpoint requires the name query parameter, so if the parameter is missing, it returns a 422 validation error.

$ curl -i 'http://127.0.0.1:4010/no_auth/pets'
HTTP/1.1 422 Unprocessable Entity
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
content-type: application/problem+json
Content-Length: 376
Date: Fri, 23 Oct 2020 09:41:15 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"type":"https://stoplight.io/prism/errors#UNPROCESSABLE_ENTITY","title":"Invalid request body payload","status":422,"detail":"Your request is not valid and no HTTP validation response was found in the spec, so Prism is generating this error for you.","validation":[{"location":["query"],"severity":"Error","code":"required","message":"should have required property 'name'"}]}
  • GET http://127.0.0.1:4010/pets/785

The previous example was an endpoint that didn't require authentication, but this one returns 401 when accessed without credentials.

$ curl -i 'http://127.0.0.1:4010/pets/458'                                                                                                                   18:49:12
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
sl-violations: [{"location":["request"],"severity":"Error","code":401,"message":"Invalid security scheme used"}]
Content-type: application/json
Content-Length: 39
Date: Fri, 23 Oct 2020 09:49:46 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"code":-2147483648,"message":"string"}

As defined under security, adding the api_key header returns a normal response. It seems only the presence of the api_key is checked, not its value.

$ curl -i 'http://127.0.0.1:4010/pets/458' -H 'api_key: key'                                                                                                 18:51:54
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Content-type: application/json
Content-Length: 126
Date: Fri, 23 Oct 2020 09:52:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"id":2,"category":{"id":1,"name":"Felis"},"tags":[{"id":1,"name":"pet"}],"name":"Fluffy","status":"available","photoUrls":[]}
  • POST http://127.0.0.1:4010/no_auth/pets

The same applies to POST requests: if required fields are defined for the request body, Prism validates them too.

In the sample schema, this endpoint has no 2xx response defined, so it results in an error. The logic that determines the response status and content is explained in detail here.

$ curl -i -XPOST 'http://127.0.0.1:4010/no_auth/pets' -H 'content-type: application/json' -d '{"name": "cat", "photoUrls": []}'
HTTP/1.1 500 Internal Server Error
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
content-type: application/problem+json
Content-Length: 148
Date: Fri, 23 Oct 2020 10:05:32 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"type":"https://stoplight.io/prism/errors#NO_SUCCESS_RESPONSE_DEFINED","title":"No response in the range 200-299 defined","status":500,"detail":""}
  • Dynamically changing the response content

Prism has a dynamic option that, when specified, makes it return different content on every request. The values appear to be generated by Faker.js based on the property types defined in the schema.

$ prism mock -d https://raw.githubusercontent.com/stoplightio/prism/master/examples/petstore.oas3.yaml
$ curl 'http://127.0.0.1:4010/no_auth/pets?name=saepe'
[
  {
    "name": "sin",
    "photoUrls": [
      "et nostrud labore veniam ut",
      "laboris enim",
      "pariatur cupidatat ad est ullamco",
      "exercitatio",
      "non eiusmod laboris",
      "aute in",
# Truncated for brevity

Since it's dynamic — or really, random — fine-grained control over the behavior might be difficult. If you define the data types in detail, the generated values will match, so it's worth writing out the schema's Data Types as thoroughly as possible where you can. For example, use type: string plus format: email for an email address, or type: integer plus minimum: 1 for a table ID, and so on.

It would be even more useful if future options allowed finer control over how response data is generated.


Summary

From trying out Prism, it's easy to run, but fine-grained control over the response data seems difficult. Even so, having a mock server that guarantees behavior based on the OpenAPI schema seems like it can meaningfully reduce the extent to which API implementation blocks frontend work.

This is still in the concept stage, so we'd like to keep exploring with our frontend engineers and build an even better development workflow.

written by

.../article/

Articles

All articles

From Firebase to Vercel, Contentful to microCMS — a migration log written with Claude Code

From Firebase to Vercel, Contentful to microCMS — a migration log written with Claude Code

We moved our corporate site's hosting and CMS, and made it bilingual along the way. The constraints we only found by running against real data were more useful than the migration itself, so this post focuses on where we got stuck.

Can't Read POST Data with Firebase Functions × Remix?

Can't Read POST Data with Firebase Functions × Remix?

How to read POST data from a Remix action when running on Firebase Functions.

Generative AI for Executives and Leaders: An Approach to Self-Driven DX

Generative AI for Executives and Leaders: An Approach to Self-Driven DX

Building a structure where executives and leaders themselves can identify issues and evaluate solutions using generative AI. We introduce how combining this with our hands-on support dramatically improves both the quality and speed of digital transformation.

Deploying a Monorepo Next.js App (App Router) to AWS Amplify

Deploying a Monorepo Next.js App (App Router) to AWS Amplify

Notes on the obstacles we hit while deploying a Next.js app managed in a monorepo to AWS Amplify.

Keeping Production Running Smoothly with Remote Work and Online Meetings [Documentation]

Keeping Production Running Smoothly with Remote Work and Online Meetings [Documentation]

Many production companies have adopted remote work as a result of the pandemic, and we are one of them.

Designing an E-Commerce Site That Sells: How to Find Great Reference Examples

Designing an E-Commerce Site That Sells: How to Find Great Reference Examples

There is no single formula for e-commerce design that sells. Driving revenue requires a solid concept, and getting to that concept requires thorough research.

Productivity Tools We Recommend as a Production Company, Including Services That Work Well Solo

Productivity Tools We Recommend as a Production Company, Including Services That Work Well Solo

With remote work becoming the norm during the COVID-19 pandemic, our team now works from home most days of the week.

We Released Thought Recorder, a Figma Plugin for Keeping a Commit History of Your Designs

We Released Thought Recorder, a Figma Plugin for Keeping a Commit History of Your Designs

We hope this helps web designers who work in Figma. Read on for how to use it.

How to Build an E-Commerce Site, and Which Platforms We Recommend

How to Build an E-Commerce Site, and Which Platforms We Recommend

Shopping online for fashion, appliances, and even groceries is now routine. With the pandemic accelerating the shift, we receive a steady stream of questions about which platform to use and how much it costs.

Generating FastAPI Schema Classes from OpenAPI

Generating FastAPI Schema Classes from OpenAPI

We chose FastAPI, a relatively modern framework, for a Python API project. FastAPI can generate an OpenAPI definition from your backend code, but here we do the opposite: generating FastAPI schema classes from an OpenAPI definition prepared in advance.

View all articles

Contact us