ProNextJS
    Forms Management with Next.js App RouterLevel up with Forms Management with Next.js App Router

    I respect your privacy. Unsubscribe at any time.

    This is a free tutorial

    In exchange for your email address, you'll get full access to this and other free ProNextJS tutorials.

    Why? First and foremost, your inbox allows us to directly communicate about the latest ProNextJS material. This includes free tutorials, NextJS Tips, and periodic update about trends, tools, and NextJS happenings that I'm excited about.

    In addition to the piles of free NextJS content, you'll get the earliest access and best discounts to the paid courses when they launch.

    There won't be any spam, and every email you get will have an unsubscribe link.

    If this sounds like a fair trade, let's go!

    lesson

    Implement a Route Handler for JSON POST Requests

    Jack HerringtonJack Herrington

    The first step in server-side validation is setting up an endpoint to send the data to. This will be an API route in our project.

    Inside of the project directory, create a new file at the following path:

    api/register/route.tsx
    

    This file tells Next.js that this function will handle requests to /api/register. We use route.tsx instead of page.tsx because we want to handle HTTP methods like POST and GET directly instead of returning any HTML or React components.

    Define a Route Handler

    Inside of the route.tsx file, import NextRequest and NextResponse from next/server.

    import { NextRequest, NextResponse } from 'next/server'
    

    Next, we'll define a route handler for POST requests to the endpoint. The request will be of type NextRequest. We'll get the JSON payload from the request, and store it in a variable called data. The return from this function will be a NextResponse that sends a message back that the user has been registered. We'll implement the validation and database saving in the next steps:

    export async function POST(req: NextRequest) {
      const data = await req.json();
      // Add data to the database
      return NextResponse.json({ message: "User registered"});
    }
    

    Validating the Data

    Remember, never trust client data implicitly. You should always use validation on both the client and server side, and make sure they match.

    Therefore, let's import the schema from registrationSchema and use Zod's safeParse() function to validate the data.

    The safeParse function won't throw exceptions if the data is invalid. However, it ensures that the data matches our schema. If it is successful, its output includes the cleaned data. Otherwise, it will give us errors including which fields are wrong.

    We can add conditionals to the function to check and proceed accordingly:

    import { schema } from "@app/registrationSchema"
    
    export async function POST(req: NextRequest) {
      const data = await req.json();
      let parsed = schema.safeParse(data)
      if (parsed.success) {
        // Add parsed.data to the database
        return NextResponse.json({ message: "User registered", data: parsed.data });
      } else {
        return NextResponse.json(
          { error: parsed.error },
          { status: 400 }  
        )
      }
    }
    

    With the handler written, we can now prepare to test it on the client.

    Updating the onSubmit Function

    In order to test our server-side validation, we need to replace the console.log() in the onSubmit with a fetch request to api/register, using the POST method. We'll indicate that we are sending JSON data, which will be stringified. Then we'll parse the response and log it to the console:

    // inside of RegistrationForm.tsx
    const onSubmit = async (data: z.infer<typeof schema>) => ({
      fetch("/api/register", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(data)
      })
      .then(response => response.json())
      .then(data => console.log(data))
    });
    

    Testing the Form on the Page

    Over on the page, provide valid data in the form and hit submit. Inside the console, we should see the server response that indicates that the user is registered:

    // inside the browser console
    { message: "User registered", user: { /* user data */ } }
    

    It seems like our server-side validation is working correctly, but the browser makes it difficult to test if it's actually applying the validation.

    We can test this in a terminal.

    Testing Server-Side Validation from the Terminal

    The curl command can be used to send an HTTP request to the server.

    We'll use -X POST to specify the method, along with the localhost address to the api/register endpoint. The data will be an empty object, and we'll provide a header that specifies it's a JSON payload:

    curl -X POST http://localhost:3000/api/register -d "{}" -H "Content-Type: application/json"
    

    Hitting enter, we get a response with the "Invalid data" message and the error fields from Zod.

    The First Validation Method

    You have now seen the first mechanism for connecting client-side and server-side validation!

    The next technique we look at will also involve a an API route, but this time instead of sending JSON is will be sending form data.

    Your task is to try implementing it on your own using the following resources. When you're ready, you can check out the next video to see how it's done.

    Resources for a Form Data API Route

    The simplest way to send FormData to a server action is to set the type of the first parameter to FormData. Then use Object.fromEntries to convert that into a JavaScript object.

    async function onFormData(formData: FormData) {
       "use server";
       const data = Object.fromEntries(formData);
       ...
    }
    

    Then over on the client side, create the FormData object using new FormData() and use the append method to set the fields before calling the server action that takes the form data as a parameter.

    There is also an additional technique you could use that uses the useFormState hook that we will explore in the next video.

    Transcript

    All right, now let's talk about server-side validation. Now, the first thing we need to do is have a place to actually send this data to. So let's go create an API route in our project. Now, I'm going to use api register and then route.tsx.

    That means that this function is handling any requests to /api/register. And it's route.tsx instead of page.tsx because we want the lower level primitives. We want to be able to handle things like POST and GET using just our code.

    We don't actually want to return any HTML or do any of that React stuff. We just want to handle the raw request. So let's bring in next request from the next response from the next server. And now we need to define a route handler. Now, from this route handler, we want to handle POST.

    We want to POST to this endpoint. And what we're going to POST is a JSON payload, first and foremost. So we're going to get that as the request. And we're going to need to crack that request and get the JSON payload. To do that, we use await request JSON. That gives us back our data.

    So since we trust the client implicitly, we're just going to add that to the database. And we're going to reply that the user is registered. Of course, because we would never call this if we didn't have valid data, right? Well, of course, wrong. We want to make sure that we have validation on both the client and the server side.

    And we want those validations to match. So let's bring in the schema and then use it to validate this data. To do that, we bring in schema from registration schema. And then we use that schema and the safe parse function that was created by Zod to parse that data safely.

    Now, safe just means it's not going to throw any exceptions if that data is invalid. So data could be anything. It could be null. It could be arrays. It could be whatever. But it's going to make sure that that data matches whatever our schema says. Now, coming out of parse are a couple of different values that we want. Success.

    That tells us whether it was successful or not, whether we actually matched the schema. If we did, then we have .data. That is the cleaned up data. Otherwise, we get a set of errors. So let's go and add some conditionals to look at that. So now that we have that parsed response, let's go and take out this code

    and replace it with a conditional that looks to see whether we were successful or not. And if we were successful, then we will return back the fact that the user is registered, as well as the data. And for now, if it's successful, then we will return that we have invalid data and give back the error. So we know which fields are wrong.

    OK, now let's try this out on the client. To do that, I'm going to replace our console log with a fetch to our API register endpoint. We're going to use a method of post that matches the async function that we have here, that have the same verb. Then we're going to tell it that we have a content type of application JSON. That means that it is a JSON post.

    And then we're going to send it the data. So we're going to stringify that data as the body of the request. And then when we get the response back, we're going to parse that JSON. It's always going to be JSON, good or bad. And then we're going to console log that out. So let's take a look and see if it worked. All right, we've got our form running in our console. It's time to hit submit. Nothing good there.

    So let's add in some good data. Hit submit. And now we get a response back that we are registered, user registered. And we have our user data. So if I were to go and add some whitespace in here, we would see that it would also trim that whitespace out for us. So everything is both cleaned and also validated. Fantastic.

    But how do we know if it's actually really applying that validation? Because, I mean, it just could pass. So let's go and see if there is a way to actually test it. Can't test it from the UI because we can't get around the client-side validation. So let's go to our Visual Studio code. Bring up another terminal.

    And then from here, run a curl against the exact same endpoint, API register. We'll give it no data. So it's going to be invalid. And then a content type of application, JSON, to show that it's a JSON payload. Return. And now we get a message back from the server saying invalid data, just like we expected. And we've got the error.

    And it tells us all of the errors that are there. In this case, we failed all of the fields. But if you are a end user of this API endpoint, that's really nice. Because it actually tells you which fields are missing.

    And that, as a API user, a legitimate user, that is fantastic information for me. So now we've implemented the first of our four different mechanisms for connecting our client to our server. The next one is going to be an API route as well. But instead of sending JSON data, we're going to send form data.

    And I want you to take a try at this first. There's some helpful information in the instructions. And then when you are through, you can go see how I implemented this in the next video.