API Design in Node.js, v4

Update Handlers Exercise & Solution

Scott Moss

Scott Moss

Superfilter AI
API Design in Node.js, v4

Check out a free preview of the full API Design in Node.js, v4 course

The "Update Handlers Exercise & Solution" Lesson is part of the full, API Design in Node.js, v4 course featured in this preview video. Here's what you'd learn in this lesson:

Students are instructed to create route handlers for the Update and UpdatePoint routes. This lesson includes the solution to the getUpdate and getOneUpdate handlers.


Transcript from the "Update Handlers Exercise & Solution" Lesson

>> So we did the products, we have pretty much all the CRUD methods we need. We tested a few of them, they seem to be working fine. What I want you to do is do the same thing for the update. And then, if you can do that, try to do the same thing for the update point, just go as far as you can.

And then we'll come back, we'll figure them out together. We'll probably move a lot faster on those, since it's mostly the same as these were, just different resources. But I want you to try, and you can reference the ones we just did here as an example of how to do them.

Cuz like I said, it's mostly going to be the same as far as the type of query you're doing inside of the intent. It's just the different resources and different relationships that you might run into. But for the most part, it's the same. Yeah, let's just add the rest, where I left off was, I didn't add the rest of the product stuff to the routes, so I'm just gonna do that.

So I was gonna go to routes, or router in my case here, for the get product by one ID, since we do have a product in a database. I'm just gonna say getOneProduct, like that, product in a database, so I can actually test this. I'm gonna go to my Prisma Studio, I'm gonna grab that ID here for this product, gonna copy it.

Gonna stop my server, start it again. Yeah, I got my server started here, gonna go to Thunder Client. And I'm going to make a GET request to api/product/ that id. And that should allow me to get a product with that id. So let's see if this works. If not, we'll have to fix our stuff.

So I don't need anything here in the body, cuz we're doing a GET. I'm ignoring it anyway, so it doesn't matter. And I should get back this same product down here. And let me just make sure I got my off, I do, we'll send this request, and I do.

I get back the same product there, so our getOneProduct works fine. And just to show you, if I change the 7, if I just get rid of the 7, I guess, and send this, it gets back nothing. Cuz there's not a product with that id, so. Okay, so get one works fine.

And then we need to do one for, I think, delete might have been the last one for this one, let's see. Yeah, it's delete. So we'll go here, and we'll say deleteProduct. So now we have deleteProduct. Going to stop, start. Go back here, I'm gonna do a DELETE.

And the URL is the same, all we did was just change from, GET to DELETE, but the URL is the same. We're still doing api/product/ this id. So I wanna delete the product with this id. So I'm gonna send that, and, voila, we deleted the product with that id, and it's sent back the thing that we deleted.

And just to verify, if I tried to get this again, it should not come back, and it doesn't, cuz we deleted it. Right, okay, that seems to be good, let's move on to the next ones. Thunder Client is messing me up so bad, [LAUGH] I think it's another app.

Okay, so let's do updates, make a new file, update.ts, there we go. If you are lazy like me, you would just copy this whole file from product.ts, put it in update.ts, and just change the things you needed to change. I'm not gonna do that, cuz I don't wanna confuse anyone in this course.

So we'll just keep doing it from scratch. But in reality, that's what I would have did, and now I just wanna change the things. But I'm just gonna keep it, we're just gonna go through these, and you can ask questions along the way. So let's just write the function names out.

First, I'll say, what is this? Yeah, getOneUpdate equals that, okay. I'm just gonna copy that, we got getOneUpdate, we got get, Updates. We have, createUpdate, we have updateUpdate, [LAUGH] that's funny. And then we have deleteUpdate. Cool, all five of those. Import our prisma, From db, and let's do it.

So to getOneUpdate, we need to get the update from prisma.update.findUnique. Well, findUnique won't work right now until we make a new index. So I'll just say findFirst for now. So findFirst, where productId is going to equal to req.body.productId, which is what update should have on the input. But if it doesn't, we need to add that.

So if I go back to the update, if I wanna do an update, Which one am I doing? Wait, hold on. This is just get one. I'm sorry, I don't need a productId, that's for creating, we're good. Okay, so to getOneUpdate, we just need the update's id. So id would be, and I guess now we can do findUnique.

So id would just be req.params.id. Then I'll just say res.json, data: update. And I don't know why I called it update, that's why I got confused cuz of the word update. But it's an update, [LAUGH] I thought I was doing an update. No, it's literally an update. Okay, that's why I got thrown off there, naming is important.

All right, so we got that, and let's do getUpdates. So for getUpdates, we can say updates are await prisma.update.findMany, Where, let's see, we wanna get all the updates for, this one's tough cuz it's like, do you wanna get all the updates for the signed in user, which will be spanned across many different products?

Or do you wanna get all the updates for a certain product? And if so, where did you send that? It's not on the query string, it's not on the URL, is it on the body? So for now, this is what I said, this is where res breaks down, cuz this probably wouldn't make sense in our UI.

I can't think of a place in that app where I would wanna see all the updates spread across all different products. I don't think we even have a screen for that in that design. But maybe we'll just make it anyway, yeah, this is where sometimes you've gotta go back and be like, yeah, we don't need that, this is a wasted route, it's not serving a purpose.

But for now, we'll just get all the updates for the signed in user. But how will that look like, because update, yes.
>> But have you thought about doing a composite route, so you have an update on the context of product? You just kind of keep building on the URI, it's a reason why we wouldn't do that?

>> That's a great question. There's no reason why you wouldn't do that. In fact, that's a very common pattern I think people start to do when they start to deviate from REST. It's like, let's just allow you to add different filters or whatever on the query string, where you could just add on and select this and add that.

Yeah, there's nothing wrong with that. It gets pretty advanced at that point. I mean, you're basically just creating your own query language at that point, which is fine if everyone understands it. And I don't think there's a problem with that. The reason I didn't wanna do it in the course, cuz I think you then have to teach someone a query language.

But yeah, that's actually what you would see in production is what you just described, nine out of ten times it's that. It's like, we're just gonna compose this URL, we're just gonna keep adding to it. And then eventually it might get too big, and someone makes another route, or, yeah, something down that line, but yeah, that's a great point.

>> When I've encountered that in the past, that's usually what I've done. But I agree that it's like if you have too composed of a resource, it can get kinda confusing in the URI. So probably it works maybe two or three max.
>> Yeah, the way that I've seen it is that eventually you gotta send up how many levels deep you wanna go.

It's like, all right, we'll default to two levels, anything past that, you gotta explicitly send up a number of how many levels you wants to go. But then you gotta deal with cyclic relationships, where it's like, a user might own a product, and a product might own an update.

But update might belong to a user, and then you start going deep, but how do you know when to stop? And you gotta detect that cyclic nature. It gets tough with that, but yeah, great point, exactly. That's one technique on how you can break out of the simplicity of REST.

But yeah, so let's say we wanna get all the updates for a user. How do we do that? Because update knows nothing about a user. And a user knows nothing about an update. There's no update on the user. There is no user on an update.
>> It's the product id?

>> Okay, yeah, common ground. Common ground is a product. A product knows about an update. It also knows about a user. So maybe if we find all the products that belong to a user, we can get all the updates for all those products, and then we get all the updates, right?

Cool, let's do that, or you could come in here and make a whole new schema if you wanted to [LAUGH], and go crazy with it. There's so many ways around this problem, but yeah, yes.
>> Would that be like a many-to-many relationship, and you'd have that joining table?

>> Yeah, technically, yeah. If this was SQL, you'd be creating a join table of, this id of this update points to this id of this user. And you'd just have a bunch of those, and then all you have to do is just query that table of like, find all the update IDs that have this user ID associated with it.

And you could do that, in fact, there is a way to do that here manually in Prisma, because they do it for you automatically. But you can manually make your own table, doing it that way. Okay, but yeah, let's do it that way. So before we get updates, we gotta get products.

So we could say products are await. And we already did this in the product stuff, right? We already did that over here where we actually get all the products for the user. But it's not abstracted enough for us to take it, cuz it's responding to requests and stuff.

And then that's when you start getting, well, how about we separate all this stuff out from the whole point of responding and using all of this? And that's the epitome of abstracted Express, if you ever reach a level where you feel like that's necessary. Mostly if you're just a crazy test person that just wants to test everything with dependency injection, you would probably do something like that.

But for the most part, you don't need all that. Anyway, yeah, so products will just be prisma.product.findMany. So we wanna find all the products where belongsToId: req.user.id, like that. And then, we want to include, The updates, True. So there we go. We got the updates on those products, so we got that.

And then what we wanna do is just only send back the updates. So what we can do is we can say, I don't know what that is, we could say res.json(data), and then we could say products.map. Grab the product, or, let's just destructure right here. Get the updates, I guess you gotta reduce this.

Let's do some of that inline, I thought that's gonna be less cleaner on one line, it's not [LAUGH]. So let's get the updates up here. So updates would be products., gotta be there's reduce then. And we can say, Let's do this. We'll get allUpdates here. We'll get product here.

And we can just basically take the product.updates and merge it into the new one. So we'll just say return a new array of all the updates. And all the product.updates, like that. So this will give us the updates, and then we get to say updates. There is better ways to do this.

If you have to do this after coming out of a database, you're doing it wrong. This is a telltale sign of, you don't have the appropriate schema set up to get the data that you need. And this is the problem with things like REST, it's like you're just trying to make it so generic that you end up sacrificing optimal querying, because you need the routes to be generic enough.

But if you were doing something a little more specific, not REST, then you could tailor this query and the schema to reflect what you need. There's tradeoffs. Cuz this is fine, but I don't know, if this was 10,000, and this was 20,000, right? And then, if you were just hitting your server over and over again.

Now you have 40,000 things stored in memory per request. You just can't put that load on your servers like that constantly. So you typically wanna avoid putting things in memory. And you wanna find a way for the database to do this for you. So how do I tell the database to do this so it happens on the database side and not my side, right?

So that's why I said, typically if you have to do something like this, you probably wanna go back and update your schemas and stuff. But I wanted you to see that, cuz I want you to know that you're not gonna just write your schemas one time, come in, write these queries and be done.

You're gonna come and write these queries, and you're like, I gotta go back. I gotta go back and change my schemas because this isn't right, or, like, wow, I need a new index. I gotta go back and change my schemas. It's not so perfect every time.

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