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

The "Creating a New User" 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 codes the workflow for creating a new user. When Clerk creates a new user, they are redirected to new-user route where the user data is sent to Prisma. Once Prisma is updated, the user is redirected to the journal page. The db branch can be used as a starting point for this lesson.


Transcript from the "Creating a New User" Lesson

>> Scott Moss: The next thing we wanna do is basically we want to, we have the logic and the middleware where if you try to go anywhere, it's gonna force you to log in. But because of that web hook issue that I told you about syncing our state with our users, we need to find a way to do that.

So we're gonna do that with a server side component. So the route to the componen, it's gonna do all that syncing for us. That's gonna be in new user. However, I was having an issue with forcing the sign up page to redirect to new user after signup. I don't think these props will work the way they're supposed to work, but I do have like multiple workarounds.

I'll explain the workarounds and then we'll pick one. So one workaround is we actually don't even have to render a signup page at all, because clerk has a hosted signup page that we can use if we don't have this in our app. Basically if we just get rid of this route and the middle where we're redirect to sign in, it'll go to the hosted page.

Which will then fix our issue most likely. I think that's what I had on the previous build, which is why it was working. I don't really like that. So I think the other way we can do it is we can just, we can check inside the homepage to see if you already signed in or not.

If you are, we'll send you to journal. But basically, we'll make this age group dynamic. If you are already signed in, will see your Journal. If you aren't signed in, then we will send you to new user, which will then trigger the off to kick up coz new user is an authenticator route.

And then, it will ask you to sign in, and because you went to new user, the redirect URL be stuck on the user and it'll send you back to new user as a sign-in person this time and then it'll do all the syncing. So that was a lot of explanation, I think that's the way we're gonna do it, so we have more control over it.

Hopefully it works. I haven't tried it yet. But in theory that should work. Yeah, if not, we'll just do the hosted page. So, let's try it. All right, so first things first is let's check for the user in there. It's actually really simple to do that. We can do the get, what is it called?

Gits clerk? No, it's a, actually, you know what? I don't have to guess, it's here. It is actually, so they have they have a lot of them actually. So they have current user, which is the one that we probably wanna get. We can actually just use off two, that's probably a better one.

So let's do that. So what we'll do is we'll get auth, so we can say, auth at the top here, input auth from clerk next js. And if you haven't had an intro to next.js 13 and server components, me put an async right here is probably gonna blow your mind.

But I can put an async array here on this component, and it works fine because it's only running on a server. It's not a client component. It's just a regular function. So that's pretty cool. So then inside of here, I can just say, await off like this.
>> Scott Moss: And I should be able to get back a user ID
>> Scott Moss: So then I can say I'll do something like this, let href =.

I'll just assume that you are not signed in, so I'll make it go to new-user. But if user ID, then href should equals journal.
>> Scott Moss: I guess I can just put this on one line it's bothering me so I'm gonna put it on one line. So user ID.

>> Scott Moss: So if you are logged in, we'll go to journal.
>> Scott Moss: Otherwise, we'll go to /new-user. I could probably even move this down here too, with async await. That's actually kinda crazy to think about, I've never done that, let me see. You could probably do just do await auth like, Like that, and that would work too.

Kinda crazy, I'm not gonna do that. No one's ever gonna approve that put request, so [LAUGH] it is just don't do it. Don't be that person that's like, whoa, I could, so I did. No one's ever gonna approve that. And if they do, they're crazy too. Like go work with somebody else.

And then it's coming out here and buying that to href, and now that'll be dynamic.
>> Speaker 2: I'm still on the async component.
>> Scott Moss: [LAUGH] Its so clean, right? [LAUGH] It's so clean. When I first learned server-side rendering in, like, 2015, this is what I thought it was, and it wasn't so now I'm like, okay, thus makes sense, like this is what i thought server-side rendering was, but no.

It wasn't [LAUGH].
>> Scott Moss: Okay, so we got that and now we can kinda check it. So, I don't think I'm logged in. I might have a cookie. We'll see. Yeah, so if I hover over this, it says journal, so I think that's because I might have a cookie in here where I was already logged in.

Yeah, I do, so let me delete that, delete that, let me refresh. So now when I hover over, you can see it goes to new user. So I don't have that cookie anymore.
>> Scott Moss: So what we wanna do is now we wanna go to this new user page and we wanna do a few things.

So first we wanna check. Well, you can't get to this page unless you're authenticated. So we don't really need to check to see if you are authenticated, but I guess it wouldn't hurt. So what we basically the agenda here is, if this if you got to this page, that means you are indeed a brand new user.

So we're gonna check in the database to see if there's not a user with this clerk user ID. If there isn't, create that user with a clerk user ID and do any initialization that we need for that new user, like a new Stripe account, a new project, new org, whatever.

Whatever you do for your app, do that here. If the user already exists then somehow you got to this page and you're already a user so we'll just redirect you to journal. You can get out of here. And that's the goal, right? So let's do that. And this is the server department so you can do all that on the logic.

So let's bring in our database so we'll say, prisma from utils prisma. Make sure you import the one from Util, the one that we made, not the one from at prisma. And if you're curious like how I'm able to do this at sign here, that's free because in our TS config we have this past setup right here that says at anything is a alias for the root slash the matching anything.

So I can say at and that basically puts me at the root of my folder slash anything. You set up more here if you want to. You just gotta make sure you do something called base URL or the base path. Now guess its base URL. You gotta add like a base URL here.

It's like dot, do you wanna set up more? It'll tell you if you don't. Cool, prisma and then let's just make some functions here. So we'll make a function that says, create new user like so. And then we'll use the same auth thing that we have from clerk, this will give us that user ID.

So we can say, this will be async, await auth, this will give us a user ID here.
>> Scott Moss: So now we have our userId, then what we wanna do is just go and see if there is a user in the database with this userId or not, right? So, now we can say, is there a match here?

So we can say, await.
>> Scott Moss: And this is where prisma is really cool. So now I can say prisma. And you can see, it knows that I have a user, it knows that I have a journal entry. It knows that I have an analysis, it just knows all that because it's type checked.

So, prisma.user, and I can say find, let's do, findUnique. Cuz I'm searching by a unique property. I can parse in an object, and I can say where. Which is another object, in this case where clerkId is userId, like so.
>> Scott Moss: And you'll just get a TypeScript error here saying, well, user ID might be no.

And this has to be a string. You can just ignore it, or if you really wanted to be saved, you could just say, well, it's a string. So we're gonna do that. And then we're gonna say, if not a match, [COUGH]. Cuz by default, this won't throw an error if it doesn't find it.

If you want it to, you can say, find unique or throw. And then it will throw an error if it doesn't find it. I don't want it to throw an error if it doesn't find it because I need to do some logic, if it doesn't find it. So I would just catch that error anyway.

So, don't even throw it. So, if there isn't a match, then this is a brand new user. Because we have this clerkId, and it's not in our database. So, either they went into their console, found all the appropriate JSON Web Tokens and cookies and IDs and added it in the console.

And then went to this route. This is probably a new user. So, they did that, they deserved that, they deserve it. Whatever they're trying to get, they deserve it, so.
>> Scott Moss: Yeah, so now we'll say user = await prisma.user.create.
>> Scott Moss: And for whenever you do a create, you can just do data, which is an object.

And then pass in all the things that a user needs. And it'll tell you if you hover over it, or it kinda tells you. It's just, it's very abstracted away. But it's the same thing, you're running a schema. So, if you forgot, just go look at your schema and you can see that a user needs.

A clerkId, an email, and entries is an empty array. So we don't have to put anything in there. Okay, so we need an email. So, this is what we want, we want current user here.
>> Scott Moss: This will be user, and I guess we probably should do a check here.

Cuz if, actually, this should never happen. Because this is an authenticated page through the middleware. So you should never be able to get to this page. This should never be known, because there's no way you should have gotten to this page without being authenticated. So I know you are at least authenticated, I'm trying to figure out if you're a new user or not.

Because this page won't load because our middleware will block you. Cuz I didn't make that a public. I didn't go in here and say, yeah, new user is a public page, I didn't do that. So, you can't get there unless you're authenticated. So, I can make that assumption.

So there's no need for me to do a check here to see if you are logged in. You are, unless my middleware failed. And if that's the case, I got bigger problems. So, I don't need do a check there. And then can come out here and say

>> Scott Moss: Like that, this thing still gonna freak out cuz that might be. No, it's not, but it's still gonna freak out. And then here clerkId is And then email is user(.), okay, he stopped doing it. Email, I think it's, we'll see. And it looks like I'm not gonna be able to call it user cuz I have a user here.

So I'll just call this new user, like that.
>> Scott Moss: It says user does not exist on type 'User'. So, what does exist on user then? Let's see.
>> Scott Moss: Show me, we'll just log it and we'll see, that's the best way. [LAUGH] That's how you do. Wait, is it gonna show all my personal stuff?

We'll see [LAUGH]. Cuz I did log in with Google, but I don't think there, we'll see, I got nothing to hide. All right, so we'll do that. We got a new user, it's got the card thing. I don't have any additional setup that I need, but this is where you would do strive.

And like I said, any initial setup you would need for a user, you would do it here on sign. This is basically your sign up, right? So we have that new user. Once all that is done, I mean, we don't even really need to
>> Scott Moss: Wait, save that anywhere, so we could just avoid it.

And now what happens is once we get past this, we're basically in the same state. So, once we already created a user, we're in the same state as if there was no match. As in, I'm sorry, we're in the same state as if there was a match. So, you are basically our current user in our database now.

So we can just go down to after this if statement, and we can redirect now, right? So now we can just be, yeah, cool, let's just redirect.
>> Scott Moss: Redirect, I know there's a redirect in there somewhere.
>> Scott Moss: What is all this stuff?
>> Scott Moss: Let's see. I think the redirect from
>> Scott Moss: Next, mm-hm, let's go see.

From next navigation. That's what I thought about. I couldn't remember if that was only client side or not. But I guess that does work on the service side. I just wanted to verify with what I had. Yeah, I guess that, I don't know why I thought that only worked on the client side.

That's awesome, okay, good for them. That's probably why they recommend it now. So, navigation, [COUGH] then redirects.
>> Scott Moss: This thing needs a from, that's why. Okay, and then now we could just say, yeah, cool now redirect to journal.
>> Scott Moss: So we got create user, we don't need off anymore.

>> Scott Moss: I mean, we could show like a loading spinner here if we want, but it doesn't really matter. There's really no UI concept here. We're basically using this page as an API route if you think about it, but it's just happening on the server. And then now I can just call the function, right?

I could just be, yeah, cool. Async, and it just awaits.
>> Scott Moss: Create new user.
>> Scott Moss: Cool, I'm guessing user doesn't have e-mail, so we'll figure out what that is in a second. But everything else should be good, yes.
>> Scott Moss: Message from the chat that user.e-mail addresses is the property and it returns an array of emails associated with the user.

Got it addresses returns an array. I think I probably had that somewhere already. Yeah, looks like I had an all file that [LAUGH] basically did that. Yeah, email addresses. Yeah, line 25 there. Yeah, okay, that makes sense. Let's try that, so user. emailAddresses,
>> Scott Moss: [0].emailAddress, I think it's always gonna be the first, in this case, we only have Google or whatever.

So it's always gonna be like that one. And this is just gonna freak out cuz this could be undefined. But there's no way you could have become a clerk user without having an email. It's either you have to pay Google, which means you have an email, or you have to sign up with email, which means you have an email.

So this will always be defined, but obviously, the SDK doesn't know that. So it'll still run. This is a TypeScript error. Okay, so now we have that. We create NewUser. Let's see what happens. And then we should get a 404 cuz we haven't made the journal page yet.

So I'm gonna do that, gonna hover over this. I'm not signed in, so it's telling me go to NewUser. If I click this, it should go to NewUser or attempt to go to NewUser, but the middleware is gonna intercept it and be like, nah, you gotta sign in.

Then I'll click sign up, I'll then sign up. That should redirect me back to NewUser, and then NewUser will create me a new user database, which should then redirect me to channel, hopefully. [LAUGH] So a lot of work. Let me also just make sure I deleted myself from clerk.

I did not, so you can just come here and delete yourself to be a new user again. And actually, I don't think that affects how this works, but just doing it anyway. Okay, let's give it a try. So click get started. Boom, middleware automatically redirects us. We can see the redirect URL is indeed pointing us to NewUser, so that's good.

I'm gonna click Sign up. I'm gonna continue with Google. Gotta log in with my Google's. It went to NewUser. NewUser waited for a minute, and boom, it redirected me to journal. So it worked exactly like we said it was gonna work. So that's why I was like, you can probably show a loading screen there if you wanted to cuz I knew it was gonna pause for a little bit because it's going to clerk to get the user.

And then it's going to our database to create the user. So I knew there's gonna be a little pause there. So you can show a loading animation there or whatever you want. I can show you how you can get that for free. In fact, if you took the intro course, you know all you have to do is make a loading file in here.

>> Scott Moss: And you get that for free. So we're not gonna do that right now. Okay, that works. Let's verify some things. So let's go look at our database. So we'll look what's in our database, npx prisma,
>> Scott Moss: studio, it's gonna launch an app and where we can look at all the things in our database.

You can see I indeed have one user in there, and that user is me. There's my email. I have no journal entries. Here's my clerk ID and all the other things. So it works.
>> Scott Moss: All right, and now if I go back home, if I hover over that button, it should just send me to journal and never to NewUser.

So it just goes to journal cuz I'm logged in.
>> Scott Moss: Cool, any questions on that?
>> Scott Moss: This is the easiest auth you're ever gonna make, I'm gonna tell you right now. It doesn't get easier than this. I remember having to roll auth from scratch, and this is before JSON web tokens.

[LAUGH] This is like rolling your own memory for cookies and sessions on the server, my God. And you go to JSON web tokens, I thought that was so much easier, even that became a pain. So it doesn't get easier than this. But if you did wanna roll your own, somewhat roll your own, as in you didn't wanna use a service, but you don't wanna write all the code for it, there's literally a package called NextAuth.

That's the only thing I would ever use if I was gonna roll my own auth. So highly recommend using this. I was gonna show this in the course instead, but I just didn't think it would be honest of me to recommend something I wouldn't use, so I wouldn't use it.

I'd just pick a service. I just wanna get back to writing the code of my app. I don't wanna be innovating on Auth. To me, Auth is the same, that is always ever gonna be. And when it changes, I want the experts to figure it out, not me.

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