You have finished the design of a search page. The cards look good, the filters work, and the layout holds up on a phone. Then you hit a wall: the backend API that supplies the data does not exist yet.
This article was written by Samuel Thomas, a guest contributor from the Bruno community who is not a Bruno employee. The views, examples, and recommendations are the author's own. We are grateful to Samuel for sharing this tutorial with our readers.
This is one of the most common delays in software projects. The frontend is ready to go, but it has nothing to talk to. So the frontend developer waits, or worse, hardcodes a pile of fake data inside the components and has to rip it out later.
There is a better way. With Bruno's Mock Servers, you can create a simulated API on your own computer, point your frontend at it, and keep building. In this article, you will learn what that means, why it helps, and how to set it up step by step.
If you are new to APIs, backend, or frontend development, do not worry. We will start from the basics.
Imagine you are building a student accommodation platform called Uninest. Students should be able to:
To make this work, you need two major parts.
These two parts talk to each other through an API.
API stands for Application Programming Interface. A simple way to think about it is this: an API is a messenger that carries requests from the frontend to the backend, and carries answers back.
Say a student opens Uninest and searches for apartments in Warri. Here is what happens behind the scenes:
Two terms are worth remembering:
Responses usually come in a format called JSON, which is a simple, readable way to exchange data between applications. A response for our search might look like this:
{
"location": "Warri",
"apartments": [
{
"name": "Royal Hostel",
"price": 150000,
"available": true
},
{
"name": "Student Lodge",
"price": 120000,
"available": true
}
]
}
Let's put two developers on the Uninest team. Samuel is building the frontend. David is building the backend.
Samuel has finished the apartment search page. David is still working on the API that fetches apartments from the database. Samuel now has a problem. He cannot properly test his search page because the data it needs is not available yet.
He could wait for David to finish, but that slows the whole project down. He could also paste fake data directly into his code, but then he has to find and remove it later, and he risks building the page around data that looks nothing like what the real API will return.
The cleaner solution is API mocking.
API mocking means creating a simulated version of an API that behaves like the real one. In plain terms, you set up a fake backend that returns the same kind of response the real backend is expected to return.
For example, Samuel and David agree that the apartments endpoint will return something like this:
{
"success": true,
"apartments": [
{
"id": 1,
"name": "Royal Hostel",
"location": "Warri",
"price": 150000
},
{
"id": 2,
"name": "Student Lodge",
"location": "Effurun",
"price": 120000
}
]
}
Samuel can serve this response from a mock API and build his page against it. His frontend code will not care whether the answer came from a real database or a mock. It just receives JSON and displays it.
One thing to keep in mind: mock data does not come from a real database. It is predefined test data that imitates what the backend should eventually return.
Bruno is an open-source API client. Developers use it to create, send, organize, and test API requests. If you have used tools like Postman or Insomnia, the idea will feel familiar.
With Bruno, you can:
This article focuses on that last feature.
A Bruno Mock Server is a local HTTP server that runs on your computer and pretends to be your API. It receives requests and sends back the responses you configured.
Back to our example. David is supposed to build this endpoint:
GET /api/apartments
It is not ready, so Samuel uses Bruno to run a mock server that answers at the same path:
GET http://localhost:4000/api/apartments
To the frontend, this looks like a normal API request. The only difference is that Bruno is answering instead of the real backend.
Bruno gives you three ways to create a mock server:
In this tutorial, we will use the first option, since it is the easiest to follow.
At the time of writing, Mock Servers carry a Beta label inside Bruno (version 4.3.0 in the screenshots below). Menus and wording may change slightly in future versions.
Our goal is simple. We want Samuel to display apartment listings in his app before David finishes the backend.
Download Bruno from the official website at usebruno.com/downloads. It is available for Windows, macOS, and Linux.
When you open Bruno for the first time, you may see a welcome screen with options such as importing from Postman, creating a collection, or starting with a request. The exact screen can vary depending on your version. Whichever you see, we will create a collection.
A collection is a folder that keeps related API requests together. For Uninest, a full collection might eventually look like this:
For now, we only need one request. Click the plus icon next to Collections, or choose Create Collection from the welcome screen.
Type the name Uninest API. Bruno shows where the collection will be saved on your computer. You can keep the default location or pick your own. Then click Create.
Inside your new collection, create a new request. Leave the type as HTTP, name the request Get All Apartments, set the method to GET, and enter the path /api/apartments.
Be careful with the URL field. The method already comes from the GET dropdown, so type only the path, /api/apartments, and not the word GET as part of it.
Click Create. The request opens in a new tab.
Notice that you did not need a working backend to do this. You have only described a request that will exist one day.
This is the step that makes mocking possible. A response example is a saved answer that you write yourself, describing what the API should return. Bruno lets you create one without sending anything to a real server.
Open the menu for your request and choose Create Example. Name it 200-apartments-success. You can add a description if you like, but it is optional.
Bruno then opens the example editor. Make sure the status is set to 200 OK, then paste the following JSON into the response body:
{
"success": true,
"apartments": [
{
"id": 1,
"name": "Royal Hostel",
"location": "Warri",
"price": 150000
},
{
"id": 2,
"name": "Student Lodge",
"location": "Effurun",
"price": 120000
}
]
}
Click Save. Your example now appears with the request path, the status, and the response body all in one view.
You have now defined what the API should return when someone asks for apartment listings. You have still not built a backend. You have only written down what its answer will look like.
Now we turn that example into a working mock. Look at the Mock Servers section at the bottom of the left sidebar and click the plus icon next to it.
Bruno asks for a name and a source. This is where you see the three options we talked about earlier: Collection, API Spec, and Standalone.
Name the server Uninest Mock API, keep Collection as the source, and pick Uninest API from the dropdown. Leave the box checked that says to sync mock responses with collection examples. This keeps your mock in step with your saved examples. Bruno will also choose an available port automatically.
Click Create.
Bruno opens a dashboard for your mock server. It currently shows as Stopped. Under the Responses tab, you can already see your example listed as a mock response, along with the route it answers.
Two other tabs are worth knowing about. The Routes tab lists the endpoints your mock serves, and the Request Log shows every request that hits your server, which is very useful when you are debugging.
Whenever you change an example in your collection, click Sync with Examples to bring the mock up to date. Bruno will warn you that mock responses matching collection examples will be overwritten with the latest example data, while custom mock responses without a matching example will be kept.
Click Start Server. The status changes to Running, and Bruno shows your base URL:
http://localhost:4000
So the full address for our endpoint is:
http://localhost:4000/api/apartments
If the word localhost is new to you, it simply means your own computer. The number 4000 is the port, which works like a door that your mock server listens at. Bruno uses port 4000 by default, though it will pick another one if that port is busy.
You can test it right away by opening that address in your browser. You should see the JSON you wrote earlier.
Now Samuel can do what he wanted to do all along. In his React app, he points the API address at the mock server:
const API_URL = "http://localhost:4000";
async function getApartments() {
const response = await fetch(`${API_URL}/api/apartments`);
if (!response.ok) {
throw new Error("Could not load apartments");
}
const data = await response.json();
return data.apartments;
}
When this code runs, the following happens:
localhost:4000.A good habit here is to keep the base URL in one place, such as an environment variable. When David finishes the real API, Samuel only has to change that one value. Nothing else in the code needs to move.
If you are using Vite, for example, it could look like this:
const API_URL = import.meta.env.VITE_API_URL;
Then you set VITE_API_URL to the mock address during development, and to the real address later.
A real API does not always say yes. Sometimes a request succeeds, but other times:
Your frontend needs to handle all of these gracefully. A search page that works with data but crashes on an empty list is not finished.
Bruno lets you add more than one mock response for the same endpoint. Click New Mock Response on the dashboard to create extra ones. Here is a useful set to start with:
| Scenario | Status code | What the response contains |
|---|---|---|
| Apartments found | 200 | A list of apartments |
| No apartments found | 200 | An empty apartments array |
| Invalid request | 400 | A validation error message |
| Apartment not found | 404 | A not found message |
| Server error | 500 | An internal server error message |
An empty result could look like this:
{
"success": true,
"apartments": [],
"message": "No apartments available"
}
And an error could look like this:
{
"success": false,
"message": "Invalid search location"
}
If you have several responses for one endpoint, Bruno needs a way to choose which to return. That is the job of response matching rules. You can set rules based on request headers, query parameters, or values in the request body.
For example, a request to /api/apartments?location=Warri could return the list of apartments, while a request with a location that does not exist could return an empty response or an error. Responses with no rules act as the default match.
This lets Samuel check his empty states, error messages, and success views without waiting for David to build any of them.
There is also a Delay setting on the dashboard, measured in milliseconds. Adding a delay makes the mock server answer slowly, which is a handy way to see how your loading spinner behaves. Real networks are never instant, and your interface should look fine while it waits.
OpenAPI is a standard way of describing an API. A specification written in this format lists the available endpoints, the HTTP methods, the required parameters, the shape of requests and responses, and the status codes.
If your backend team writes an OpenAPI spec before building the API, Bruno can use it as a source for a mock server. That is the API Spec option we saw earlier. In that case, you do not have to write every example by hand, because Bruno can generate responses from the spec.
This works especially well when the frontend and backend teams agree on the API contract at the start of a project.
Faster frontend work. You can start building and testing features right away, instead of waiting for the backend.
Parallel development. Frontend and backend developers can work at the same time without blocking each other.
Easier testing of edge cases. Success, failure, and empty results are all one configuration away.
Predictable data. A mock returns the same answer every time, so you can repeat a test without worrying about database records changing underneath you.
Earlier feedback. Frontend problems show up sooner, which means fewer surprises during integration.
Local and simple. The mock server runs on your own machine. There is nothing to deploy and no cloud setup needed.
Team friendly. Mock server setups are stored with your workspace files, so they work well with Git and can be shared with teammates.
A mock server is not a backend, and it should never be treated like one. Here is the difference:
| Mock server | Real backend |
|---|---|
| Returns predefined responses | Runs real application logic |
| Uses configured test data | Reads from and writes to a database |
| Imitates API behavior | Implements actual API features |
| Helpful during development | Required for production |
| Does not store new data by itself | Can create, update, and delete data |
A few practical lessons follow from this:
When the real backend is ready, switch the base URL from the mock to the real API and run proper integration tests. This is the moment where any gap between the mock and reality will show up, and it is much better to find it early.
Waiting for a backend should not stop a frontend from moving forward. With Bruno Mock Servers, you can describe how an API will respond, run that description as a local server, and build your interface against it today.
In this tutorial, we created a collection, saved a response example, turned it into a mock server, and connected a React app to it. Along the way, we looked at how to test errors and empty states, how OpenAPI can generate mocks for you, and why a mock should never be mistaken for the real thing.
If you are working on a project where the backend is running behind, try it on your next feature. Start with a single endpoint, like the apartments list in our example. Once you see your frontend come alive with realistic data, you will wonder why you ever waited.
Build your first mock server today.
Download Bruno