What is the best way to validate user input?

Introduction

Recently, during a pair programming session, I had a really interesting discussion. We talked about validating incoming payload which was basically a client’s input in a JSON format.

We were wondering if there’s one proper place to validate requests in web applications. Multiple ideas appeared so I’d like to present them here.

Plug / Middleware

The very first approach assumes we leverage intermediate layer just after routing (sometimes even within a router) and before a controller to provide specific validations. Technical details may depend on used framework and libraries but the idea is check payload correctness before it actually hits our business logic.

In Rails we would use middleware for that, in Elixir we could use plug, in Hapi we are able to use validations.

The entire concept is all about performing validations like checking types, formats, lengths and requirements the soonest as it’s possible. Let’s see how it could look like with Plug :

defmodule PlugValidation.Plugs.RequireParams do

import Plug.Conn def init(opts),

do: Keyword.fetch!(opts, :params) def call(conn, params) do

conn

|> query_params()

|> contains_fields?(params)

|> handle_response(conn)

end defp query_params(conn) do

conn

|> Plug.Conn.fetch_query_params()

|> Map.get(:query_params)

|> Map.keys()

end defp contains_fields?(keys, fields),

do: {Enum.all?(fields, &(&1 in keys)), fields, keys} defp handle_response({true, _fields, keys}, conn),

do: send_resp(conn, 200, "Query params were: #{inspect keys}.") defp handle_response({false, fields, _keys}, conn) do

send_resp(

conn,

400,

"Query params: #{inspect fields} must be provided!"

)

end

end

We define a custom middleware that accepts params option which is supposed to be an array of all required URL query params. We validate if incoming request contains all parameters we provided in these options. E.g. we may want that user always provide page_size when listing some content to avoid fetching all records from our DB. In case all keys are provided we respond with success, otherwise we return “bad request (400)” response.

The entire code is available here:

What are benefits and disadvantages of this approach?

Pros

We validate user request in the soonest possible place

We don’t invoke our controller logic until it really makes sense to do

Cons

Not every web framework / HTTP library provides such feature

Our validation may be somehow limited to just very simple checks

Form Object

This is an idea where you introduce FormObject pattern to wrap user input into a convenient structure with basic validation for your application. Our request hit a controller so we can build this from from received parameters. Having a proper Form Object we are sure that we will provide valid parameters in any place

Pros

Validation logic is encapsulated in an auxiliary module

We operate on valid params

Cons

You cannot validate domain logic here as it does not know about underlying services’ requirements

Services

In this way, we delegate input validation to a particular service. Imagine, we have a controller that accepts user order. The responsibility of this controller is not a single action but a series of steps to be covered, e.g. create a new order, send an invoice, schedule a delivery and so on.

Pros

Our controller or any other module doesn’t know anything about required validation.

We ensure context-based validation, a specific for every service

Cons

One service may succeed while the other can fail. We need to call a set of services in some kind of transaction.

Subscribe to get the latest content immediately

https://tinyletter.com/KamilLelonek

Summary

As you can see, there are many approaches to input validation. I believe there’s no silver bullet for that because all depends on the context. You can leverage your language/framework features to provide a specific validation or you can put this logic in the closest place where it makes sense. Everything varies from your particular use case so it’s up to us to make it the most reasonable.