Error-Proof APIs: Adding Resilience with Validation and Error Handling
Table of contents
- The Importance of Error Handling in APIs
- Adding Basic Error Handling to the API
- Using Middleware for Centralized Error Handling
- Data Validation – Ensuring Clean and Reliable Input
- Testing Error Handling and Validation with Postman
- Enhanced API with Error Handling and Validation
- Strengthening APIs with Validation and Error Handling
In the previous part, we structured our API according to REST principles, adding routes to handle various data operations. Now, it’s time to level up by implementing error handling and data validation. Error handling ensures that users receive clear messages if something goes wrong, while validation helps keep data clean and reliable.
Let’s dive in and make our Space Mission Info API more resilient and user-friendly!
The Importance of Error Handling in APIs
In any API, errors are inevitable, whether due to invalid data, missing resources, or unexpected server issues. Proper error handling:
Improves user experience by providing clear feedback.
Protects the server from processing faulty or malicious requests.
Simplifies debugging for developers by pinpointing specific issues.
Common sources of errors include:
Invalid or missing data: Input data that doesn’t match the required format.
Unavailable resources: Requests for data that doesn’t exist (e.g., a non-existent mission ID).
Server errors: Unhandled exceptions or system issues that prevent processing.
With error handling, we can respond to each type of issue with a meaningful status code and message, guiding users through the API seamlessly.
Adding Basic Error Handling to the API
To handle errors, we’ll use Express’s built-in error-handling middleware. This allows us to catch errors from our routes and respond with helpful messages.
Step 1: Adding Error Handling to Existing Routes
Let’s update the GET, PUT, and DELETE routes to include error handling.
Add the following code to index.js:
// GET route to retrieve a specific mission by ID with error handling
app.get('/missions/:id', (req, res) => {
const missionId = parseInt(req.params.id);
const mission = missions.find(m => m.id === missionId);
if (!mission) {
return res.status(404).json({ error: "Mission not found" });
}
res.status(200).json(mission);
});
// PUT route with error handling for missing missions
app.put('/missions/:id', (req, res) => {
const missionId = parseInt(req.params.id);
const updatedData = req.body;
const mission = missions.find(m => m.id === missionId);
if (!mission) {
return res.status(404).json({ error: "Mission not found" });
}
// Update mission properties
mission.name = updatedData.name || mission.name;
mission.destination = updatedData.destination || mission.destination;
mission.launchDate = updatedData.launchDate || mission.launchDate;
res.status(200).json(mission);
});
// DELETE route with error handling for missing missions
app.delete('/missions/:id', (req, res) => {
const missionId = parseInt(req.params.id);
const missionIndex = missions.findIndex(m => m.id === missionId);
if (missionIndex === -1) {
return res.status(404).json({ error: "Mission not found" });
}
const deletedMission = missions.splice(missionIndex, 1);
res.status(200).json(deletedMission[0]);
});
Explanation:
404 Not Found: If the specified mission doesn’t exist, the route responds with a 404 status and a descriptive error message.
Error Response: Each error is returned in JSON format to keep responses consistent.
Using Middleware for Centralized Error Handling
In a larger application, handling errors in each route individually can become repetitive. Instead, we can create a centralized error-handling middleware that catches errors from all routes.
Add this middleware function at the bottom of index.js:
// Centralized error-handling middleware
app.use((err, req, res, next) => {
console.error(err.stack); // Log error stack trace for debugging
res.status(500).json({ error: "Something went wrong on the server. Please try again later." });
});
Explanation:
app.use(): Registers a middleware function in Express.
err.stack: Logs the error stack trace to the console for debugging.
500 Internal Server Error: Responds with a 500 status if an unhandled server error occurs.
With this middleware, if any unexpected errors occur, our users receive a clear message while we see the technical details in the console.
Data Validation – Ensuring Clean and Reliable Input
To keep our data accurate and prevent issues, we need data validation. For instance, when adding or updating a mission, we want to ensure:
name is a non-empty string,
destination is a valid location,
launchDate is in the correct date format.
Express doesn’t have built-in validation, so we’ll use a package called Joi to define and enforce validation rules.
Step 1: Install Joi for Data Validation
To get started with Joi, run the following command:
npm install joi
Step 2: Define Validation Schemas
Let’s define a validation schema for our mission data, ensuring it meets our requirements.
Add the following code to index.js:
const Joi = require('joi');
// Define Joi validation schema for missions
const missionSchema = Joi.object({
name: Joi.string().min(3).required(),
destination: Joi.string().required(),
launchDate: Joi.date().iso().required()
});
Explanation:
Joi.string().min(3): Ensures
name
is a string with at least 3 characters.Joi.date().iso(): Validates that
launchDate
is an ISO-formatted date.
Step 3: Apply Validation to Routes
We’ll use this schema in our POST and PUT routes to validate incoming data.
// POST route with validation
app.post('/missions', (req, res) => {
const { error } = missionSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
const newMission = req.body;
newMission.id = missions.length + 1;
missions.push(newMission);
res.status(201).json(newMission);
});
// PUT route with validation
app.put('/missions/:id', (req, res) => {
const missionId = parseInt(req.params.id);
const mission = missions.find(m => m.id === missionId);
if (!mission) {
return res.status(404).json({ error: "Mission not found" });
}
const { error } = missionSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
// Update mission properties if validation passes
mission.name = req.body.name;
mission.destination = req.body.destination;
mission.launchDate = req.body.launchDate;
res.status(200).json(mission);
});
Explanation:
missionSchema.validate(req.body): Validates the incoming data against the defined schema.
400 Bad Request: If validation fails, the API responds with a 400 status and a specific error message.
Now, if a user sends invalid data, the API provides a helpful message explaining what went wrong.
Testing Error Handling and Validation with Postman
Let’s test our enhanced API using Postman, focusing on error handling and validation.
Invalid GET Request: Try retrieving a non-existent mission by sending a GET request to
/missions/99
. You should see a 404 Not Found response.Invalid POST Request: Send a POST request to
/missions
without all required fields. For example:{ "name": "Test Mission" }
The response should be 400 Bad Request with an error message specifying the missing fields.
Invalid Data Format: Send a POST request with an invalid date format:
{ "name": "Mars Rover", "destination": "Mars", "launchDate": "Mars-Day" }
The response should indicate that
launchDate
is not in the correct format.
Testing each scenario ensures that our error handling and validation logic is working as expected, providing users with meaningful feedback.
Enhanced API with Error Handling and Validation
Here’s a flowchart summarizing the improved request-response flow with error handling and validation:
[ Client (e.g., Postman) ]
|
+----------|----------+
| HTTP Method |
| - GET, POST, PUT, DELETE |
+----------|----------+
|
[ API Server with Validation ]
|
Validates Data with Joi
|
+----------|----------+
| Error Handling |
| - 400, 404, 500 status |
+----------|----------+
|
[ Client (Receives Response) ]
This flow illustrates how our API processes requests, validates data, and responds with appropriate error messages if needed.
Strengthening APIs with Validation and Error Handling
In this part, we focused on making our Space Mission Info API robust by adding error handling and data validation. We:
Implemented centralized error handling to manage unexpected issues.
Used Joi for validating incoming data, ensuring clean and reliable inputs.
Tested each route to confirm that the API responds appropriately to different types of requests and errors.
In the next part, we’ll look at adding authentication and security measures to protect our API, making it production-ready.
Stay tuned for the next part, where we’ll secure our API with authentication and additional safeguards!
Subscribe to my newsletter
Read articles from gayatri kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by