Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Validating data with a schema

A client can send anything: an empty title, a number instead of text, extra fields. Fastify checks incoming data against a schema — a description in the JSON Schema format (a standard where a data structure is defined with a plain object). If the data does not match, the handler is not even called, and the client gets a 400 error.

A body schema

The schema is passed as the second route argument in the schema option. In src/routes/posts.js:

// src/routes/posts.js
const createPostSchema = {
  body: {
    type: 'object',
    required: ['title', 'body'],
    properties: {
      title: { type: 'string', minLength: 3, maxLength: 120 },
      body: { type: 'string', minLength: 1 },
    },
  },
};
 
app.post('/posts', { schema: createPostSchema }, async (request, reply) => {
  const post = { id: posts.length + 1, ...request.body };
  posts.push(post);
  return reply.code(201).send(post);
});

The schema requires two string fields: title of 3 to 120 characters and a non-empty body.

What the client gets

Send a post without text:

curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -d '{"title":"Hello"}'

The server answers with code 400:

{
  "statusCode": 400,
  "code": "FST_ERR_VALIDATION",
  "error": "Bad Request",
  "message": "body must have required property 'body'"
}

A request with both fields passes and returns the new post with code 201.

Params and query

The same rules work for params and querystring (that is the name of the query string schema):

// src/routes/posts.js
const listSchema = {
  querystring: {
    type: 'object',
    properties: {
      limit: { type: 'integer', minimum: 1, maximum: 50, default: 10 },
    },
  },
};
 
const showSchema = {
  params: {
    type: 'object',
    properties: {
      id: { type: 'integer' },
    },
  },
};
 
app.get('/posts', { schema: listSchema }, async (request) => {
  return posts.slice(0, request.query.limit);
});
 
app.get('/posts/:id', { schema: showSchema }, async (request) => {
  return posts.find((post) => post.id === request.params.id);
});

Fastify converts strings to numbers by the integer type, so Number() is no longer needed. default: 10 is used when limit is missing, and the address /posts/abc returns a 400 error.

Main keywords

KeywordExampleMeaning
type'string', 'integer', 'object'Value type
required['title']Required fields
minLength, maxLength3String length
minimum, maximum1Number bounds
enum['draft', 'published']Allowed values
format'email'String format
default10Default value

Common mistake

In Fastify 5 a schema must be complete. The old shorthand without type: 'object' and properties is no longer supported: the server either fails to start or the check silently does nothing:

// src/routes/posts.js
// Wrong in Fastify 5:
const schema = { querystring: { limit: { type: 'integer' } } };

Always write type: 'object' and list the fields in properties.

Thanks for reading!

Donate to the site — we work hard for you.