In this task, you will employ Hono’s error handling capabilities to catch unhandled errors and return consistent error responses to clients.
Error handling is another critical aspect of building reliable APIs. Proper error handling ensures that your API can gracefully handle unexpected errors, provide meaningful error messages to clients, and prevent sensitive information from leaking to the outside world.
We have already implemented basic error handling in the API by returning appropriate status codes and error messages when an error occurs. Here, we will expand on this by exploring Hono’s error handling capabilities.
Currently, every route handler follows the same pattern for error handling:
try {
// Route handler logic
} catch (error) {
return c.json({ error: "An error occurred" }, 500);
}
While this approach works, it can be improved by centralizing error handling logic and providing consistent error responses across all route handlers.
Hono provides a way to define a global error handler that can catch unhandled errors and return a consistent error response. This is done using the app.onError
method.
Add the following code to the src/app.ts
file to define a global error handler:
// Error handling
app.onError((err, c) => {
console.error("Caught error:", err);
return c.json(
{
message: "An unexpected error occurred",
},
500,
);
});
In this code snippet:
app.onError
method.err
(the error object) and c
(the context object).Now that we have a global error handler in place, we can simplify the route handlers by removing the try-catch blocks and relying on the global error handler to catch any unhandled errors.
Update the route handlers in the src/routes/posts.ts
file to remove the try-catch blocks. Once you remove the try-catch blocks, test the API:
sqlite.db
file!You should see the global error handler response (An unexpected error occurred) with a 500 status code.
HTTPException
for custom errorsIn addition to catching unhandled errors, you can also throw custom exceptions to handle specific error cases. Hono provides an HTTPException
class that you can use to throw HTTP errors with custom status codes and messages.
For example, when fetching a specific post by ID, you can throw a 404 Not Found
error if the post does not exist:
import { HTTPException } from "hono/http-exception";
// GET a specific post by ID
postRoutes.get("/posts/:id", zValidator("param", getPostSchema), async (c) => {
const { id } = c.req.valid("param");
const post = await db.select().from(posts).where(eq(posts.id, id)).get();
if (!post) {
throw new HTTPException(404, { message: "post not found" }); // // 👀 Look here
}
return c.json(post); // HTTP Status code 200 "Ok"
});
In this code snippet:
HTTPException
class from hono/http-exception
.404 Not Found
error with a custom error message.Next, update the src/app.ts
file to handle HTTPException
errors and return the appropriate status code and message:
// Error handling
app.onError((err, c) => {
console.error("Caught error:", err);
if (err instanceof HTTPException) { // 👀 Look here
return err.getResponse();
}
return c.json(
{
message: "An unexpected error occurred",
},
500,
);
});
In this code snippet:
HTTPException
.HTTPException
instance.Now update the remaining route handlers to throw HTTPException
for specific error cases and test the API.
The src/routes/posts.ts
file should now look like this:
import { Hono } from "hono";
import { db } from "../db";
import { posts } from "../db/schema";
import { eq } from "drizzle-orm";
import {
createPostSchema,
updatePostSchema,
getPostSchema,
} from "../validators/schemas";
import { zValidator } from "@hono/zod-validator";
import { HTTPException } from "hono/http-exception";
const postRoutes = new Hono();
// Get all posts
postRoutes.get("/posts", async (c) => {
const allPosts = await db.select().from(posts);
return c.json(allPosts);
});
// Get a single post by id
postRoutes.get(
"/posts/:id",
zValidator("param", getPostSchema),
async (c) => {
const id = parseInt(c.req.param("id"));
const post = await db.select().from(posts).where(eq(posts.id, id)).get();
if (!post) {
throw new HTTPException(404, { message: "Post not found" });
}
return c.json(post);
}
);
// Delete a post by id
postRoutes.delete(
"/posts/:id",
zValidator("param", getPostSchema),
async (c) => {
const id = parseInt(c.req.param("id"));
const deletedPost = await db
.delete(posts)
.where(eq(posts.id, id))
.returning()
.get();
if (!deletedPost) {
throw new HTTPException(404, { message: "Post not found" });
}
return c.json(deletedPost);
},
);
// Create a new post
postRoutes.post(
"/posts",
zValidator("json", createPostSchema),
async (c) => {
const { content } = await c.req.json();
const newPost = await db
.insert(posts)
.values({
content,
date: new Date(),
})
.returning()
.get();
return c.json(newPost);
}
);
// Update a post by id
postRoutes.patch(
"/posts/:id",
zValidator("param", getPostSchema),
zValidator("json", updatePostSchema),
async (c) => {
const id = parseInt(c.req.param("id"));
const { content } = await c.req.json();
const updatedPost = await db
.update(posts)
.set({ content })
.where(eq(posts.id, id))
.returning()
.get();
if (!updatedPost) {
throw new HTTPException(404, { message: "Post not found" });
}
return c.json(updatedPost);
},
);
export default postRoutes;
With these changes, you have improved the error handling in the API by centralizing error handling logic, throwing custom exceptions for specific error cases, and returning consistent error responses to clients.
In this task, you learned how to improve error handling in your API by using Hono’s error handling capabilities. By defining a global error handler, throwing custom exceptions, and returning consistent error responses, you can enhance the reliability and maintainability of your API. Proper error handling is essential for building robust APIs that provide a good user experience and are easy to debug and maintain.