You need to accept input from a user to sign them up to your application. Creating a user in the database is a straightforward Prisma.create function call.
The tricky part here is that we want to ensure bad data is not inputed into the form. We’ve done form validation before but this lesson will show you a more complex version of it. Among the validation that is applied, you will hash the password that’s given to us before saving it to the database as well as check the database for existing users when a sign up is attempted.
Ian Jones: [0:01] You're going to wire up the signup page.
[0:04] At the end of the lesson, when someone fills out the email and password form, they will create a user in the database and be redirected to the login page.
[0:15] You might remember that you post to a Remix ActionFunction to create entries in the database.
[0:23] Here is the basic structure of the signup action. It grabs the email and password off of the formData from the request. It then calls the signup user function with that data.
[0:36] After that, you redirect to the login page and have the user log in to the application.
[0:43] The user signup function hasn't been created yet, so let's create it. You can add a new file called users.server.ts. Signup user will take an email and a password, both of which are strings.
[1:01] Next, you can pass those fields to db.user.create. You don't ever want to return the hashed password field, so you can pass a select object selecting every field, except the password.
[1:16] Back in the signup page, you can import the function. Ignore the TypeScript errors for now, you can come back and fix them later.
[1:24] Now, you can head over to the browser and create a user. I will type ian@email.com and a password. You will notice that by submitting the form, you are redirected to the login page.
[1:37] To double-check that the user is really created, open up Prisma Studio, and let's inspect the database. Here in the users table, you'll see that you have an email and a password.
[1:49] Obviously, you didn't hash this password. It's important to note that you don't want to store plain text passwords in your database. Because you are iteratively building a system, it's OK to test things out without doing them 100 percent correctly the first time. You just don't want to deploy this to production.
[2:10] Let's go solve this issue. First thing you'll do is run npm install secure-password in your terminal. This package doesn't come with TypeScript types out of the box, so you can run npm install savedev@types/secure-password.
[2:29] Now, back in your code, you will create an AuthUtil server.ts file. The hash password function is relatively simple. It converts a string to a buffer and parses that buffer to the secure_password hash function.
[2:47] Next, you take the hashed buffer and re-encode it into a string, so that you can store it in the database. Now that you're hashing a password, you can update the call to signup users to hash the password that comes in.
[3:02] Now, when you create a user from the signup page and then head over to Prisma Studio, you will see that the password is indeed, hashed.
[3:17] Now, it's time to validate the data that is coming in to the signup action. You want to prevent people from submitting bad emails and passwords. You have already seen how to validate form input before.
[3:31] The first thing you can do is head over to the validations file and create a signup schema. The schema will take an email that's a string and a password that is a minimum of five characters long.
[3:45] Back in the action, here you can see the validation code. You can see you're using this BadRequest helper function.
[3:53] The BadRequest function takes a type of action data. The data is of type action data, which in turn is parsed to the Remix JSON function with the status that defaults to 400, because we're returning a bad request.
[4:09] This action data type will look very familiar to your Post form action data. There's an error key with the form error and field errors for email and password. Action data also returns fields of email and password to keep the state of the signup form, if there's any error.
[4:32] Back in the action. For TypeScript sake, you want to inspect raw email and raw passwords type, so that we know the values are strings. Form data can be anything from a file to a number to a string, so you want to narrow down the possibility to a string.
[4:50] Next, you can create a fields object to parse back to your form, if there is an error.
[4:56] Now you can call signup.safeParse to validate the incoming data, passing in raw email and raw password. If success is false, you know you got an error, so you can flatten the error and return a BadRequest.
[5:13] The ActionFunction is returning JSON, but our page component isn't. You can call useActionData and pull off the error in field, parse those into the userForm.
[5:26] When you head on over to the browser to test your validation, you can see that errors are being parsed back when the input doesn't match what the signup schema requires.
[5:37] When you try to create a user that is already in the database, you will get a Prisma error. You can see that the error says, there's a unique constraint on the user's table. To avoid this error in production, you can check the database before attempting to create the user.
[6:04] Head over to the user.server file. Here's a function that queries the count of users that have a particular email address that's passed in. If the number is greater than zero, you know the user already exists in the database.
[6:20] Back to your ActionFunction, you can call the checkUserExists function and if it's true, you can return a BadRequest with a form error explaining that the user already exists. Notice that the button is hard-coded to say login.
[6:46] You can solve this by replacing the button with children and have the page component parse the submit button. You will also need to move the useTransition hook up to the signup page.
[7:11] Here, you can parse the button as children of the userForm. Add the useTransition hook to the signup page. One last thing, user signup may fail because of some random issue. You want to check if user signup returns a user.
[7:59] If user exists return a redirect, else return a BadRequest saying something bad happened. To review, you created an ActionFunction. You got the values for the user from the userForm. If those values aren't strings, you return a BadRequest.
[8:23] You validated that the input matches your schema. If it doesn't, you pass the field errors back to the user. If it does pass your schema, then you check if the user exists. If they do, you return a BadRequest.
[8:38] If they don't exist, you can call user signup passing email and password. If you successfully created a user, you redirect the login page. If you don't, you sadly say something went wrong.
[8:53] Meanwhile, inside of your user service, you made sure to hash the user password, because you never want to store plain text passwords in the database. Here's what it looks like one last time.