DEV Community

Cover image for Validating the right way: API gateway with JsonSchema
Jodamco
Jodamco

Posted on

Validating the right way: API gateway with JsonSchema

We all know input validation, right? Tons of ways on doing it and it saves us a lot of time preventing troubles of many types. There are different types of validation and for different use cases we may use different approachs. We may validade inputs with some requirements, custom functions and, whenever using #Angular we are able to validate things through some high level Validation function from Reactive Forms module.

Well, before start with Backend i always got myself thinking about how to validate the body of a REST requisition. Don't get me wrong, I can for sure guarantee that things will be sent fine, but always wandered if the backend function receiving the data would have to parse it back and validate the same way I do when the front end receive data. Some time passed, I started to do backend and had to come up with a solution and since we used API Gateways, things were right next.

When I was getting things done and understanding stuff propperly with serverless and API Gateway, I used to validate inputs like this:

module.exports.myAPIPostFn = async (event) => {
    const { prop1, prop2, prop3 } = event.body
    if(notNull(prop1) || notNull(prop2) || notNull(prop3))
        throw new Error('Error [400]: invalid body')

    ... // code continues...
}
Enter fullscreen mode Exit fullscreen mode

It doesn't sit right, you see? By doing that, I feel like i'm making the function responsible for it's input integrity, while it should receive things right in first place. Of course that if I want to be sure no side effects would occour I could try and use some classes and models for it also, but it also didn't fit with the way things were being done.

If the caller was the responsible, then I should try and find something to validate things at it's side. That's when I found the input validation with JSON Schema. As stated in its official website

JSON Schema is the vocabulary that enables JSON data consistency, validity, and interoperability at scale

See? Just what I was looking for. It has a set of rules that one can use in a very declarative way to define what a JSON should look like. A JSON schema would look like this

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "required": [
        "name",
        "surName",
        "age",
        "email",
        "phone",
        "isMarried"
    ],
    "properties": {
        "name": { "type": "string" },
        "surName": { "type": "string" },
        "age": { "type": "integer" },
        "email": { "type": "string" },
        "phone": { "type": "string" },
        "isMarried": { "type": "boolean" }
    },
    "additionalProperties": false
}

Enter fullscreen mode Exit fullscreen mode

With this schema I am able to define the object's properties with name, type and also prevent additional props from comming with it. The best part of it, when using serverless with API Gateway, is that the integration is as symple as including a line of code (actually, 3 if you count linke breaks):

newClient:
    handler: src/functions/client/handler.newClient
    events:
        - http:
              path: client
              method: put
              integration: LAMBDA
              authorizer: ${self:custom.authorizerConfig}
              **request:
                  schemas:
                      application/json: ${file(schemas/new-client.json)}**
Enter fullscreen mode Exit fullscreen mode

When using serverless you can provide several configs for your event type, and when it comes to using HTTP event type you are also able to define a schema for the request. When the schema is defined, the serveless will setup the API in a way that it uses the provided schema to validate the body of the request being received. This will ensure that when the lambda function executes, the event.body will have all the desired properties and, if they don't, the API Gateway will gracefully respond with Invalid request body without ever call the lambda.

In the end I got quite happy with this approach since it brought me some advantages:

  • Cleaner code (we all love it)
  • Better controll over contract between front and backend without changing the way the backend works (AKA not implementing models)
  • Close one more security breach, since now I am able to prevent some malicious inputs
  • Cost saving: I no longer have to execute lambda code to notice that inputs are wrong.

Looney Toones

That's it for now. Let me know in the comment section if you would have done differently. I am also looking for some simmilar tool to validate data comming from path and query parameters, if you happen to know I would be glad to hear!

Top comments (2)

Collapse
 
ivis1 profile image
Ivan Isaac

This is really informative! Could you elaborate more on how JSON Schema handles nested objects in validation?

Collapse
 
jodamco profile image
Jodamco

Yes, Ivan!
You can declare objects and their structures in a simmilar way you would do with classes and interfaces.

For instance, this schema:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "required": ["user", "userId"],
    "properties": {
        "captcha": { "type": "string" },
        "userId": { "type": "string" },
        "user": {
            "type": "object",
            "required": [
                "firstName",
                "lastName",
                "phone",
                "email",
            ],
            "properties": {
                "firstName": { "type": "string" },
                "lastName": { "type": "string" },
                "phone": { "type": "string" },
                "email": { "type": "string" },
                "photo": { "type": "string" },
                "eventDates": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            },
            "additionalProperties": false
        }
    },
    "additionalProperties": false
}

Enter fullscreen mode Exit fullscreen mode

As you can see, you may have nested objects and also arrays. I never needed, but you can also define the objects within the schema and reuse instead of re-declare it's whole structure.