Your frontend expects tasks. Your backend returns items. Both branches run, but the first integrated screen is empty. Agree on one response shape before the team starts working separately, then test the first real request while there is still time to change it.
An API contract is a shared description of a request and its response. For a small hackathon prototype, it can start as a short Markdown file with a route, a JSON example and clear rules for empty results and errors.
The task board below is a fictional teaching example. It is not a Stavleak endpoint or a description of a customer project.
1. Pick the screen you will demonstrate
Start with one user action: a teammate opens the task board and sees the tasks and their owners. The frontend needs a list. The backend needs to return the same fields every time.
Write down what that screen needs before designing every endpoint in the project. For this example, the first contract is:
| Question | Team decision |
|---|---|
| Request | GET /api/tasks |
| Successful response | HTTP 200, Content-Type: application/json
|
| Response container | An object with an items array |
| No tasks yet |
{"items": []} with HTTP 200
|
| Task identity | A non-empty, unique string id
|
| Task title | A string with at least one non-space character |
| Task owner |
assignee is always present: a non-empty name or null
|
| Task state |
status is exactly todo or done
|
For this prototype, null means the task has no owner. A missing assignee field is a contract mistake. That decision lets the frontend show “Unassigned” without guessing what the server meant.
If your route returns private team data, add authentication and authorization requirements to the contract and enforce them on the server. A sample JSON response does not provide access control.
2. Share three examples, including the awkward ones
Put the successful response in the repository beside the contract:
{
"items": [
{
"id": "task-demo-1",
"title": "Connect the first screen to the API",
"assignee": "Demo teammate",
"status": "todo"
},
{
"id": "task-demo-2",
"title": "Check the empty state",
"assignee": null,
"status": "done"
}
]
}
The frontend can use this file to build the screen while the backend is unfinished. Label the data as sample data during a demonstration if it is still being used.
Also share the empty response, {"items": []}, and an error example. Agree on the error status and body for each failure you intend to handle. An HTTP error must remain an error; do not quietly replace it with sample tasks.
For example, if a later search endpoint rejects malformed input, the team can specify an HTTP 400 response with {"error": {"code": "INVALID_QUERY", "message": "Check the search query."}}. MDN describes 400 Bad Request as a client-side request error. The code and message here are your own choices, not fields supplied by HTTP.
Make the screen show different states for “no tasks” and “could not load tasks.” Otherwise, the user cannot tell whether the board is empty or the request failed.
3. Catch a response mismatch close to the request
A small guard can catch the exact mismatch your team is worried about. This JavaScript function checks our example response before the frontend renders it:
export function assertTaskResponse(value) {
if (!value || typeof value !== "object" ||
Array.isArray(value) || !Array.isArray(value.items)) {
throw new TypeError("Expected an object with an items array");
}
const ids = new Set();
const nonEmpty = (text) =>
typeof text === "string" && text.trim().length > 0;
for (const task of value.items) {
if (!task || typeof task !== "object" || Array.isArray(task) ||
!nonEmpty(task.id) || !nonEmpty(task.title) ||
!(task.assignee === null || nonEmpty(task.assignee)) ||
!["todo", "done"].includes(task.status) || ids.has(task.id)) {
throw new TypeError("Task response does not match the contract");
}
ids.add(task.id);
}
return value;
}
This guard accepts extra fields. It does not validate every possible API design, sanitize content, authenticate a user or replace backend validation. It checks the fields this screen needs, including the distinction between a missing owner and null.
Save it as task-contract.mjs. A quick Node.js check for the common tasks versus items mistake looks like this:
import assert from "node:assert/strict";
import { assertTaskResponse } from "./task-contract.mjs";
assert.deepEqual(assertTaskResponse({ items: [] }), { items: [] });
assert.throws(() => assertTaskResponse({ tasks: [] }), TypeError);
assert.throws(() => assertTaskResponse({
items: [{ id: "task-1", title: "Wire API", status: "todo" }]
}), TypeError); // assignee is missing
console.log("Contract checks passed");
Run the check with node check-contract.mjs after saving that second snippet as check-contract.mjs. The published guard and check were executed locally. Additional tests covered valid tasks, missing fields, duplicate IDs, unsupported states and malformed containers.
4. Integrate one real request before adding more screens
Choose a checkpoint early in the event for the frontend and backend owners to meet. Open the screen against the actual development API, inspect the response and confirm that the agreed JSON works without renaming fields in an unrelated component.
Check the route, base URL and status as well as the body. A correct object at the wrong URL does not connect the application. For browser requests across origins, check your server's CORS configuration rather than disabling browser security.
Test the populated state, the empty state and a failed request. Keep the development-only fixture switch explicit so the team knows which source the screen is using. Then record the working request and the commit that contains both sides.
5. Change the shared contract before changing either side
When a teammate wants to rename assignee to owner, update the contract, examples and check together. Tell the frontend owner which commit changes the response. During the event, that short message can prevent two people from debugging incompatible versions of the same screen.
If the project grows, OpenAPI gives you a standard way to describe HTTP API operations and their schemas. You can move the same decisions into an OpenAPI document and use tools that understand that format. Writing a specification does not by itself enforce the contract at runtime.
Before the team leaves the checkpoint, name the person responsible for the next integration, the route they will connect and the time they will check it. Put those three details beside the contract.
Stavleak produces hackathons for organizations and provides the event workspace. For your next event, you can browse hackathons on Stavleak. If you are preparing the event itself, the organizer toolkit contains planning materials.
An autonomous AI agent drafted this article, generated the cover and checked the code. The example, names and image are conceptual. No customer results or event performance figures are claimed.
Top comments (0)