Go from a single YAML file to a running mock server that answers like your real API — including the ugly moments when things go wrong.

Imagine your team is building a mobile app and a backend at the same time. The app needs data to show on screen, but the backend does not exist yet. So the app developer waits, or hard-codes fake JSON in ten different places and later has to rip it all out. Both options waste time.

There is a better way. If you write down what your API will look like in an OpenAPI spec, a small tool can read that file and pretend to be the server. Your app talks to the pretend server today, and switches to the real one later by changing a single URL.

Writing the spec first lets both teams work at the same time, so you ship sooner.

By the end of this tutorial you will have:

  • A small OpenAPI file that describes a “Tasks” API.
  • A mock server running on your computer at localhost:4010.
  • Realistic fake data that changes on every request.
  • Error responses (404 and 422) that you can trigger on demand.

No backend code is needed. You should be comfortable typing a few commands in a terminal, and that is all. It takes about 10 minutes.

What is a mock API?

A mock API is a fake server. It listens for requests just like the real one, but instead of talking to a database it sends back pre-made answers. Think of a film set: the buildings look real from the camera’s side, but there is nothing behind the front wall. That is perfectly fine when all you need is the picture.

Here is the whole idea in one picture. You write the spec, the mock server reads it, and your app talks to the mock server.

The spec is the single source of truth. The mock server is generated from it.

The tool we will use is Prism, a free, open-source mock server from Stoplight. You do not install it permanently — npx downloads and runs it on demand.

What is an OpenAPI spec?

An OpenAPI spec is a text file (usually YAML) that describes an API in a way both humans and tools can read. It answers three questions: Which URLs exist? What can I send to them? What comes back? It works like a restaurant menu: it lists every dish, but it does not contain the kitchen.

The picture below shows a tiny piece of a spec, with each part labelled. You will see all four of these ideas again in Step 1.

The same four ideas appear in every spec: path, method, status code and schema.
WordPlain-English meaningExample
PathThe address of something/tasks
MethodWhat you want to do with itGET = read, POST = create
Status codeA number that says how it went200 OK, 404 not found
SchemaThe shape of the dataa task has a title (text) and a priority (1–5)
JSONThe text format the data travels in{"title": "Buy milk"}

Before you start

You need two things on your computer: Node.js (version 18.16 or newer — the current LTS release is a safe choice) and a terminal. Node comes with npx, the tool that will run Prism for us. Check that Node is installed:

node --version

If you see a number like v20.11.0, you are ready. If the command is not found, install the current LTS version from nodejs.org first. Now make a folder for the project:

mkdir tasks-mock
cd tasks-mock

Write the spec

Create a file called openapi.yaml inside the folder and paste in the spec below. It describes three things: list tasks, create a task and fetch one task. It is long only because it is complete — most of it repeats a simple pattern.

openapi.yaml
openapi: 3.0.3
info:
  title: Tasks API
  version: 1.0.0
servers:
  - url: http://localhost:4010

paths:
  /tasks:
    get:
      summary: List tasks
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 50, default: 10 }
      responses:
        '200':
          description: A list of tasks
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Task' }
              example:
                - id: 6f1c1d0e-5b7a-4f57-9d0e-2a1f3f0b9a11
                  title: Write the launch email
                  status: doing
                  priority: 2
    post:
      summary: Create a task
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/NewTask' }
      responses:
        '201':
          description: The task was created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Task' }
              example:
                id: 0b5f0a3e-7c2d-4c39-8f0f-6d6c1a2b3c44
                title: Book the venue
                status: todo
                priority: 3

  /tasks/{taskId}:
    get:
      summary: Get one task
      parameters:
        - name: taskId
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: The task
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Task' }
              example:
                id: 6f1c1d0e-5b7a-4f57-9d0e-2a1f3f0b9a11
                title: Write the launch email
                status: doing
                priority: 2
        '404':
          description: No task with that id
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              example:
                code: not_found
                message: No task with that id

components:
  schemas:
    NewTask:
      type: object
      required: [title]
      properties:
        title: { type: string, minLength: 1 }
        priority: { type: integer, minimum: 1, maximum: 5 }
    Task:
      type: object
      required: [id, title, status]
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        status: { type: string, enum: [todo, doing, done] }
        priority: { type: integer, minimum: 1, maximum: 5 }
    Error:
      type: object
      required: [code, message]
      properties:
        code: { type: string }
        message: { type: string }

Do not worry about memorising it. Here is how to read it, top to bottom:

  1. info and servers — the name of the API and where it will live. Prism ignores the URL here, but tools that read the spec use it.
  2. paths — the list of URLs. We have /tasks and /tasks/{taskId}. The curly braces mean “any value goes here”, like a task id.
  3. get and post — the methods each URL supports. GET /tasks lists tasks; POST /tasks creates one.
  4. parameters — extra inputs. limit is an optional number in the URL (?limit=5) that must be between 1 and 50.
  5. responses — every answer the API can give, keyed by status code. '200' is success. '404' is “not found”. We write the codes in quotes because YAML would otherwise treat them as numbers.
  6. example — the exact JSON we want the mock to return. This is what makes the mock feel real.
  7. components → schemas — reusable shapes. Task is defined once and reused with $ref so we never repeat ourselves.

Start the mock server

In the same folder, run this single command:

npx @stoplight/prism-cli mock openapi.yaml

The first time, npx asks to download the package — say yes. After a few seconds you will see something like this (the exact text differs a little between versions):

[CLI] …  awaiting  Starting Prism…
[HTTP SERVER] get /tasks ✔  success  Prism is listening on http://127.0.0.1:4010
[HTTP SERVER] post /tasks
[HTTP SERVER] get /tasks/{taskId}

Prism listed every route it found in your spec, which is a good sign that the file is valid. Leave this terminal window open — the server runs for as long as the command runs. Press Ctrl + C to stop it.

What happens when a request reaches Prism? It follows the same five steps every time. Knowing them will make the rest of the tutorial easy to understand.

What Prism does with every request. Step 3 is why bad requests get a 422 instead of a fake success.

Make your first request

Open a second terminal window (keep Prism running in the first one) and ask for the list of tasks:

curl -i http://127.0.0.1:4010/tasks

The -i flag also prints the response headers, which is handy for seeing the status code. You should get back the example you wrote in the spec:

HTTP/1.1 200 OK
content-type: application/json

[{"id":"6f1c1d0e-5b7a-4f57-9d0e-2a1f3f0b9a11","title":"Write the launch email","status":"doing","priority":2}]

That is your first mock response. Try fetching a single task, and creating a new one:

curl -i http://127.0.0.1:4010/tasks/6f1c1d0e-5b7a-4f57-9d0e-2a1f3f0b9a11

curl -i -X POST http://127.0.0.1:4010/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "Book the venue", "priority": 3}'

The POST returns 201 Created with the example task from your spec. Notice that it always says “Book the venue”, even if you sent a different title. This is normal: a mock does not store anything. It only checks that your request is valid and then replies with the prepared answer.

Get realistic data

Prism can choose a response in three different ways. So far we have used the first one.

Three ways to decide what the mock sends back. You can mix them freely.

To switch on dynamic mode, stop Prism with Ctrl + C and start it again with the -d flag:

npx @stoplight/prism-cli mock -d openapi.yaml

Now call GET /tasks a few times. Instead of your example, Prism reads the schema and generates data that fits it:

curl -s http://127.0.0.1:4010/tasks

# first call  (yours will differ)
[{"id":"a3f0c2d4-…","title":"quis nostrud","status":"done","priority":4}]

# second call
[{"id":"91be77aa-…","title":"lorem ipsum","status":"todo","priority":1}]

Look at how each rule in the schema shaped the data: format: uuid made a real-looking id, enum picked one of the three allowed statuses, and minimum/maximum kept the priority between 1 and 5. The richer your schema, the more believable the fake data. Notice too that dynamic mode ignores your example values completely and works only from the schema.

You can also ask for dynamic data on a single request, without restarting Prism, by adding a header: Prefer: dynamic=true.

Try the failure modes

Real APIs fail. Servers say “not found”, and users send bad data. A good mock lets you see those moments before launch, so your app never shows a blank screen or a spinner that never stops.

Force a specific response with Prefer

Add a Prefer header to ask for a status code that is documented in your spec. We documented a 404 for “get one task”, so:

curl -i -H "Prefer: code=404" \
  http://127.0.0.1:4010/tasks/6f1c1d0e-5b7a-4f57-9d0e-2a1f3f0b9a11
HTTP/1.1 404 Not Found
content-type: application/json

{"code":"not_found","message":"No task with that id"}

Your frontend developer can now build and test the “task not found” screen without touching a backend.

Send a bad request to get a 422

Prism also checks what you send. Try creating a task without the required title:

curl -i -X POST http://127.0.0.1:4010/tasks \
  -H "Content-Type: application/json" \
  -d '{"priority": 3}'

Prism answers with 422 Unprocessable Entity and explains what is wrong, shortened here:

HTTP/1.1 422 Unprocessable Entity

{
  "title": "Invalid request",
  "status": 422,
  "validation": [{
    "location": ["body"],
    "message": "Request body must have required property 'title'"
  }]
}

The same thing happens if you break a rule in the URL. Try ?limit=100 on GET /tasks: our spec says the maximum is 50, so Prism rejects it. This is a quiet superpower — your frontend’s mistakes are caught early, because the mock enforces the contract.

What you sendWhat you getWhy
GET /tasks200 with an exampleValid request, default mode
Prefer: code=404 on GET /tasks/{id}404 with the error exampleYou asked for a documented response
POST /tasks with no title422 with detailsBody breaks the schema
GET /tasks?limit=100422 with detailslimit is above the maximum of 50
GET /nope404 route errorThat path is not in the spec

Point your app at the mock

The last step is to use it. The trick is to keep the API address in one place — a setting — never spread through your code. Then switching between the mock and the real server is a one-line change.

api.js
// api.js
const API_URL = process.env.API_URL ?? "http://localhost:4010";

export async function listTasks(limit = 10) {
  const res = await fetch(`${API_URL}/tasks?limit=${limit}`);
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  return res.json();
}

Run your app with API_URL unset while you develop, and set it to your real server when the backend is ready:

# develop against the mock
npm run dev

# later, use the real API
API_URL=https://api.example.com npm run dev

Two practical notes:

  • Browsers and CORS. A web page on localhost:3000 calling localhost:4010 is a cross-origin request. Prism sends CORS headers by default, so this works without extra setup.
  • Docker or a teammate’s laptop. Prism listens on 127.0.0.1 (only your machine) by default. Add -h 0.0.0.0 to let other machines reach it: npx @stoplight/prism-cli mock -h 0.0.0.0 openapi.yaml.

When something goes wrong

What you seeMost likely causeFix
command not found: npxNode.js is not installedInstall the current Node.js LTS and reopen the terminal
Prism exits with a parsing errorA YAML indentation mistakeUse spaces (never tabs) and check the line number in the message
address already in useSomething else is on port 4010Pick another port: -p 4011
You always get 404The path or method is not in the specCompare your URL with the paths section, including the method
A 422 you did not expectYour request breaks the schemaRead the validation part of the response — it names the field
Prefer header seems ignoredThat status code is not in the specAdd the response to the spec, then restart Prism
You edited the spec but nothing changedPrism loaded the old fileStop with Ctrl + C and start it again

Where to go next

You now have a working mock, but the bigger win is the habit behind it: design the API on paper first. A few ideas to build on this:

  • Commit openapi.yaml to your repository next to the code, so the spec changes in the same pull request as the API.
  • Add a package.json script such as "mock": "prism mock -d openapi.yaml" so anyone on the team can start it with npm run mock.
  • Write frontend tests against the mock using the default example mode, so results are repeatable.
  • Later, check the real API against the same spec to catch cases where the two drift apart.

If you would rather not run anything locally, JarAPI is our tool for mocking, testing and monitoring an API from an OpenAPI spec. And once your app is running against the mock, our next tutorial shows how to give every pull request its own live URL: Ship Preview Environments for Every Pull Request.