Complete Go for Professional Developers

Handlers for Getting & Updating Workouts

Melkey
Twitch
Complete Go for Professional Developers

Lesson Description

The "Handlers for Getting & Updating Workouts" 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 codes the API handlers to get workouts by ID and update workouts. Additional server-side data validation is added to the update function since a request may contain null values for some fields. The current code can be found on the 1.6 commit in the repo.

Preview
Close

Transcript from the "Handlers for Getting & Updating Workouts" Lesson

>> Melkey: All right, very nice. So now we have to get workout and the update workout methods only in our data store. Let's go all the way up to the workout handler and fix these up, so that we can actually call them, query them and test them out. Let's go into the workout handler above. So just a quick reminder we already have HandleCreateWorkout function, working. So now we're gonna work in the HandleGetWorkoutByID. This can be fairly quick, not quick, but simple integration here. So at the bottom here, after we check the last error for the workoutID, what we can do is workout error is gonna be our workout handler, the workoutStore and the GetWorkoutByID function. We can pass in the workoutID.
>> Melkey: As per usual, let's check the error, so if error does not equal to nil, we're gonna do a format print line error.
>> Melkey: Bear with me, it's not permanent. We will fix this in a couple of minutes. We'll do the http.Error function, pass in the response writer like so. We can say failed to read the workout or fail to retrieve the workout. And here we can do http.StatusNotFound.
>> Melkey: So we can actually just say method to read the workout because they fail to fetch the work, I think it's a bit more explicit to what's happening, okay? Let's not forget the return at the end there, okay? And at this point, we are assuming we're able to retrieve the workout correctly. So then what we can do is w.Header.Set, Content=Type
>> Melkey: Application/json. We can do w.WriteHeader over here, when I give it the status of (http.StatusOK), like so. And right below, we can do json.NewEncoder, pass in the response writer I'm gonna encode the workout like so. Let's go ahead and just create the update method now as well. At the bottom, let's get some room for us here, like so. Create func wh for our pointer to our WorkoutHandler. I'm gonna call HandleUpdateWorkoutByID, like so. It's gonna take our w http.ResponseWriter and the body of http.Request or the pointer, excuse me. All right, and here we have to parse that ID as well. Since we already have that logic written and this will be its own function, we can do is simply just go up here and let's go ahead and link this much code. Instead of rewriting it from scratch, go ahead and copy and paste this from the GetWorkoutByID function, and just paste it in the first body, in the first line for the HandleWorkoutByID. Okay, I'm gonna use this workoutID to first fetch and get the workout. So here we can do existingWorkout error is going to be our workoutHandler, the workoutStore, GetWorkoutByID, and he passed in the workoutID that we just extracted from our query using chi. Check the error, so if our error does not equal to nil, down here, we can do http.Error. W failed to fetch workout http.Status you do InternalServerError like so, and then we can return. And here we wanna check if existing workout is nil cuz we can do a get request and see if it's empty or not. So here if existing workout is in fact nil after the error check, you just do http.NotFound for right now, w r and then return. Okay, so at this point, we can assume, we are able to find existing workout.
>> Melkey: Okay, now what we're going to do right now is create a new struct which is going to act as the request from our Client struck. So you remember, when we create a new workout, we are basically decoding that JSON into the struct. We need to do the same for update requests, okay? But the thing is, we don't wanna blindly trust our client. I'm sure there's gonna be some client side validation, and that's important. But we can also do some server side validation as well. Is it overkill? I don't think so, I think it's probably good to trust but validate at the same time. And so with that kind of thinking, we're gonna create this struck, not anonymous. It's gonna live within the scope of this function, and you can make it live outside the scope. You can globally declare it in the package, that's fine. But here, this is going to act as that preliminary location for us to decode that JSON. We can then validate that are the fields correct, is it adhering to the password length that we want etc, etc. And then we can replace the values in our actual existing workout with those values from our client and then call the update workout method. So I'll show you how that looks, you can create a new variable called updateWorkoutRequest. This is gonna be struck, and this struck is basically going to have the same fields our workout does, with a slight difference. We can do title and this time, instead of being a string, this is gonna be a pointer to a string like so. Same json tags, title, all right, and we'll follow up with the rest of them. Description is going to be also a pointer to a string. We can put description like so, okay, we can have DurationMinutes. This is gonna be a pointer to an int.
>> Melkey: json duration_minutes. We're gonna have calories_burned,
>> Melkey: Oops.
>> Melkey: Like so, this would be pointer to int.
>> Melkey: Okay, and the last one are entries, right? Because we know entries is a slice, the native default type for slice is already nil. So we can do store.WorkoutEntry. Okay, we can do our json tag as just entries, like so. With that now declared, we can use the same json logic we had earlier of the NewDecoder. Let's grab it from our r.Body, and what we're going to decode is in the updateWorkoutRequest, like so. Let's check that error. So if our error does not equal to nil, we can just do http.Error pass in the response writer. And then we can just simply write the actual error message that we are extracting from the decoder like so. We can do http something like StatusBadRequest.
>> Melkey: And make sure we return.
>> Melkey: So now that we have this updateWorkerRequest, and let's say we successfully decoded it, the question is, why are these pointers? And the reason for that is this allows us to now check if those values are truly nil or if they intend to be empty. Let's say someone goes back and updates the workout, and they said, you know what, I don't want a description on this field, and they leave it empty, like an empty string. Which we found out is the default or zero value of strings for Go. Do we want to treat that as if it's a non existing value and not update that work with entry? Or do we think that our user is actually trying to update this form a description to be empty. So to do that successfully, we can check with a nil pointer. We can check if that value is actually nil or not from our client, because if they don't send anything from our client, there's nothing gonna be there, and a nil is not the same thing as an empty string. So this updateWorkoutRequest variable that we have, we could do something a little bit repetitive, but I think it's worth it. We can check if updateWorkoutRequest.Title, and we can do this for every field does not equal to nil, right? What we can then do is existingWorkout.Title, reminder, existingWorkout is the worker we treat just a few lines above. We could set this equal to the def reference value of updateWorkerRequest.Title, like so. Cool, so just a little bit more service-side validation on what we're getting and expecting from our client calls.
>> Melkey: Okay, and we can do this for all those fields, so you can go ahead and copy and paste this for description, duration, minutes, calories burned, and entries. You can do UpdateWorkerRequest instead of .Title, we can do description. If you are copying, make sure you are careful with fixing the field names.
>> Melkey: So I got DurationMinutes, next I'm gonna do CaloriesBurned.
>> Melkey: All right.
>> Melkey: And the last one is Entries.
>> Melkey: Yeah.
>> Speaker 2: Could you explain why we don't need the pointer on the Entries again?
>> Melkey: Yeah, absolutely, so string and ints, the zero value for those is either an empty string or a zero, right, with a slice, the zero or different value is nil. So the reason we're even using pointers like on relative line 29 up for title, description, duration, is because the zero value for a pointer is nil. And so that gives us that ability to check, does this actually exist or does it not exist? And which goes back to the example, maybe someone actually wants to send zero, right? If they do, okay, cool, we can process that but if we don't have a way to fully check the integrity of the request that we may accidentally take a zero value by Go and replace it to be something that it really shouldn't be. So this nil check gives us more of that, not authenticity, but more of that confidence to make sure that we are processing the right request from our caller. Here, we can just do error. We can do workout handler is workoutStore. We can click UpdateWorkout. You can pass in that new, fairly new, but we can say improved existing workout. You can check if our error does not equal to nil. We can just print the error for ourselves. We can just do format.Println and say, update workout error to be more explicit in case some people do get errors here. So we're trying this out. We can do error w and we can also say, failed to update the workout. We can put http.InternalServerError, we can return. And the last little bit is running back to our client. So w.Header we can set Content-Type.
>> Melkey: Application/json, like so, we can do right our header, so w.Write.
>> Melkey: There you go, right. Here we can do http.StatusOK. And then we can just say json.NewEncoder, pass in our response writer, and then we can encode the existing workout like so. So we just wrapped up writing our update handler, and for that, we just created the functions in our Data Store for creating, reading and updating the workouts. So now we're gonna test these out. There's just one thing we have to do, which is to route one of the new functions that update handler into our routes. So we'll go here, so right underneath the workouts, we can do an r.Put, okay? And here we'll put passed in/workouts, and then the slug, which is just the curly brackets, id. And here it will be the function name. So we app.WorkoutHandler and then HandleUpdateWorkoutByID. Again, we're not calling these functions cuz functions are pass as first class citizens in Go. So we could just pass them like so. So with all that, we can finally validate if those functions we created all work accordingly. So go to your running go server, you can go ahead and nuke it and run it back up. Awesome, if you have any errors, I'll show up right now. Any compiler errors or not compilers but any errors from connected to database, things like that. Those will show up right now. All right, so I'm gonna make sure I have my Docker compose running. I have PSQL still running, so that's allows you to check what's going on my database. And I have this last terminal which I can use to curl. And again, you can use whatever you want. But if you are following along, you can go into that post_notes section here, and the next sub section should be 1.6. Now this 1.6 request body, it's going to make the put function call to our address, http.localhost8082, to workouts to the one workout we have in our database right now. And here we're gonna be updating it, so you can see we have tile description, duration, calories, burn entries. And one thing to note, we're actually adding a third entry here, the plank, right? So we had previous squats, and I think it was push ups. Now we're modifying this to dead lifts, Pull-Ups, and adding a third ring called plank. And if you want to validate this, we can go again into select all from workout_entries in PSQL and see that the two entries we have for our workout are just squats and bench. So ideally, once we call this function, we should see a bunch of this stuff be updated. If not, we're in trouble with the debug. So go ahead and copy all this down and in your empty terminal or again, whatever UI you want to run, paste it, you click Enter. You should now see this very, very appealing looking JSON square blob thing.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now