Treat validation as an application boundary
Validate client data before logic that writes to a database. A schema describes the data shape; it does not replace authorization, rate limits or business rules. Set text-length limits and meaningful numeric ranges. Do not build executable schemas from user-submitted code.
Define input and output
This example assumes a Node.js project with Fastify and ES modules. Match the Node version to the framework release. The response schema determines which fields are serialized.
import Fastify from "fastify";
const app = Fastify();
app.post("/notes", {
schema: {
body: {
type: "object",
required: ["text"],
properties: {
text: { type: "string", minLength: 1, maxLength: 500 }
}
},
response: {
201: { type: "object", properties: { ok: { type: "boolean" } } }
}
}
}, async (request, reply) => {
return reply.code(201).send({ ok: true });
});The example does not persist a note. A real application must check the user’s identity and permission before writing. Validator defaults may modify inputs; decide whether type coercion or removal of additional properties is appropriate, and test that policy explicitly.
Test rejected input
const result = await app.inject({
method: "POST",
url: "/notes",
payload: { text: "" }
});
if (result.statusCode !== 400) throw new Error("Expected 400");
await app.close();Add a valid request, a missing field and an oversized string. inject exercises routing without opening a public port. Avoid testing only the exact wording of an error: verify the status and that persistence logic did not run.
Keep expensive I/O out of structural validation
Database account checks or external API calls belong in a suitable hook after structural validation. Limit body size, request volume and operation duration separately. Never return raw exceptions, secrets or database connection details to clients.
Sources and documentation
Source links are retained for checking dates, availability and original context.