How to add validation to a custom API in Strapi?

How to add validation to a custom API in Strapi?

Vivek Agarwal wrote a great tutorial for adding custom API endpoints in Strapi. It didn't include any tips for validating the request input. In this tutorial, I will show how to create a pleasant user-friendly API validation experience in Strapi 4.

Strapi is using Koa. An easy way to add validation for custom API endpoints is by using Koa compatible validation library. I've had a good experience with node-input-validator. It has a very nice validation syntax and good flexibility for defining new validation rules.

This tutorial is tested with

Framework / LibraryVersion
Strapi4.5.5
node-input-validator4.5.1

Prerequisites

  1. Install Strapi with Typescript option.

  2. $ npm i node-input-validator --save

Steps

1. Create a custom API endpoint

$ npx strapi generate
? Strapi Generators api - Generate a basic API
? API name check-number
? Is this API for a plugin? No
✔  ++ /api/check-number/routes/check-number.ts
✔  ++ /api/check-number/controllers/check-number.ts
✔  ++ /api/check-number/services/check-number.ts

2. Create a route middleware

$ npx strapi generate      
? Strapi Generators middleware - Generate a middleware for an API
? Middleware name validate
? Where do you want to add this middleware? Add middleware to an existing API
? Which API is this for? check-number
✔  ++ /api/check-number/middlewares/validate.ts

3. Implement validation middleware

// File: /api/check-number/middlewares/validate.ts.
import niv from "node-input-validator";

export default (config) => {
  return niv.koa();
};

4. Implement a route

// File: /api/check-number/routes/check-number.ts
export default {
  routes: [
    {
     method: 'POST',
     path: '/is-it-big',
     handler: 'check-number.checkBig',
     config: {
       policies: [],
       middlewares: ["api::check-number.validate"],
     },
    },
  ],
};

5. Implement a controller

// File: /api/check-number/controllers/check-number.ts
export default {
  checkBig: async (ctx, next) => {
    try {
      await ctx.validate({
        number: "required|integer|min:0",
      });

      const checkNumberService = strapi.service(
        "api::check-number.check-number"
      );

      ctx.body = checkNumberService.confirmBigNumber(
        Number(ctx.request.body.number)
      );
    } catch (err) {
      ctx.body = err;
    }
  },
};

6. Implement a service

// File: /api/check-number/services/check-number.ts
export default {
  confirmBigNumber: (number: number): string => {
    if (number >= 1000000) {
      return `${number} is big! Yay! 👍`;
    }
    if (number < 1000000) {
      return `${number} is too small! Boo! 👎`;
    }
  },
};

7. Allow access to the API endpoint

Log in to the Strapi admin interface and allow the checkBig endpoint. See the screenshot below for reference.

8. Test the API endpoint

Ensure that Strapi is running on localhost:1337. Then try these curl commands.

$ curl -X POST -F 'number=word' localhost:1337/api/is-it-big
{"message":"Unprocessable Entity","body":{"message":"The given data is invalid.","errors":{"number":{"message":"The number must be an integer.","rule":"integer"}}}}

$ curl -X POST -F 'number=-1' localhost:1337/api/is-it-big
{"message":"Unprocessable Entity","body":{"message":"The given data is invalid.","errors":{"number":{"message":"The number must be at least 0.","rule":"min"}}}}

$ curl -X POST -F 'number=10000' localhost:1337/api/is-it-big 
10000 is small! 👎

$ curl -X POST -F 'number=11234152' localhost:1337/api/is-it-big 
11234152 is big! Yay! 👍

How about GET params?

Here we go.

// File: /api/check-number/routes/check-number.ts
export default {
  routes: [
    ...
    {
      method: "GET",
      path: "/credit-card/:input",
      handler: "check-number.checkCreditCard",
      config: {
        policies: [],
        middlewares: ["api::check-number.validate"],
      },
    },
  ],
};

// File: /api/check-number/controllers/check-number.ts
export default {
  ...
  checkCreditCard: async (ctx, next) => {
    try {
      await ctx.validate(
        { input: "required|creditCard" },
        ctx.params
      );

      ctx.body =
        "Are you crazy!? You just sent a credit card number inside a GET parameter.";
    } catch (err) {
      ctx.body = err;
    }
  },
};

Postfix

Let me know if you found this useful!

Please, comment also if you have any critical notes or some ideas on how to do validation even better! It's always valuable to share ideas and insights so we can all be better developers together. Thanks!