Twitch

Lesson Description
The "Logging & JSON Error Responses" Lesson is part of the full, Complete Go for Professional Developers course featured in this preview video. Here's what you'd learn in this lesson:
Melkey refactors the API handlers to use the new utility functions for logging and responding with JSON error payloads. The API is tested with a few cURL commands for creating, updating, and deleting workouts.
Preview
CloseTranscript from the "Logging & JSON Error Responses" Lesson
>> Melkey: We're gonna do one more thing. You know those annoying format print line errors I've been asking you guys to put? Those are not great. That's very, very bad error handling. It works when it has to, but we can make our jobs of error debugging a little bit nicer and easier.
So we can go back to our app.go, and when we're declaring the workout handler, we're gonna give it a new argument logger, okay? Right now it's gonna say too many arguments in the call to our constructor, don't worry about it.
>> Melkey: And we're actually going to leverage our logger, the one we declared in the very first kind of submodule, to write error messages for us. So now, if we go back to our workout handler. Let me go all the way back up, this workout handler struck no longer is just going to be accepting the workout store. Now add logger, which can be of type log.logger.
>> Melkey: In our constructor method it's gonna take logger, it's gonna be that argument that we just passed in, log.logger. And then our actual return, we can put logger as logger, like so. So here comes the fun part, we'll be rewriting a lot of code. You can hate me, that's fine, I'll take it. But I want to show how we can really get something working, and then how we can improve on it. All right, so here we're gonna start with the HandleGetWorkupByID. Everything here, from paramsWorkoutID to the workoutID return we can delete. And here, what we're going to do instead is define workoutID err as utils.ReadIDParam, I'm gonna pass it in r. Okay, so we're gonna use the utility function we have here, we can check the error. So if err does not equal to nil, we can go ahead and write our wh, our logger like so, I'm gonna leverage this. We're gonna do Printf, and now this print is gonna be for us. I'm gonna make it a little bit nice. I'm gonna do error, all right? We can do readIDParam, something like this, %v, and I put the error like so. And we can also leverage our new writeJSON function. So we can do utils.writeJSON(w can pass an http status, so StatusBadRequest, okay? And then we can use utils.Envelope like so, and we can just say error and then pass in the key invalid workout id and make sure we return, okay? And so go ahead and copy those two lines, the logger line and the writeJSON line. And now, when we call or check errors, we can simply use that style, that blueprint, that schema, if you will, to handle our errors. So here, error, which can be getWorkoutByID. So that tells us exactly what the error is going to be, okay? Otherwise, instead of StatusBadRequest, http.StatusInternalServerError.
>> Melkey: Okay, and in the envelope after error, we can just say internal server error, because remember, this is gonna be for our client. Our client doesn't necessarily need to know what's happening from our own back end. If our back end is breaking, that's on us to log and figure out. We don't need to send every error message back to the caller, especially users.
>> Melkey: You can, of course, be more explicit and maybe provide them an error code that they can then go to customer support or something like that. But that kind of be outside the scope of what we're doing here, all right? And then we can just simply delete all this stuff from the get workout ID at the bottom. We can, again, utilize our new writeJSON, cuz it's not just for handling errors, we can do utils.WriteJSON, pass in our w and then here we http.StatusOK.
>> Melkey: We can use utils.Envelope, and here we can see some of the cool things of that interface or any type. We can just put workout, and here just put workout like so. So it's a nice little flexible function that we have for just handling JSON back to our client. Let's move on to the create workout. So again, I'm just gonna go ahead and copy that logger in the utils print. And right here, right underneath the decoder, we can go ahead and paste this like so. The first logger error could be ERROR: decodingCreateWorkout. Again, this is for us. And here we can say invalid request sent, and this is back to our callers. Let's go ahead and take this, put it down in underneath the created workout. So when we call our handler workout store and the create workout, here the error mesh would be CreateWorkout. And there's a bit of a pattern here. So the error message is followed by, typically, what is the function calling that workout, whatever is more flexible for you. There's this kind of pattern set, but nothing set in stone. I'd always recommend do what's most obvious for you. Here, we can do StatusInternalServerError, cuz if something goes down and I was creating it, which is back to our database layer, that's on us. So here we can just say "Failed to create workout".
>> Melkey: All right, and then here instead of doing the header and the encoder, what we can do is utils.writeJSON(w, http.StatusCreated).
>> Melkey: Like so, can use utils.Envelope. Okay, we can do workout as the createdWorkout. Let's go ahead and finish up the update workout and then the delete workout. So in the handle update, we have, again, those params, so we can just simply go back to handle workout right here. Let's go ahead and just delete all that and declare workoutID, okay, and error is going to be utils.ReadIDParam, pass in the pointer r, check the error.
>> Melkey: All right, I'm gonna just go scroll back up where I already wrote this, so something like this.
>> Melkey: Okay, and then when we fetch the workout, so we can just rename to getWorkoutByID error.
>> Melkey: You can make this StatusInternalServerError, okay? And in the envelope, we can do error and can do internal server error, okay? Now, again, you can feel free to modify those error message to whatever you want. There's lots of flexibility with the utils.WriteJSON utility function. If you wanna be more explicit to your caller, more explicit to yourself, feel free to do so. I'm just taking a bit of a liberty here. I'm just kind of using the internal server error as a catch-all statement. Is it the best user experience? We could probably debate that, but we can explore different ways to make it better down the line. So we have a few more spots we need to handle our errors. So if we go into the decode here, you can see ERROR: decodingUpdateRequest. Instead of internal server error, this is is gonna be StatusBadRequest.
>> Melkey: Okay, I'm gonna say invalid request payload like so. And then in updateWorkout, you can go ahead and say error updating the workout. This would be StatusInternalServerError once again.
>> Melkey: Like so, and then we can remove these header types at the bottom here and again leverage our utils.Envelope, but utils.WriteJSON. We'll say (w, http.StatusOK, utils.Envelope).
>> Melkey: You can put workout, and here you can put the existingWorkout. So we can do the rest for delete as well. I'll leave that up to you because it's repeating the same thing. But this is just a way for us to handle our errors, and specifically our JSON. And I wanna demonstrate that, so we have the go server now fully running. If we go back to our post notes here, so we finished 1.6. There's this new post notes text section 1.8, and this is simply a summary of the co-request, so we don't need to have them repeated, but I think it's just easier. So here I'm gonna copy this post. This is the same one for creating, right? And in here, all I want to demonstrate is how the JSON looks. So if I paste this and I click Enter, you can now see our returnJSON looks much nicer. It's not just this blob at the bottom and you kind of looking like, what does this mean? What is this? I don't really understand this. It's much nicer for us to parse and release. You can see here's a request and here are the values. And actually noticing "id": 0, that is probably because we're not return the id from our entry, so we can modify that, or even remove it entirely. So we can go ahead and do that. So here, if we go to workout-store, and this was in the Create method.
>> Melkey: So here we're returning ID.
>> Melkey: It's a bit odd, I'm not too sure why it's actually empty.
>> Melkey: Returning id, let me just double-check. We'll explore that later. So the worker does, in fact, have the id. I'll debug why id here is 0. I'll make a quick fix for it. It's probably some typo going on, or maybe I'm not having that field correctly in the actual entries. But the main point is that now our JSON is legible and we can use this more accurately in the rest of our code. And we can even do something like a get now. So if you go back into the post_notes and then here. So we just curled and created a workout. If we just do a simple GET, I'm gonna go ahead and copy this and paste it. You can see we actually have the work that's working. Yes, so GET is actually responding with the appropriate ids, but to create isn't so. Gotta figure out why that is. But yeah, now we have much better JSON, much better area handling as well.
>> Melkey: And we're actually going to leverage our logger, the one we declared in the very first kind of submodule, to write error messages for us. So now, if we go back to our workout handler. Let me go all the way back up, this workout handler struck no longer is just going to be accepting the workout store. Now add logger, which can be of type log.logger.
>> Melkey: In our constructor method it's gonna take logger, it's gonna be that argument that we just passed in, log.logger. And then our actual return, we can put logger as logger, like so. So here comes the fun part, we'll be rewriting a lot of code. You can hate me, that's fine, I'll take it. But I want to show how we can really get something working, and then how we can improve on it. All right, so here we're gonna start with the HandleGetWorkupByID. Everything here, from paramsWorkoutID to the workoutID return we can delete. And here, what we're going to do instead is define workoutID err as utils.ReadIDParam, I'm gonna pass it in r. Okay, so we're gonna use the utility function we have here, we can check the error. So if err does not equal to nil, we can go ahead and write our wh, our logger like so, I'm gonna leverage this. We're gonna do Printf, and now this print is gonna be for us. I'm gonna make it a little bit nice. I'm gonna do error, all right? We can do readIDParam, something like this, %v, and I put the error like so. And we can also leverage our new writeJSON function. So we can do utils.writeJSON(w can pass an http status, so StatusBadRequest, okay? And then we can use utils.Envelope like so, and we can just say error and then pass in the key invalid workout id and make sure we return, okay? And so go ahead and copy those two lines, the logger line and the writeJSON line. And now, when we call or check errors, we can simply use that style, that blueprint, that schema, if you will, to handle our errors. So here, error, which can be getWorkoutByID. So that tells us exactly what the error is going to be, okay? Otherwise, instead of StatusBadRequest, http.StatusInternalServerError.
>> Melkey: Okay, and in the envelope after error, we can just say internal server error, because remember, this is gonna be for our client. Our client doesn't necessarily need to know what's happening from our own back end. If our back end is breaking, that's on us to log and figure out. We don't need to send every error message back to the caller, especially users.
>> Melkey: You can, of course, be more explicit and maybe provide them an error code that they can then go to customer support or something like that. But that kind of be outside the scope of what we're doing here, all right? And then we can just simply delete all this stuff from the get workout ID at the bottom. We can, again, utilize our new writeJSON, cuz it's not just for handling errors, we can do utils.WriteJSON, pass in our w and then here we http.StatusOK.
>> Melkey: We can use utils.Envelope, and here we can see some of the cool things of that interface or any type. We can just put workout, and here just put workout like so. So it's a nice little flexible function that we have for just handling JSON back to our client. Let's move on to the create workout. So again, I'm just gonna go ahead and copy that logger in the utils print. And right here, right underneath the decoder, we can go ahead and paste this like so. The first logger error could be ERROR: decodingCreateWorkout. Again, this is for us. And here we can say invalid request sent, and this is back to our callers. Let's go ahead and take this, put it down in underneath the created workout. So when we call our handler workout store and the create workout, here the error mesh would be CreateWorkout. And there's a bit of a pattern here. So the error message is followed by, typically, what is the function calling that workout, whatever is more flexible for you. There's this kind of pattern set, but nothing set in stone. I'd always recommend do what's most obvious for you. Here, we can do StatusInternalServerError, cuz if something goes down and I was creating it, which is back to our database layer, that's on us. So here we can just say "Failed to create workout".
>> Melkey: All right, and then here instead of doing the header and the encoder, what we can do is utils.writeJSON(w, http.StatusCreated).
>> Melkey: Like so, can use utils.Envelope. Okay, we can do workout as the createdWorkout. Let's go ahead and finish up the update workout and then the delete workout. So in the handle update, we have, again, those params, so we can just simply go back to handle workout right here. Let's go ahead and just delete all that and declare workoutID, okay, and error is going to be utils.ReadIDParam, pass in the pointer r, check the error.
>> Melkey: All right, I'm gonna just go scroll back up where I already wrote this, so something like this.
>> Melkey: Okay, and then when we fetch the workout, so we can just rename to getWorkoutByID error.
>> Melkey: You can make this StatusInternalServerError, okay? And in the envelope, we can do error and can do internal server error, okay? Now, again, you can feel free to modify those error message to whatever you want. There's lots of flexibility with the utils.WriteJSON utility function. If you wanna be more explicit to your caller, more explicit to yourself, feel free to do so. I'm just taking a bit of a liberty here. I'm just kind of using the internal server error as a catch-all statement. Is it the best user experience? We could probably debate that, but we can explore different ways to make it better down the line. So we have a few more spots we need to handle our errors. So if we go into the decode here, you can see ERROR: decodingUpdateRequest. Instead of internal server error, this is is gonna be StatusBadRequest.
>> Melkey: Okay, I'm gonna say invalid request payload like so. And then in updateWorkout, you can go ahead and say error updating the workout. This would be StatusInternalServerError once again.
>> Melkey: Like so, and then we can remove these header types at the bottom here and again leverage our utils.Envelope, but utils.WriteJSON. We'll say (w, http.StatusOK, utils.Envelope).
>> Melkey: You can put workout, and here you can put the existingWorkout. So we can do the rest for delete as well. I'll leave that up to you because it's repeating the same thing. But this is just a way for us to handle our errors, and specifically our JSON. And I wanna demonstrate that, so we have the go server now fully running. If we go back to our post notes here, so we finished 1.6. There's this new post notes text section 1.8, and this is simply a summary of the co-request, so we don't need to have them repeated, but I think it's just easier. So here I'm gonna copy this post. This is the same one for creating, right? And in here, all I want to demonstrate is how the JSON looks. So if I paste this and I click Enter, you can now see our returnJSON looks much nicer. It's not just this blob at the bottom and you kind of looking like, what does this mean? What is this? I don't really understand this. It's much nicer for us to parse and release. You can see here's a request and here are the values. And actually noticing "id": 0, that is probably because we're not return the id from our entry, so we can modify that, or even remove it entirely. So we can go ahead and do that. So here, if we go to workout-store, and this was in the Create method.
>> Melkey: So here we're returning ID.
>> Melkey: It's a bit odd, I'm not too sure why it's actually empty.
>> Melkey: Returning id, let me just double-check. We'll explore that later. So the worker does, in fact, have the id. I'll debug why id here is 0. I'll make a quick fix for it. It's probably some typo going on, or maybe I'm not having that field correctly in the actual entries. But the main point is that now our JSON is legible and we can use this more accurately in the rest of our code. And we can even do something like a get now. So if you go back into the post_notes and then here. So we just curled and created a workout. If we just do a simple GET, I'm gonna go ahead and copy this and paste it. You can see we actually have the work that's working. Yes, so GET is actually responding with the appropriate ids, but to create isn't so. Gotta figure out why that is. But yeah, now we have much better JSON, much better area handling as well.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops