Lesson 1
Handling Errors in API Requests
Introduction to Error Handling in API Requests

Welcome to the first lesson of Efficient API Interactions with Scala 3. In this course, you will learn how to handle common scenarios when working with APIs more effectively. One of the key aspects of working with APIs is error handling. Handling errors gracefully not only helps in building robust applications but also enhances user experience by providing meaningful feedback when things go wrong. Our goal in this lesson is to help you manage these outcomes effectively.

Understanding HTTP Status Codes

When you send a request to an API, the server responds with an HTTP status code. These codes indicate the result of your request. Understanding them is essential for effective error handling. Here's a brief overview:

  • 2xx (Success): Indicates that the request was successfully received, understood, and accepted. For example, a 200 status code means OK.
  • 4xx (Client Errors): Suggests that there was an error in the request made by your client. For example, 404 means the requested resource was not found.
  • 5xx (Server Errors): Indicates that the server failed to fulfill a valid request. A common code here is 500, which means an internal server error.

By paying attention to these codes, you can determine whether your request succeeded or if there was a problem that needs addressing.

Handling HTTP Errors Using Scala Functional Paradigms

Scala's functional paradigm allows for elegant error handling using constructs like Try, Success, and Failure. In Requests-Scala, handling errors can be effectively achieved through these constructs, providing a structured approach to managing exceptions.

Consider the following example, which fetches todo items from an API:

Scala
1import requests.* 2import scala.util.{Try, Success, Failure} 3 4def fetchTodos(): Unit = 5 // Base URL for the API 6 val baseUrl = "http://localhost:8000" 7 8 // Attempt to fetch all todos with error handling 9 Try(requests.get(s"$baseUrl/todos")) match 10 case Success(resp) if resp.statusCode < 400 => 11 println("Todos fetched successfully!") 12 case Success(resp) => 13 println(s"HTTP error occurred: ${resp.statusCode} ${resp.statusMessage}") 14 case Failure(exception) => 15 println(s"Other error occurred: ${exception.getMessage}")

In this example:

  • Try is used to encapsulate the request logic.
  • Success and Failure handle the normal and exception cases, respectively.
  • Using pattern matching, we handle specific status codes and exceptions separately.
Examples: Non-existent Route

In this scenario, a GET request is sent to a non-existent route, leading to an HTTP error because a 404 Not Found status code is returned.

Scala
1def checkInvalidRoute(): Unit = 2 val baseUrl = "http://localhost:8000" 3 Try(requests.get(s"$baseUrl/invalid-route")) match 4 case Success(resp) if resp.statusCode == 404 => 5 println("HTTP error occurred: 404 Not Found") 6 case Success(resp) => 7 println(s"Unexpected status: ${resp.statusCode} ${resp.statusMessage}") 8 case Failure(exception) => 9 println(s"Other error occurred: ${exception.getMessage}")

This will produce the following output indicating that the requested resource was not found:

Plain text
1HTTP error occurred: 404 Not Found
Examples: POST Request Without Required Title

Continuing with error handling, the next scenario involves sending a POST request without a required field, the title, resulting in an HTTP error due to a 400 Bad Request.

Scala
1def postInvalidTodo(): Unit = 2 val baseUrl = "http://localhost:8000" 3 Try(requests.post(s"$baseUrl/todos", data = ujson.Obj())) match 4 case Success(resp) if resp.statusCode == 400 => 5 println("HTTP error occurred: 400 Bad Request") 6 case Success(resp) => 7 println(s"Unexpected status: ${resp.statusCode} ${resp.statusMessage}") 8 case Failure(exception) => 9 println(s"Other error occurred: ${exception.getMessage}")

The following output shows a 400 Bad Request error, indicating missing required fields:

Plain text
1HTTP error occurred: 400 Bad Request
Examples: Handling Broader Request-Related Issues

Let's examine how to handle broader request-related issues. This example demonstrates a scenario where a Failure occurs due to connectivity issues or other problems external to the HTTP response itself.

Scala
1def checkInvalidUrl(): Unit = 2 Try(requests.get("http://invalid-url")) match 3 case Success(_) => 4 println("Unexpected success") 5 case Failure(exception) => 6 println(s"Other error occurred: ${exception.getMessage}")

When a connection cannot be established, the following output will provide details about the connectivity issue:

Plain text
1Other error occurred: Connection error (NameResolutionException ... etc)
Summary and What's Next

In this lesson, you learned about the importance of error handling in API requests and were introduced to effective techniques using Scala's Try, Success, Failure constructs along with Requests-Scala. These practices are crucial for creating robust applications that deal with errors gracefully and provide clear feedback to users.

You are now equipped to practice these skills through hands-on exercises that will reinforce the concepts you've learned. As you move forward, you'll continue to build on these techniques to engage with more advanced API features. Remember, practicing error handling is key — experiment with different scenarios to see how errors are managed and how they affect your applications.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.