Skip to content

Commit a13c966

Browse files
authored
Merge pull request #769 from marle3003/develop
Develop
2 parents 9fcedb3 + cca38f2 commit a13c966

File tree

22 files changed

+583
-111
lines changed

22 files changed

+583
-111
lines changed

docs/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@
201201
"description": "Explore Mokapi's resources including tutorials, examples, and blog articles. Learn to mock APIs, validate schemas, and streamline your development."
202202
},
203203
"items": {
204+
"Ensuring API Contract Compliance with Mokapi": "resources/blogs/ensuring-api-contract-compliance-with-mokapi.md",
204205
"Mock APIs based on OpenAPI and AsyncAPI": "resources/blogs/mock-api-based-on-openapi-asyncapi.md",
205206
"Automation Testing in Agile Development": "resources/blogs/automation-testing-agile-development.md",
206207
"Contract Testing": "resources/blogs/contract-testing.md",

docs/javascript-api/mokapi/eventhandler/httprequest.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ that contains request-specific data such as HTTP headers.
1818
| header | object | Object contains header parameters specified by OpenAPI header parameters |
1919
| cookie | object | Object contains cookie parameters specified by OpenAPI cookie parameters |
2020
| body | any | Body contains request body specified by OpenAPI request body |
21+
| api | string | The name of the API, as defined in the OpenAPI info.title field |
2122

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
---
2+
title: Ensure API Contract Compliance with Mokapi Validation
3+
description: Validate HTTP API requests and responses with Mokapi to catch breaking changes early and keep backend implementations aligned with your OpenAPI spec.
4+
image:
5+
url: /mokapi-using-as-proxy.png
6+
alt: Flow diagram illustrating how Mokapi enforces OpenAPI contracts between clients, Playwright tests, and backend APIs.
7+
---
8+
9+
# Ensuring Compliance with the HTTP API Contract Using Mokapi for Request Forwarding and Validation
10+
11+
In modern distributed systems, APIs are everywhere — frontend-to-backend,
12+
backend-to-backend, microservices communicating internally, mobile apps, test
13+
automation tools, and more. Each interaction relies on a shared API contract,
14+
often expressed through an OpenAPI specification. Even small
15+
deviations can introduce bugs, break integrations, or slow down development.
16+
17+
By placing Mokapi between a client and a backend, you can ensure that every
18+
**request and response adheres to your OpenAPI specification**. With a few lines
19+
of JavaScript, Mokapi can forward requests to your backend while validating both
20+
sides of the interaction. This provides a powerful way to enforce API correctness —
21+
whether the client is a browser, Playwright tests, your mobile app, or even
22+
another backend service.
23+
24+
In this article, I explore how Mokapi can act as **a contract-enforcing validation layer**
25+
and why this approach benefits frontend developers, backend teams, QA engineers,
26+
and platform engineers alike.
27+
28+
<img src="/mokapi-using-as-proxy.png" alt="Flow diagram illustrating how Mokapi enforces OpenAPI contracts between clients, Playwright tests, and backend APIs.">
29+
30+
## How to Use Mokapi for API Validation with Request Forwarding?
31+
32+
Mokapi cannot only be used for mocking APIs, but it can also sit between any
33+
consumer and a backend service to validate real traffic. Using a small
34+
JavaScript script, Mokapi can forward requests to your backend and
35+
validates both requests and responses.
36+
37+
38+
Consumer (Frontend, Playwright, Microservice) → Mokapi → Backend API
39+
40+
```typescript
41+
import { on } from 'mokapi';
42+
import { fetch } from 'mokapi/http';
43+
44+
/**
45+
* This script demonstrates how to forward incoming HTTP requests
46+
* to a real backend while letting Mokapi validate responses according
47+
* to your OpenAPI spec.
48+
*
49+
* The script listens to all HTTP requests and forwards them based
50+
* on the `request.api` field. Responses from the backend are
51+
* validated when possible, and any errors are reported back to
52+
* the client.
53+
*/
54+
export default async function () {
55+
56+
/**
57+
* Register a global HTTP event handler.
58+
* This function is called for every incoming request.
59+
*/
60+
on('http', async (request, response) => {
61+
62+
// Determine the backend URL to forward this request to
63+
const url = getForwardUrl(request)
64+
65+
// If no URL could be determined, return an error immediately
66+
if (!url) {
67+
response.statusCode = 500;
68+
response.body = 'Failed to forward request: unknown backend';
69+
return;
70+
}
71+
72+
try {
73+
// Forward the request to the backend
74+
const res = await fetch(url, {
75+
method: request.method,
76+
body: request.body,
77+
headers: request.header,
78+
timeout: '30s'
79+
});
80+
81+
// Copy status code and headers from the backend response
82+
response.statusCode = res.statusCode;
83+
response.headers = res.headers
84+
85+
// Check the content type to decide whether to validate the response
86+
const contentType = res.headers['Content-Type']?.[0] || '';
87+
88+
if (contentType.includes('application/json')) {
89+
// Mokapi can validate JSON responses automatically
90+
response.data = res.json();
91+
} else {
92+
// For other content types, skip validation
93+
response.body = res.body;
94+
}
95+
96+
} catch (e) {
97+
// Handle any errors that occur while forwarding
98+
response.statusCode = 500;
99+
response.body = e.toString();
100+
}
101+
});
102+
103+
/**
104+
* Maps the incoming request to a backend URL based on the API name
105+
* defined in the OpenAPI specification (`info.title`).
106+
* @see https://mokapi.io/docs/javascript-api/mokapi/eventhandler/httprequest
107+
*
108+
* @param request - the incoming Mokapi HTTP request
109+
* @returns the full URL to forward the request to, or undefined
110+
*/
111+
function getForwardUrl(request: HttpRequest): string | undefined {
112+
switch (request.api) {
113+
case 'backend-1': {
114+
return `https://backend1.example.com${request.url.path}?${request.url.query}`;
115+
}
116+
case 'backend-2': {
117+
return `https://backend1.example.com${request.url.path}?${request.url.query}`;
118+
}
119+
default:
120+
return undefined;
121+
}
122+
}
123+
}
124+
```
125+
126+
For each interaction, Mokapi performs four important steps:
127+
128+
### 1. Validates incoming requests
129+
130+
Mokapi checks every incoming request against your OpenAPI specification:
131+
132+
- HTTP method
133+
- URL & parameters
134+
- headers
135+
- request body
136+
137+
If the client sends anything invalid, Mokapi blocks it and returns a clear
138+
validation error.
139+
140+
### 2. Forwards valid requests to your backend
141+
142+
If the request is valid, Mokapi forwards it unchanged to the backend using JavaScript.
143+
144+
- No changes are required in your backend.
145+
- No additional infrastructure is necessary.
146+
147+
### 3. Validates backend responses
148+
149+
Once the backend responds, Mokapi validates the response against the OpenAPI specification:
150+
151+
- status codes
152+
- headers
153+
- response body
154+
155+
If something doesn't match the contract, Mokapi blocks it and sends a validation error back to the client.
156+
157+
### 4. Return the validated response to the client
158+
159+
Only responses that pass validation reach the client, guaranteeing contract fidelity end-to-end.
160+
161+
## Where You Can Use Mokapi for Request Forwarding and Validation
162+
163+
Mokapi’s forwarding and validation capabilities make it useful far beyond local development or Playwright scripting.
164+
165+
### Between Frontend and Backend
166+
167+
Placing Mokapi between your frontend and backend ensures:
168+
- automatic request and response validation
169+
- immediate detection of breaking changes
170+
- backend and API specification evolve together
171+
- fewer “why is the frontend broken?” debugging loops
172+
173+
Frontend developers can experiment with confidence, knowing the backend
174+
cannot silently diverge from the published contract.
175+
176+
### Between Backend Services (Service-to-Service)
177+
178+
In microservice architectures, API drift between services is a frequent cause of instability.
179+
Routing service-to-service traffic through Mokapi gives you:
180+
- strict contract enforcement between services
181+
- early detection of incompatible changes
182+
- stable integrations even as teams evolve independently
183+
- clear validation errors during development and CI
184+
185+
Mokapi becomes a lightweight, spec-driven contract guardian across your backend ecosystem.
186+
187+
### In Automated Testing (e.g., Playwright)
188+
189+
This is one of the most powerful setups.
190+
191+
Playwright → Mokapi → Backend
192+
193+
Benefits:
194+
- CI fails immediately when the backend breaks the API contract
195+
- tests interact with the real backend, not mocks
196+
- validation errors are clear and actionable
197+
- tests remain simpler — no need to validate everything in Playwright
198+
199+
Your tests are guaranteed to hit a backend that actually matches the API contract.
200+
201+
### In Kubernetes Test Environments
202+
203+
Mokapi can also be used in temporary or preview environments to ensure contract validation across the entire cluster.
204+
205+
In Kubernetes, Mokapi can be deployed as:
206+
- a sidecar container
207+
- a standalone validation layer in front of backend services
208+
- a temporary component inside preview environments
209+
210+
This brings:
211+
- consistent contract validation for all cluster traffic
212+
- early detection of breaking API changes before staging
213+
- contract enforcement without modifying backend services
214+
- transparent operation — apps talk to Mokapi, Mokapi talks to the backend
215+
216+
You can integrate Mokapi into Helm charts, GitOps workflows, or test namespaces.
217+
218+
## Why Teams Benefit from Using Mokapi Between Client and Backend
219+
220+
### Automatic Contract Enforcement
221+
222+
Every interaction is validated against your OpenAPI specification. Your backend can no longer quietly drift from the contract.
223+
224+
### Immediate Detection of Breaking Changes
225+
226+
Issues are caught early, not just in staging or production, such as:
227+
- renamed or missing fields
228+
- wrong or inconsistent formats
229+
- unexpected status codes
230+
- mismatched data types
231+
232+
### More Reliable Frontend Development
233+
234+
Frontend teams get:
235+
- consistent, validated API responses
236+
- fewer sudden breaking changes
237+
- a smoother development workflow
238+
239+
This reduces context-switching and debugging time.
240+
241+
### Better Collaboration Between Teams
242+
243+
With Mokapi validating both sides:
244+
- backend developers instantly see when they violate the contract
245+
- frontend engineers get stable, predictable APIs
246+
- QA gets reliable test environments
247+
- platform engineers reduce risk during deployments
248+
249+
Mokapi becomes a shared API contract watchdog across the organization.
250+
251+
### Smooth Transition from Mocks to Real Systems
252+
253+
Teams often start with mocked endpoints in early development. Later, they can simply begin forwarding requests to the
254+
real backend—while keeping validation in place.
255+
256+
## Conclusion
257+
258+
Using Mokapi between frontend and backend, between backend services, or inside Kubernetes environments provides:
259+
- strong contract enforcement
260+
- automatic validation for every interaction
261+
- early detection of breaking changes
262+
- stable multi-team integration
263+
- more reliable CI pipelines
264+
- a smooth path from mocking to real backend validation
265+
266+
Mokapi ensures your API stays aligned with its specification, no matter how quickly your system evolves.

engine/js_test.go

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"net/url"
1414
"strings"
1515
"testing"
16-
"time"
1716

1817
r "github.com/stretchr/testify/require"
1918
)
@@ -23,31 +22,30 @@ func TestJsScriptEngine(t *testing.T) {
2322
t.Run("valid", func(t *testing.T) {
2423
t.Parallel()
2524
e := enginetest.NewEngine()
26-
err := e.AddScript(newScript("test.js", "export default function(){}"))
25+
err := e.AddScript(newScript("valid.js", "export default function(){}"))
2726
r.NoError(t, err)
2827
r.Equal(t, 0, e.Scripts(), "no events and jobs, script should be closed")
2928
})
3029
t.Run("blank", func(t *testing.T) {
3130
t.Parallel()
3231
e := enginetest.NewEngine()
33-
err := e.AddScript(newScript("test.js", ""))
32+
err := e.AddScript(newScript("blank.js", ""))
3433
r.NoError(t, err)
3534
r.Equal(t, 0, e.Scripts(), "no events and jobs, script should be closed")
3635
})
3736
t.Run("typescript", func(t *testing.T) {
3837
t.Parallel()
3938
e := enginetest.NewEngine()
40-
err := e.AddScript(newScript("test.ts", "const msg: string = 'Hello World';"))
39+
err := e.AddScript(newScript("typescript.ts", "const msg: string = 'Hello World';"))
4140
r.NoError(t, err)
4241
r.Equal(t, 0, e.Scripts(), "no events and jobs, script should be closed")
4342
})
44-
t.Run("typescript async default function", func(t *testing.T) {
43+
t.Run("async default function", func(t *testing.T) {
4544
t.Parallel()
4645
e := enginetest.NewEngine()
47-
err := e.AddScript(newScript("test.js", "import { every } from 'mokapi'; export default async function(){ setTimeout(() => { every('1m', function() {}) }, 500)}"))
46+
err := e.AddScript(newScript("async.js", "import { every } from 'mokapi'; export default async function(){ setTimeout(() => { every('1m', function() {}) }, 500)}"))
4847
r.NoError(t, err)
4948
r.Equal(t, 1, e.Scripts())
50-
time.Sleep(2 * time.Second)
5149
e.Close()
5250
})
5351
t.Run("script from GIT provider", func(t *testing.T) {
@@ -60,6 +58,63 @@ func TestJsScriptEngine(t *testing.T) {
6058
r.Equal(t, 1, e.Scripts())
6159
e.Close()
6260
})
61+
t.Run("example from Mokapi as proxy article", func(t *testing.T) {
62+
t.Parallel()
63+
e := enginetest.NewEngine()
64+
err := e.AddScript(newScript("proxy.ts", `
65+
import { on } from 'mokapi';
66+
import { fetch } from 'mokapi/http';
67+
68+
export default async function () {
69+
on('http', async (request, response) => {
70+
const url = getForwardUrl(request)
71+
if (!url) {
72+
response.statusCode = 500;
73+
response.body = 'failed to forward request';
74+
} else {
75+
try {
76+
const res = await fetch(url, {
77+
method: request.method,
78+
body: request.body,
79+
headers: request.header,
80+
timeout: '30s'
81+
});
82+
response.statusCode = res.statusCode;
83+
response.headers = res.headers
84+
switch (res.headers['Content-Type'][0]) {
85+
case 'application/json':
86+
// mokapi validates the data
87+
response.data = res.json();
88+
default:
89+
// mokapi skips validation
90+
response.body = res.body;
91+
}
92+
} catch (e) {
93+
response.statusCode = 500;
94+
response.body = e.toString();
95+
}
96+
}
97+
});
98+
99+
function getForwardUrl(request: HttpRequest): string | undefined {
100+
switch (request.api) {
101+
case 'backend-1': {
102+
const url = {host: 'https://backend1.example.com', ...request.url}
103+
return url.toString();
104+
}
105+
case 'backend-2': {
106+
const url = {host: 'https://backend1.example.com', ...request.url}
107+
return url.toString();
108+
}
109+
default:
110+
return undefined;
111+
}
112+
}
113+
}
114+
`))
115+
r.NoError(t, err)
116+
e.Close()
117+
})
63118
}
64119

65120
func TestJsEvery(t *testing.T) {

0 commit comments

Comments
 (0)