Validating Rails API requests with Reform
I’ve written about the creation of custom exception classes to organize code in Rails web applications and APIs. However, while going through my previous article, I realized that my final example glossed over how request parameter validation failures would be detected by the API in the first place.
Today I want to discuss a pattern that I haven’t seen in the wild - one which uses Reform - form objects, decoupled from models (their description) - and exception classes to beautifully manage API parameter validation.
An intro to form objects, for those unfamiliar with it.
If you haven’t used form objects before, know that they allow you to decouple validation of input from the user (via forms), and the validation of data managed by a
Model. My primary reason for favoring this separation is that it allows the model validations to be less constrained than form validation.
Validations failures from a model should be unexpected, whereas form validations are always expected (users make mistakes) and thus should be handled as a natural part of your application. Decoupling in this manner also makes it easy to manage multiple forms that interact with one or more models, with different validation requirements. Google
form validation vs model validation to learn more.
But API-s don’t have forms. Right?
No, they don’t. What they do have are end-points where you POST data, and expect something about your application’s database to change. Such data needs to be validated. This article is going to focus on how to use Reform - a library meant for validating data submitted by users into forms, to validate data submitted by clients to API endpoints. Clients instead of users, and API endpoints instead of forms. Let’s begin.
Let’s assume that we need to implement a simple invite route for our API - to invite other users to join our platform. It accepts just two parameters:
…and responds with a
200 OK if the invitation can be processed.
However, as with all such requests, there are conditions which must be fulfilled before the request can be executed. Let’s list down three simple requirements:
nameis optional, but if present, it must be longer than 3 characters.
The last one is a bit contrived, but this is just a demonstration. ¯_(ツ)_/¯
Create a Reform Form.
Let’s get the basics out of the way. Setting up Reform should be pretty simple. Add
gem 'reform-rails', '~> 0.1.7' to your
Gemfile, and run
bundle install. That should be it. Now, let’s build our form object.
reform-rails allows you to write validations in much the same way that you’d write validations for regular Rails models. Granted that this is a very simple form, but even complex Reform forms are readable, and its API is rich and flexible.
If you’re confused by
email: true, I’m assuming the presence of a custom Rails validator. For an example, check out Rails’ documentation on adding custom validators.
Note that I’m overriding the default
save method, since I don’t want Reform touching the model. I advise keeping code handling the actual invitation out of the form. Our form is responsible for data validation, not operations. Leave business logic to services.
Set up the endpoint.
Reform requires explicit validation which, I think, reads better than the Rails way. Unlike Rails’
if @user.save, Reform asks the form object to validate the form with the supplied params, and then do something if the validation passes (or something else when it fails).
In this case, a failure to validate lets us raise
ValidationException, passing it the form object. This is a custom exception which I’d elaborated on in a previous article. Let’s take another look.
I’m going to extend an
ApplicationException class that I went into detail in my article on exception classes:
Coupled with application level exception handling, as described in my previous post, if you POST to the API with invalid data, it responds with a lot of detail.
Isn’t that beautiful?
Those validation errors can now be used by the client to display the failure to the user. Obviously, this needs to be set up only once. As long as you validate params with Reform, and pass the form with the errors to
ValidationFailureException when raising it, responses in a standard format are taken care of.
Form objects are a great abstraction that allow us to deal with of the process of validating user input in one place. If you’re building an API for other developers or the public, good documentation is an absolute necessity. And while invalid form submissions showing users what’s wrong is commonplace, APIs doing something similar for their clients is rare.
Using Reform, it’s possible to give consumers of your API insights into what went wrong the same way a user entering incorrect information into a form is treated. Sure, once set up, an API client won’t make (the same) mistake again. But when integrating with a new API, a little help can go a long way.