IV.1
Validate the response against the definition
You need this: When clients aren't trusted: public forms, APIs, compliance.
You need Section IV only if you can't trust the client: the browser already validates everything. Load the definition into a survey model on the server and set the submitted data; it reports answers that don't fit the definition: wrong types, unknown choices, missing required answers. Reject the save with the errors. Here the SurveyJS service does it, next to this app (docker compose up -d surveyjs).
The SurveyJS service is called at http://surveyjs:3000. If it isn't running, the endpoint answers
503 "SurveyJS service is not running": start it with docker compose up -d surveyjs.
Try this
Code that just ran
These regions are read from the files that served this page, the same lines the Server Integration page shows.
shared/client/validate-response.js · sjs:IV.1.client
View on GitHub
// Show the server's verdict in the completion message
survey.onComplete.add(async (sender, options) => {
options.showSaveInProgress();
const res = await fetch("/api/responses", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ formId: "claim", data: sender.data })
});
if (res.ok) return options.showSaveSuccess();
const { errors, error } = await res.json();
options.showSaveError(errors ? errors.map(describe).join("; ") : error);
});
routes/examples/validate-response.php · sjs:IV.1.server
View on GitHub
// POST /api/responses — ask the SurveyJS service to validate, then store as usual
Route::post('/api/responses', function (Request $request) {
$body = json_decode($request->getContent(), flags: JSON_THROW_ON_ERROR); // the raw body keeps {} as {} (see I.1)
$definition = DB::table('forms')->where('key', $body->formId ?? null)->value('json') ?? abort(404, 'Unknown form');
try {
$check = Http::baseUrl(config('surveyjs.service_url'))->timeout(10)
->post('/response', ['schema' => json_decode($definition), 'response' => $body->data ?? null]);
} catch (ConnectionException $e) { // cURL error 28 is a timeout
return str_contains($e->getMessage(), 'cURL error 28')
? response()->json(['error' => 'SurveyJS service timed out'], 504)
: response()->json(['error' => 'SurveyJS service is not running at '.config('surveyjs.service_url')], 503);
}
if ($check->status() === 422 && $check->json('errors') !== null && ! array_key_exists('warnings', $check->json())) {
return response()->json(['errors' => $check->object()->errors], 400); // the answers don't fit the definition
}
if ($check->status() !== 200 || $check->body() !== '{}') { // fail closed: only exactly {} means valid
Log::error('SurveyJS service: unexpected answer from /response', ['status' => $check->status(), 'body' => $check->body()]);
abort(502, $check->status() === 422 && $check->json('warnings') !== null ? 'The stored definition has errors' : 'SurveyJS service error');
}
$id = DB::table('responses')->insertGetId(['form_id' => $body->formId, 'created_at' => now('UTC')->toIso8601ZuluString(),
'data' => json_encode($body->data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION | JSON_THROW_ON_ERROR)]);
return response()->json(['id' => $id], 201);
});
Definition: shared/definitions/relational-storage.v1.json