A second version of this course released using Next 13+, but this course is great if you want to see another example fullstack project. We now recommend you take the Build a Fullstack App with Next.js, v2 course.

Check out a free preview of the full Build a Fullstack Music App with Next.js course:
The "Build Dynamic Playlist Pages" Lesson is part of the full, Build a Fullstack Music App with Next.js course featured in this preview video. Here's what you'd learn in this lesson:

Scott live codes a dynamic route for playlist pages that will render and authenticate a page for each user's playlists. The server request will also get the songs and artists in each playlist. Student questions regarding if the include keyword is similar to a join and if the playlist request returns everything about songs unless specified are also covered in this segment.

Get Unlimited Access Now

Transcript from the "Build Dynamic Playlist Pages" Lesson

>> Now, we're gonna make these playlist pages. So in order to do that, first we need to make a route for the playlist pages. And luckily for us, Next.js makes that easy. But the route, because we have multiple playlist pages, we're not gonna make one page, we need a dynamic page.

[00:00:16] Like a route with a parameter in it, where it'd be like /playlist/the id of the playlist. We can do that. So the way we do that in Next.js is we make a folder. So on the page's folder, we'll make a new folder called playlist, so that'd be a path.

[00:00:32] So now, it'd be /playlist, and then inside of there, we're gonna make one file of a route that has an index in it. The way you make a dynamic query parameter for Next.js is you use these square brackets and you can put whatever name you want. In this case, I'm gonna call it [id], and I'm gonna say .tsx.

[00:00:54] So what this is saying is I wanna create a new route for a /playlist/id. And I could put whatever I want in here. I don't have to call it id, I can call it playlist id, I can call it whatever, it doesn't matter. But I'm gonna call it id because that's what it is.

[00:01:11] So now, we have a route called /playlist/id. And I'm just going to just make a function right now, a component, that just shows up, so we can actually route to it so you can see what I'm talking about. So Playlist = a component, that's gonna render a div that says playlist, export default Playlist.

[00:01:37] So now, when I go to /Playlist/subid, it should render this page. So let's try that out. So I'm gonna go to /Playlist/4, and obviously, my server needs to be started, so let's do that. And you can see we're on the playlist page, Playlist. I can go to any /playlist whatever, and it'll go there.

[00:02:09] Cuz it's a dynamic variable. Okay, so let's build up the playlist page, and then we'll go back to our sidebar and make these linked to their appropriate /playlist/whatever, the id of that playlist is, and then we'll have playlist with songs. So if we look at our data, what does a playlist page have?

[00:02:32] Well, it has all the information in the playlist, which has the name here, how many songs it has, how much time all the songs are combined. We could actually run that analysis. I have the duration of every song in seconds saved in database, I think. I can't remember if it's seconds or minutes, but we could do the math.

[00:02:50] A description, playlist don't have description, so maybe we'll skip that one. We can use a placeholder image for that. And then it has the song list, which all playlists does have songs. So yeah, we should be able to make this look pretty close to this, so let's do that.

[00:03:11] What we'll do now is start working on the layout of it. And luckily for us, we have this gradient layout that looks exactly like this. So we could just start with that. And then we can pull down a playlist and we can kinda go from there. So we gotta kinda think about how do we wanna get the playlist.

[00:03:29] We know that we can use client side data using a hook to get a playlist. In fact, we do have the usePlaylist hook here, although this gets a list of playlists and not one playlist. But we also learned on the index page that we can do server side fetching for data that won't change once the user's looking at it.

[00:03:50] So I'll ask you all, what do you think is the appropriate way to get the data for this playlist page? Should we fetch it on the client after the page is loaded up? Or should we fetch it on a server so when it does load up, it's already there?

[00:04:04] Number two? Okay, anybody else got any opinions on it? No, okay, there's no wrong answers here. They both work, it's just what experience do you wanna deliver to your users. I'm definitely gonna go with number two. I'm just gonna have everything there when it loads up every single time.

[00:04:27] And then if you had the ability to add songs to the playlist while you were still looking at this page then you can use client side for that. So you can actually do both if you want to, right? You can render it on the server initially, and then use client side data fetching to update it while you're looking at it.

[00:04:43] I don't know, let's say someone's on their phone on Spotify, and they add them to the playlist and you wanna see a pop up on the desktop immediately. I mean, obviously, that'd be like a WebSocket or service event. But still, you can still do client side things even though you're rendered initially on the server.

[00:04:57] And that's the whole point, is initially render on the server to get that first quick render, which should be quicker than a client side render cuz it's not parsing JavaScript. But once you're on the client, only use client side stuff, because that's actually quicker than going back to the server.

[00:05:11] So you get the best of both worlds. And that's the power of Next.js. All right, so in order to do that, let's go back to our sidebar where we're going to fix our routes. For the playlist right now, it's just linking to home. Instead, we want to link to the playlist.

[00:05:31] The way we can do that is we can pass an object to this href and this NextLink where we're looping over the playlist on the sidebar. So if you wanna meet me there where we're doing playlist.map in the sidebar, there's href for this NextLink component. And we could pass an object instead of just a string, and the first part would be the path name.

[00:05:49] And the path name is the name of the path. So in this case, the path would be /playlist/id. I think you have to pass it in exactly the way that you see it, like that. I'm pretty sure it has to be like that, literally the same way. So we'll do that.

[00:06:13] And then the next one is gonna be query, so what are the values for that? So for the query object, we have an id, that's gonna be playlist.id So now, when someone clicks on a playlist, it's going to go to this route and it's gonna substitute this id placeholder with this id value.

[00:06:36] So we go back. And if you hover over these, you can see that it indeed changes the playlist id, when I hover over these, that seems to be working. At the bottom left, you can see the id is changing. So we should we get there, so we got that set up.

[00:06:55] And now, that we have that, when we go server side, On the playlist page, we can access that query and get the playlist id from the query string. And then use Prisma to make a database query to get the actual playlist and get the songs and get the artist and get everything.

[00:07:16] All we really need is the id, so that's what we'll do. So if we head over to the pages/playlist [id].tsx, we'll export const, getServersideProps, Like this, and we're gonna get the query. You're gonna destructure the query, property of the context object that comes in here. We're also gonna import prisma from there.

[00:07:52] And we gotta think about the query here, cuz we're not just gonna get the playlist, we have to get the songs for the playlist. And then I'm pretty sure we have to get the artist name for the playlist, too. Because on that table, I think it shows what is the name of that artist.

[00:08:10] So it's gonna be a pretty decent query for Prisma, something a little bigger than we've done before. So we could do that. The other thing we can do is, like I said, we could also validate the token here if we wanted to. We don't need to, but we could totally do it.

[00:08:29] But we will at least need to get the token and get the id from that token, so we're making sure we're getting the right playlist. Because how do we know that the playlist that we're getting belongs to the current signed in and user, right? So we need to be able to do that.

[00:08:45] So what we'll do is we'll also get the request here as well, so we'll get the request. And then we need to find some way of getting the ID from the cookie. So we need to be able to validate that. So what I'm gonna do is I'm actually gonna head back over to the auth file and we'll make another helper function here that will basically just validate.

[00:09:05] It'll just do this right here, it'll literally just do this. It'll just verify a token and give us back an id, that's all it's gotta do. We could actually literally just do it in the homepage, or I'm sorry, in the playlist page here and the getServerSide props. I just feel like it's gross if we do it there.

[00:09:20] I don't want to do it there. So we're gonna do it in auth. I'm just gonna make another function down here. I'm just gonna call it export const validateToken, and it just takes in a token. And all it does is it will get a user back from jwt.verify, and verify takes a token, and then the passphrase was for me is Hello.

[00:09:50] And it's just going to return it, that's it. That's that's all it does, it doesn't do anything special. Literally just called verify and returns that, so it's a one liner. Now that we have that, we can go back to our playlistID page, and we could use that, so I can say const {id} = validateToken.

[00:10:20] And for the actual token, I can pass in req.cookies., the name of our cookie, which was TRAX_ACCESS_TOKEN, like that, that'll give me an id. And now that I have the id, I know how to ask Prisma for what playlists based off this id. And I know this id is valid because we have a bunch of authentication stuff all over.

[00:10:49] So now I can just say, let me get the playlist here. In fact, it's gonna return an array because we're not creating the playlist by unique fields. Because, again, I don't think a playlist even has anything unique about it other than id. I have to go look at Playlist.

[00:11:07] Yeah, it only has an id that's unique. So because of that we can't do a findUnique, we had to do a findMany. Because obviously, we're gonna get back an array of playlists, but we only want one. But like I said, it only has one unique field, that's id, and we're trying to query by user id.

[00:11:25] We're trying to query by all these other things. We can't do a findUnique. So we're gonna have to do a findMany and just get the first one that matches it, which should only be one. So yeah, again, Playlist doesn't have anything unique about it. So we have to do a findMany, even though we're only finding one.

[00:11:37] So don't get confused by that. It's only because there's nothing unique about Playlist other than id, which we could just search for the id there, but we wanna also make sure that this playlists belongs to the user. So yeah, I guess, the playlist id is enough for that, but just to make sure, let's say I wanted to search by the playlist id and this user id just to double check that.

[00:12:02] Cuz what's stopping someone from, I know your playlists id, let me just ask for it on the API. And now I got your playlist. But if I also say, it has to be the right id and you have to be signed in with this id, okay, now I can't do that.

[00:12:14] So this is helping you with multi-tenancy, where you have multiple people on the same database with different data. How do you prevent them from accessing each other's data? Most of your queries also have to include the user's id, that's how you do it. So it's not enough just to say, well, obviously, this play this with this id belongs to this user, it's a unique id.

[00:12:34] Yeah, but how do you know that that's the user asking for it? So that's why you have to also include the user id when it comes to multi tenancy. So we'll do that here. So now we'll say, like I said, its gonna return an array, but I really only care about the first thing in it and only thing that'll be in it.

[00:12:49] And this is gonna be an async function. And what we wanna do here is we want to await prisma.playlist., like I said, findMany. And I guess we could also do find first, which is literally the same thing as findMany, it just returns the first one, so it doesn't really matter.

[00:13:11] I guess it just saves you a destructuring. So we'll say that. And then we're gonna say where id is gonna be query.id, that'll be the playlist id, that's gonna be in the query because that's the parameter for this route, it's gonna come from here. And I know it's called id because that's what I call it in a file name.

[00:13:30] If I call this something else in between these brackets, that's what it will be called here, too. If I call this abc, it'd be query.abc. That's where that name comes from. So this is the playlist id. And then I also wanna say it too needs to have the same userId as that, this right here, as the userId.

[00:13:54] So that's the first part. And this is where it gets pretty cool. I can use this include field. Include is basically, hey, Playlist has a relation, right? Playlist has a relation for Songs. I wanna include the songs on the playlist query, cuz we're about to populate the Songs table on this playlists page.

[00:14:11] So instead of making another database query to get the songs after I get the playlist, I can do the all in one by saying, hey, I also want to include a bunch of things. The first thing I want to include is, actually, let's look at the designs here.

[00:14:25] So yeah, I guess, really only we wanna include here's the songs, and then the artist of those songs, cuz it looks like a list of artists underneath them. We don't have albums. We don't have that, so yeah, we need to do that. And yeah, let's do it. So we'll go back, we'll say include songs.

[00:14:46] You see to get that typo hit there, which is really cool. And then for songs, we also want to include, because songs have artists, we wanna do another nested include here, is that we wanna include the artist for those songs, like that. And then for the artists, the only thing that we want to select, we can use the select keyword here, is gonna be the name, and we'll say true, and the id, then we'll say true.

[00:15:20] So just to go through what this is saying, this is saying I want to find playlists where the playlist id is the id that's on the URL. And it has a user id that's the same thing as the signed in user. And then I want you to include the songs, and then from the songs, I want you to include the artist for those songs.

[00:15:45] And then for the artists, I only want you to select the name and id field. Yeah, question?
>> So is an include a join?
>> Include is a join, yeah. If you use SQL, it's a join. If you use Mongo, it's a populate
>> So the ORM just uses the verb include instead just to, okay.

>> Use the word include, you can use select, as well, in place of include, but you can't use them on the same level. I couldn't use select right next to this include. You can't use them both together. You can see it'll freak out, like no, you can't do that, you only use either or on the same level.

[00:16:21] All right, so, yes?
>> The way you have that currently structured, is it gonna include everything about songs and the artist, just name and id?
>> Correct, yep, if you aren't specific about what you want to select, it will select everything, yeah. So one bug we'll have here is that the query string is always gonna be a value type of string where all ids are numbers.

[00:16:44] So we got to convert this to a number, I just put the plus sign in front of it and that'll convert it to a number. Otherwise this will be a string with the playlist id in it. And it will never find it in the database cuz all ids are numbers in the database.

[00:16:57] So just convert that to string, you will run into that. You ran into that, okay, all right, [LAUGH] yeah, that happens a lot. Okay, so now we have our playlists and if all goes well, we should be able to return an object with some props, that's our playlist, like that.

[00:17:30] And then, lastly, is we can use our playlists that gets injected up here, like so. So now we have playlists, and I'm just going to put the playlists.name here. Okay, so let's go back to our page. Obviously, this is just gonna freak out because it's got an invalid id, cuz that's not a real id, so let's go back here.

[00:18:07] And if I click on playlist, whatever that is, you can see I'm on Playlist #1, Playlist #10, Playlist #2, Playlist #4. So so far it's working. Don't get confused with the playlist ids and the number on the playlist name, the seed script didn't coordinate that, so don't let that confuse you.

[00:18:29] The name and id have no association, they just both use numbers.