Input Validation in REST APIs

December 15, 20244 min read

Input Validation in REST APIs

Input validation is crucial for building secure and robust REST APIs. In this article, we will explore how to implement input validation in Spring Boot using Java, Kotlin, and Go (with Gin).


🌟 Why Validate Input?

Validation ensures the data sent to your API adheres to expected formats and prevents potential vulnerabilities like SQL Injection, XSS, and bad data entries.


🛠️ Step 1: Add Validation Dependencies

For Spring Boot projects, include the following dependencies:

  • Maven:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
  • Gradle:
implementation 'org.springframework.boot:spring-boot-starter-validation'

For Go projects with Gin, use the following:

# Install the Gin framework go get -u github.com/gin-gonic/gin # Install the validator package go get -u github.com/go-playground/validator/v10

📖 Step 2: Define a DTO with Validation Rules

Use annotations to define validation constraints on fields. Examples include @NotNull, @Size, and @Pattern.

package com.example.demo.dto; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Data; @Data public class TodoRequest { @NotNull(message = "Title is required") @Size(min = 3, max = 50, message = "Title must be between 3 and 50 characters") private String title; private boolean completed; }
package com.example.demo.dto import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Size data class TodoRequest( @field:NotNull(message = "Title is required") @field:Size(min = 3, max = 50, message = "Title must be between 3 and 50 characters") val title: String?, val completed: Boolean = false )
package dto import ( "github.com/go-playground/validator/v10" ) type TodoRequest struct { Title string `validate:"required,min=3,max=50"` Completed bool `validate:""` } var validate = validator.New() func ValidateTodoRequest(todo TodoRequest) error { return validate.Struct(todo) }

📘 Step 3: Create a Controller with Validation

Integrate validation into your REST endpoints.

package com.example.demo.controller; import com.example.demo.dto.TodoRequest; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/todos") public class TodoController { @PostMapping public String createTodo(@Validated @RequestBody TodoRequest request) { return "Todo created: " + request.getTitle(); } }
package com.example.demo.controller import com.example.demo.dto.TodoRequest import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/api/todos") class TodoController { @PostMapping fun createTodo(@Validated @RequestBody request: TodoRequest): String { return "Todo created: ${request.title}" } }
package controller import ( "dto" "github.com/gin-gonic/gin" "net/http" ) func CreateTodoHandler(c *gin.Context) { var todo dto.TodoRequest if err := c.ShouldBindJSON(&todo); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := dto.ValidateTodoRequest(todo); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Todo created", "title": todo.Title}) }

📋 Step 4: Handle Validation Errors

Customize error handling to return user-friendly responses.

package com.example.demo.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.MethodArgumentNotValidException; import java.util.HashMap; import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage())); return errors; } }
package com.example.demo.exception import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice import org.springframework.web.bind.MethodArgumentNotValidException @RestControllerAdvice class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException::class) fun handleValidationExceptions(ex: MethodArgumentNotValidException): Map<String, String> { return ex.bindingResult.fieldErrors.associate { it.field to it.defaultMessage.orEmpty() } } }
package middleware import ( "github.com/gin-gonic/gin" "net/http" ) func ErrorHandler() gin.HandlerFunc { return func(c *gin.Context) { c.Next() if len(c.Errors) > 0 { c.JSON(http.StatusBadRequest, gin.H{"errors": c.Errors.JSON()}) } } }

🌐 main.go Example

Here is an example of the main.go file for setting up a Gin application:

package main import ( "controller" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.POST("/api/todos", controller.CreateTodoHandler) r.Run() // Start the server on http://localhost:8080 }

▶️ Step 5: Run the Application

To run the application:

Spring Boot (Java/Kotlin)

Run the Spring Boot application from your IDE or terminal:

./mvnw spring-boot:run # For Maven projects ./gradlew bootRun # For Gradle projects

Access the API at http://localhost:8080/api/todos.

Gin (Go)

Run the Go application:

go run main.go

Access the API at http://localhost:8080/api/todos.

🧪 Testing with cURL

Here are some example cURL commands to test the API:

  • POST a new Todo:
curl -X POST http://localhost:8080/api/todos \ -H "Content-Type: application/json" \ -d '{"title": "New Task", "completed": false}'
  • GET all Todos:
curl -X GET http://localhost:8080/api/todos
  • Handle Validation Errors:

Send an invalid request:

curl -X POST http://localhost:8080/api/todos \ -H "Content-Type: application/json" \ -d '{"title": ""}'

You should receive validation error messages as part of the response.