Check out a free preview of the full Build an AI-Powered Fullstack Next.js App, v3 course

The "Creating a New Entry" Lesson is part of the full, Build an AI-Powered Fullstack Next.js App, v3 course featured in this preview video. Here's what you'd learn in this lesson:

Scott creates the route for the new journal entries. A click handler is used to navigate to the new route by appending the ID of the new entry to the end of the URL. The journal-page branch can be used as a starting point for this lesson.


Transcript from the "Creating a New Entry" Lesson

>> Now, we're gonna make the journal card actually do something. So let's do that. There we go. So what we wanna do here is on click, we want to create a new journal entry, and then navigate to the editor page in which you can edit that journal entry.

And all that needs to happen without asking the user for anything. So we're not gonna show model what's the name of your journal? Well, our journals don't have names, and I know that cuz we wrote the scheme, and they literally don't have names. So we don't need any information and they don't have anything unique about them other than the IDs.

So we could give them the same content as well as a starting point, which is exactly what we're going to do. So it's just gonna be like a default journal entry that says, New Entry, and from ther, you can go and start editing it. So let's do that.

First things first is we need to set up an onClick inside of our NewEntryCard. How do we set up an onClick on a server component? Anybody know?
>> You use client thing how to do that.
>> You can, you have to make it a client component. So now we've got to turn it into a client component.

So if you don't know what this is, you actually do know what this is. It's every React component you've ever made in your entire life, that's what this is. Every component we've made up until this point was different, it was a server component. It tricked you, it made you think you were writing React components where you were not, you're writing React server components.

This is exactly what you thought it was, it's a regular React component. Now, we can use hooks, we can do anything that you could have done in any React component. Whereas in something like EntryCard, you cannot write a hook in here, you cannot do any of that stuff, it's just not possible.

The other thing is, because this is a client component, you cannot make this async. You cannot do that, that is not gonna work. So everything has its own purpose. This is gonna be a client component because we need interactivity. Interactivity requires JavaScript, JavaScript means it's gonna run on client.

So let's do that. So onClick, we want to be able to do something. So we'll say, onClick, We're going to, let's just make a function up here that says handleOnClick. Cool, that was passed out here. So far so good, so onClick, we want to be able to make an API call and create a new journal entry.

So it's a few things we have to do. One, we need a API to even hit to make a new entry, that's one thing. Two, we need a function that calls that API, right? So we got to make both of those. So let's start with the actual API, and then we'll come back and we'll make the function.

And then we'll bring it in here and we'll tie it all together. So to make an API, we just gotta go into app/api, which we already have, and just make a new folder. And we're gonna call this the journal folder, so it'll be /api/journal. And inside of here, if you make a route.ts, that's a very specific name, just like you have to make a page.

In the API, you have to make a route file. This is saying, I now have a route for /api/journal. And inside this route, I want someone to be able to create something. Usually, the ACP for creating something is a post. So all I have to do here is export a function called POST, put a capital letters.

And this is going to allow me to, This is basically enabling /api/journal POST, like a post request to that route. So for this route, I'm gonna say, what do I need to create a journal? Let's look at the schema, I need a userId, I need some content. I don't need an analysis, that's optional.

So we're not gonna actually use it for content, and it's not unique, so we can just make some content out, but we do need the userId. So we need to get the user's information. Luckily for us, we already have a utility function for that, and it's called getUserByClerkID.

This works in the API's functions as well because we didn't explicitly say it, but this all file is only being used on the server side. This is not being used on a client. If you try to use this on the client, it will, for sure, 100% not work because these are node modules.

How do I know that? Always I know principle doesn't work on a client. I know this auth function was meant to work on the server from clerk. There is a package you can install called server only to ensure your stuff only runs on the server. I'm not gonna do that, you just import it and it basically will error out if you try to bring anything from this file into the client.

It'll error out, not to be confused with doing something like this, which is called use server. That's only useful for creating server actions, which I don't think we're gonna be doing because I don't feel the need to. There's a lot going on there. [LAUGH] I know it's very confusing, but that's just how it is.

Anyway, we got the user, and now we want to be able to create an entry. So let's do that. So we'll say, entry = await, let's bring in our prisma.journalEntry.create, like so. And then we wanna create with this data, this data is gonna be userId, it's gonna be

You do that. The other way you can do this is you can say, user, pass an object, say, connect, pass another object, id, and you can say, You can do that too. This is like you specifically connecting this user to this entry. But why am I gonna do all that when I can do this?

So I'm just gonna do that. So we got the userId, it's freaking out, cuz it's like, hey, you forgot something, you're missing something, you're missing content. So I'm gonna add content, and every new entry is just gonna have the same content. Write about your day, something like that.

This needs a comma. Cool, so we got that, satisfied all the needs there. We have our entry, and now we can just send this back with a return NextResponse that you got to import from NextServer, dot json. And I typically always send back an object with a data property with the data that I'm sending back.

That's just how I've always done it, you can do whatever you want. I prefer to send back an object with the same key on it so I could have some predictable code on the frontend that's looking for the same key. If I just send back to entry by default, that object's gonna have different keys on it, another route's gonna have different keys on it.

It's just better just to be like, I know that's gonna be on the data property, let me just grab it, so. Any questions on that? And also, because the middleware is protecting all of our routes, we can also assume that you can't access this unless you're logged in as well because the middleware won't let you.

Cool, all right, so we got that, now what we wanna do is make a function that will call this API. So I'm gonna go to my utils, make a new file, I'm gonna call it api.ts. I'm gonna make a new function here called createNewEntry, like so. And we're gonna use fetch here, which is a slightly modified version of fetch that Next.js modify.

We don't need to worry about those modifications. But one big difference is that fetch now uses the request standard object that's built into the web, so we'll be using that. So I was gonna make a utility function that creates a URL for us. Remember, fetch, you can't pass in the relative URL.

So you have to pass in the full path URL, even on localhost. So I could just hardcode that, I could just say, HTTP localhost slash whatever. But what about when I'm not on localhost, if it's hardcoded? So I want that to be dynamic. So I'm gonna make a function that dynamically does that.

So I'll just say const createURL, and then that's just gonna take in a URL path that you want. And then it's just going to return, let's just say, window.location.origin + path. So cuz our API is always gonna be on whatever website we're on slash API slash whatever. So window.location.origin, it's always gonna give you the full URL that you're currently on.

And no slashes after, right? So if I go to another website here, it's just gonna give me Cool, okay, so we got that. Now, we can go back to this function, createNewEntry, and I guess I got that backwards. There we go, what I was doing there. Now, we can say, const res = await, make this async.

This will be fetch. And then inside of fetch, I'm gonna do a new request, like this, request takes in a URL. So the URL that I want is createURL, like so, with /api/journal. That's the route we just made. The second argument to request is an object for configuration.

So method is gonna be POST, and then body, this is going to be, we don't actually have to send anything up here, because in our route, we already just hardcode the defier. We don't even need to send a body here, but if you were to send a body, just make sure you JSON.stringify first.

So stringify, Stringify. Make sure you stringify whatever thing you were gonna send up here. We don't really need it, so I'm just gonna do that, and then I can say, if res.ok, then return res. Or actually, I'll just say, const data = await res.json, that's a promise, and then I'll return the data, .data cuz I always send back data.

And if it's not okay, we're not gonna worry about errors right now, we'll do error handling later. I could just do all my error handling at once, because then I found out if I just piecemeal the error handling, it'd be different for every single function that I do.

Where it's just creating a unified error handling system with appropriate messages and status codes, you gotta think about it like that. Not just like, this time, we'll handle this function, and this time, we'll handle this function, it'll just be different everywhere you go. You gotta take it as a system approach.

So I like to think about that entirely and later. So we're good for now. All right, so we'll return that data, we have create new entry. This should work, hopefully. And let's go see what happens. Cool, so let's head over to our NewEntryCard. Onclick we will say, async, let's await createNewEntry, like this.

And we don't need to get the return value here. The reason we don't need the return value is because we can't do anything with it. There's literally nothing we can do. This component doesn't render all the entries on the page, so having access to that return entry here means nothing.

You might say, well, what if we just had a function in here that we can call that says, onClick, and then we pass in the value there? We can't do that because this NewEntryCard component is being rendered in a server component here. And you can't pass functions as properties to server components and client components because they can't be serialized over the Internet.

So that won't work, so there really is no point of trying to get the return value of the API call. And you'll see we'll run into a UI problem, where it's like the screen won't update until you hit refresh. And we'll talk about how we can fix that.

But that's because we're fetching the data on the server in one place, and then we're updating the data on the client in another place. Whereas before, you probably use something like React, what is it? React Query or SWR, where you're mutating and fetching all in the same place on the frontend.

So you do wanna get the return value and update the cache and do all this stuff. These two things don't share the same cache. We're literally getting data on the server and then we're doing stuff on the frontend. So it doesn't work that way, they don't know about each other.

There's no JavaScript on the frontend for this component at all. It's just HTML, whereas this component has tons of JavaScript on the frontend. So, Cool, okay, so we have that. And then after this, what we wanna do is we wanna do a redirect, but we don't wanna use redirects.

We want to use router, so we're bringing in useRouter from NextNavigation. Inside of here, we'll say router = useRouter, like so. After we create this, we'll just say router.push to /journal/ and we need the ID of the journal. So I guess in this case, we will get the data here only because we need to route to it, but not because we're trying to show it on the page.

So like that. So router.push, the router is just a, I really like what Next.js do here, they're just abstracting around. They're just leveraging the web, they're like, there's already a good router in the web, let's just make a wrapper around it, useRouter. If you've ever used the HTML5 push state router, that's what this is.

It's just a stack, it's just an array of routes, and you're pushing to it, which means go to it. And when you go back, you pop, it's very simple. So this is the equivalent of saying, go here, right? So you could replace it if you wanted to. And basically, it will go to it, and then it will replace the route that you're on now with this new entry.

So when you go back, you won't go back here, you'll go back to one that's before this. So you're not creating a new instance, a new space in the stack, you're replacing the space in the stack, but we're not gonna do that. That's useful for something else. If you had a modal attached to a route, and when someone hits back, you don't want them to go back to the modal.

You want them to go back to the page before the modal so you would do a replace, not a push. It's a lot to think about, well, let's see what we broke. [LAUGH] All right, all right, okay, so well, if this works, for sure, what's gonna happen is we're gonna get a 404 cuz we don't have that route set up for Journal ID.

So that's what we should see. So we click this, it worked, it did exactly what I was supposed to do. So you can see you got the ID here, and it went there, and it's 404. And just to verify like we always do, we can go to npx prisma studio, load that thing up, and click this tab here.

And you can see, we do have a journal entry now. And it has our default content right about your day. Pretty cool, any questions on that? Yes.
>> Could you show the createNewEntry thing in the utils API?
>> Absolutely, there you go. So basically, what this is doing, it's weird looking cuz I'm passing a function in another function, in another function.

So it's weird, I guess I could have not done it that way. But yeah, so this creates the URL, this creates the request, and this creates the fetch.
>> We don't need any credentials for the fetch because this is all happening on the server, right?
>> This is actually happening on the client.

And there are credentials being passed or cookies. Cookies are passed automatically, so we don't have to manually pass them. Yeah, but I think Kurt also includes a JSON web token in the local stores that you can use yourself if you want to do that, but the cookies are being passed automatically.

And then what's happening is the cookies are being passed automatically, which then, because the route that we're hitting is not protected, or it is protected, right here, this middleware is checking that cookie. You'd be like, yeah, they're good to go. And then our function runs, so then our function comes here.

This function then runs. And then there's another cookie check here, so it text a cookie, then it goes to clerk, comes back with a user, and then our code runs. Yeah, there's a lot going on there.

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