QA Lead balancing between manual testing and automation. I break down products, find weak spots, and share my insights in articles.
Walkthroughs, tutorials, guides, and tips. This story will teach you how to do something new or how to do something better.
The is an opinion piece based on the author’s POV and does not necessarily reflect the views of HackerNoon.
Modern web applications are complex multifunctional platforms where the back-end and front-end interact with each other via API. It is very important that these two components work in harmony.
But what happens if the API passes unexpected data to the front-end or returns an error in the response? In this case, the user may encounter some problems, such as a blank screen, incomplete data loading, an infinite loader on the screen, or the absence of user-friendly errors. 🐞😯
In this article, I want to analyze common and other cases when the API can break the front-end, and I will tell you how to test the API to avoid problems as early as possible. Let's look at real examples to better understand the causes and ways to prevent these errors.
If you want your interface to always please users, and not make them nervous, this article is for you!
Typically, the front-end expects certain status codes to process responses and uses them to quickly check the success of the request. If the status code indicates an error (for example, 400 or 500), the front-end can immediately show a general message - “Server Error”.
And if the API starts returning unexpected status codes - this can lead to incorrect work on the front-end.
For example, change the status code in the request from 404 Not Found
to 200 OK
.
Response body:
{
"status": "success",
"data": null
}
The front-end reads 200 OK and expects the response to return data, for example:
{
"status": "success",
"data": {
"id": 1,
"email": "example.com",
"name": “Martin”,
"balance": 4000
}
}
But in fact, data: null
is returned, which causes an error on the front-end end when trying to access data.id
- a TypeError
.
Or another example: before the API update, with invalid data, the 400 Bad Request
error was returned with the response body:
{
"status": "error",
"message": "Invalid data"
}
And after the update, the status code started coming: 500 Internal Server Error
. The front-end may display “Server Error” instead of “Invalid data”, which will mislead the user.
pm.test("Check status code and response body", function () {
// Check status code
pm.expect(pm.response.code).to.eql(200);
// Check response body
const responseData = pm.response.json();
pm.expect(responseData.status).to.eql("success");
pm.expect(responseData.data).to.be.an("object").that.is.not.null;
});
Let's imagine a web application that has filters and graphs. For example, users can select a period, filter by date, and then draw graphs. Everything works perfectly until one day the API decides to "make a joke" with date formats.
Initially, the API sent dates in ISO 8601
format:
{
"dateFrom": "2025-02-25T21:35:10Z",
"dateTo": "2025-02-28T18:00:00Z"
}
And after updating the back-end, the date format was changed to local:
{
"dateFrom": "24/02/2025 21:35",
"dateTo": "28/02/2025 18:00"
}
Or converted to timestamp, like:
{
"dateFrom": 1740422381,
"dateTo": 1740681581
}
What happens on the front-end?
If date handling has not been designed to take into account possible format changes, code expecting ISO 8601
will simply break. For example, when using new Date()
in JavaScript:
const response = {
dateFrom: "24/02/2025 21:35",
dateTo: "28/02/2025 18:00"
};
const dateFrom= new Date(response.dateFrom);
console.log(dateFrom.toISOString());
We expect the date to be parsed correctly, but in fact we see Invalid Date
in the console.
It's a bit easier with timestamp. However, if the API unexpectedly changes the format, the front-end will receive similar errors when working with dates without preliminary checks.
What's happening on the UI:
At first, it may seem that the bug is on the front-end, but in fact the reason lies in the API. And here an obvious question arises: how could this have been prevented?
When testing the API, do not forget to check the parameter formats so that they match the documentation.
No documentation? Agree with the backend to provide a strict format for API contracts and document it, for example, in Swagger.
Include integration tests (backend + front-end) in each task, even if at the analytics stage you did not foresee possible risks and did not set a task for improving the front-end. QA can prevent this collapse at the testing stage.
Test related functionality with dates more thoroughly: working with filters, sorting, graphs. Check range boundaries, take into account scenarios with time zones.
Do not forget about negative scenarios, for example, what will happen to the front-end if you send null? or an empty string to the date field? The front-end should be able to return errors understandable to the user in such cases.
Automate checks, for example, add a script to Postman:
// Pre-request Script (Importing the AJV library safely)
pm.sendRequest("https://cdn.jsdelivr.net/npm/ajv@8.12.0/dist/ajv.min.js", function (err, response) {
if (!err) {
new Function(response.text())(); // Execute safely instead of eval()
pm.globals.set("ajvLoaded", "true"); // Mark that AJV is available
}
});
// Tests
pm.test("Response contains 'dateFrom' and 'dateTo'", function () {
let jsonData = pm.response.json();
pm.expect(jsonData).to.have.property("dateFrom");
pm.expect(jsonData).to.have.property("dateTo");
});
pm.test("Date format follows ISO 8601", function () {
let jsonData = pm.response.json();
// Ensure the AJV library is available before execution
if (pm.globals.get("ajvLoaded") !== "true") {
throw new Error("AJV library is not loaded yet.");
}
const ajv = new Ajv();
// Function to validate ISO 8601 format
const isISO8601 = (dateString) => {
const schema = {
"type": "string",
"format": "date-time"
};
const validate = ajv.compile(schema);
return validate(dateString);
};
pm.expect(isISO8601(jsonData.dateFrom)).to.be.true;
pm.expect(isISO8601(jsonData.dateTo)).to.be.true;
});
Let's imagine that there is an application where the front-end requests a list of some data and expects the API to return 10-20 records per page. Some request GET /api/users?page=1&limit=20
API response:
{
"page": 1,
"totalPages": 100,
"items": [
{ "id": 1, "name": "Alena" },
{ "id": 2, "name": "Alisa" }
// ... total 20 entries
]
}
Everything works: the front-end renders the list, the "Next Page" buttons work, users are happy.
But after updating the API, the pagination parameters stop working, and now the server returns all ... 20,000 records in one JSON response:
{
"page": 1,
"totalPages": 1,
"items": [
{ "id": 1, "name": "Alena" },
{ "id": 2, "name": "Alisa" },
{ "id": 3, "name": "Bob" }
// ... Another 19 997 records! 😱
]
}
The result? Instead of a quick response with 20 records, the front-end receives a huge JSON of 5-10 MB. The front-end may not be optimized to handle such a volume of data, and as a result, it will start to slow down or freeze completely.
What consequences can there be for the front-end:
limit=20&offset=0
).pm.sendRequest("https://master.example.com/users?page=1&limit=20", function (err, res) {
let jsonData = res.json();
pm.test("API correctly applies the limi", function () {
pm.expect(jsonData.items.length).to.eql(20, "API ignores the limit parameter!");
});
});
For example, let's imagine a web application that displays analytical statistics in the form of a table. Data for the table (e.g. metrics, financial indicators, KPIs) is requested from the back-end. Column and row names are also dynamically loaded via API.
{
"columns": ["Date", "Sales", "Revenue", "Profit"],
"rows": [
{"Date": "2025-01-01", "Sales": 100, "Revenue": 1000, "Profit": 200},
{"Date": "2025-01-02", "Sales": 150, "Revenue": 1500, "Profit": 300}
]
}
For example, a user has a significant amount of statistics accumulated over a long period, and he needs a table with detailed information. It is possible that due to high load on the database, the back-end may start returning timeouts, which can lead to visual and functional bugs on the front-end.
What happens on the front-end?
The front-end expects data in a certain format and uses it to render the table:
const data = response.data;
const columns = data.columns; // Column names
const rows = data.rows; // Data for rows
renderTable(columns, rows); // Render the table
But if the back-end is unable to process the request due to high load, it may return 504 Gateway Timeout
. In this case, the front-end will not receive the data and will try to "save" the situation: either by inserting zeros or using old cached data.
Example of processing with the substitution of empty data:
const data = response.data || { columns: [], rows: [] }; // Fallback to empty data
renderTable(data.columns, data.rows); // Render an empty table
As a result, the user will see incorrect data:
This situation is aggravated if there is no warning message about the error that occurred. In this case, the user simply will not understand that something went wrong inside the application.
If the names of columns and rows are also requested from the back-end, and this request ends with a timeout, the front-end may display a table with empty headers. For example, instead of "Date", "Sales", "Revenue", "Profit" the user sees empty cells or placeholders like "No data".
As an example for the fifth case, let's analyze the situation with images on the front-end. For example, we are testing an online store where promo banners are displayed on the main page, and their images are displayed on product cards. And all these images are uploaded by managers through the admin panel, the files are stored on the server or CDN, and the admin API provides links to these images. The front-end requests images through the API, and if it breaks, we will see bugs on the UI again.
What happens on the front-end?
500 Internal Server
or 503 Service Unavailable
-> the images will not load on the front-end, and in their place we will see “broken” icon images, an error (for example, 404) or an empty space.
So :) we have considered several possible cases when the API can break the front-end. Of course, there are opposite cases when the front-end can break the API. Therefore, it is always necessary to pay special attention to testing the interaction between the back-end and the front-end.
Let's consider one of the common examples when the front-end can "break" the back-end - this is an error in transmitting Unicode characters to the API.
Let's imagine that we have a form where the user enters a name. What happens if we send data containing special characters, emoji or Unicode strings to the API? Is the back-end ready to process them correctly?
Back-end expects:
POST /api/users
{
"name": "Martin"
}
For such a request, the back-end will return 201 Create
and the response body:
{
"id": 1,
"name": "Martin",
"status": "ok"
}
What if the user decides to send?
{
"name": "🚀😎👻🤩🐙こんにちは"
}
If the back-end is not ready to handle special characters, it may crash with error 500 or, if the database does not support UTF-8 encoding, the record may simply be lost.
Test not only the correctness of responses, but also the behavior of the front-end with different statuses.
Check how the front-end handles missing or incorrect data - use stubs, request interceptions, substitute errors and data formats, and analyze their impact on the UI.
Localize the bug before determining who is responsible. It is necessary to clearly establish where exactly the problem occurred - on the front-end or the back-end.
Agree on a strict API contract in the team using OpenAPI (Swagger) or GraphQL schemas.
Use API versioning in the team.
It is important to establish prompt communication between the back-end and front-end about any changes in the API to avoid inconsistencies.
Thanks and Good luck❤️