Next.js Discord

Discord Forum

request.body is appearing as a string

Answered
Giant panda posted this in #help-forum
Open in Discord
Giant pandaOP
I am sending form data via the fetch api (post request) to a page API route.

      const formData = new FormData(event.currentTarget);
      const response = await fetch('/api/jobUpload', {
        method: 'POST',
        body: formData,
      });



export default async function handler(req: NextApiRequest, res: NextApiResponse) {
 const { method, body } = req;
  
    await dbConnect();
}



When I read the value of the body i get the following string
'------WebKitFormBoundaryUB3bmQCSu0puH8ox\r\nContent-Disposition: form-data; name="title"\r\n\r\ntester\r\n------WebKitFormBoundaryUB3bmQCSu0puH8ox\r\nContent-Disposition: form-data; name="datetime"\r\n\r\n\r\n------WebKitFormBoundaryUB3bmQCSu0puH8ox\r\nContent-Disposition: form-data; name="occupation"\r\n\r\n\r\n------WebKitFormBoundaryUB3bmQCSu0puH8ox\r\nContent-Disposition: form-data; name="description"\r\n\r\n\r\n------WebKitFormBoundaryUB3bmQCSu0puH8ox--\r\n'


how can i convert this to an JSON object so i can validate the content with ZOD
Answered by joulev
try this code
import { type Fields, type Files, formidable } from "formidable";
import type { NextApiHandler } from "next";
import type { IncomingMessage } from "node:http";

const form = formidable({ multiples: true });

function parseForm(req: IncomingMessage) {
  return new Promise<{ fields: Fields; files: Files }>((resolve, reject) => {
    form.parse(req, (err, fields, files) => {
      if (err) {
        reject(err);
        return;
      }
      resolve({ fields, files });
    });
  });
}

const handler: NextApiHandler = async (req, res) => {
  const { fields } = await parseForm(req);
  console.log(fields);
  res.status(200).send("Ok");
};

export const config = {
  api: {
    bodyParser: false,
  },
};

export default handler;
View full answer

20 Replies

Silver Marten
Use something similar to this:

  const jobSchema = z.object({
  title: z.string(),
  datetime: z.date(),
  occupation: z.string(),
  description: z.string(),
});
const MyForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = async (data) => {
    // Validate the form data against jobSchema
    try {
      const validatedData = jobSchema.parse(data);
      // If validation is successful, you can send the data to your API
      // Replace this with your API call
      console.log('Valid data:', validatedData);
    } catch (error) {
      console.error('Validation error:', error);
    }
  };
  return (
     <form onSubmit={handleSubmit(onSubmit)}>
      <label>Title:</label>
      <input {...register('title')} />
      {errors.title && <span>{errors.title.message}</span>}

      <label>Date and Time:</label>
      <input type="datetime-local" {...register('datetime')} />
      {errors.datetime && <span>{errors.datetime.message} (Please enter a valid date and time)</span>}

      <label>Occupation:</label>
      <input {...register('occupation')} />
      {errors.occupation && <span>{errors.occupation.message}</span>}

      <label>Description:</label>
      <textarea {...register('description')} />
      {errors.description && <span>{errors.description.message}</span>}

      <button type="submit">Submit</button>
    </form>
  )
and in your api you wanna make sure to pass the schema as well:
import {jobSchema} from "@/schemas/path/to/jobschema"
  export async function POST(req: NextRequest, res) {
    try {
       const formData = jobSchema.parse(req.body);
      // handle the data using formData
    } catch (error) {
    return NextResponse.json({message: "Invalid schema"}, {status: 500}}
  }  
  }
Giant pandaOP
nice thanks
got to convert to use the api router for the NextRequest
@Giant panda got to convert to use the api router for the NextRequest
if you use the app router then you can use [req.formData](https://nextjs.org/docs/app/building-your-application/routing/route-handlers#request-body-formdata) directly, but of course migrating to the app router is not required to parse form data
Giant pandaOP
your amazing, ill keep you updated if i run into any troubles
thanks
Giant pandaOP
hey ive turned off bodyparser and left the pages router still up (until a new pr)
but its freezing when formidable tries to parse the request object
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { method } = req;

  await dbConnect();

  switch (method) {
    case 'POST':
      try {
        const form = formidable();
        const [fields] = await form.parse(req)
this is the request object:
Just FYI, i tried changing the typescript type to NextResponse and NextRequest, but it still froze in the same place
@Giant panda but its freezing when formidable tries to parse the request object
try this code
import { type Fields, type Files, formidable } from "formidable";
import type { NextApiHandler } from "next";
import type { IncomingMessage } from "node:http";

const form = formidable({ multiples: true });

function parseForm(req: IncomingMessage) {
  return new Promise<{ fields: Fields; files: Files }>((resolve, reject) => {
    form.parse(req, (err, fields, files) => {
      if (err) {
        reject(err);
        return;
      }
      resolve({ fields, files });
    });
  });
}

const handler: NextApiHandler = async (req, res) => {
  const { fields } = await parseForm(req);
  console.log(fields);
  res.status(200).send("Ok");
};

export const config = {
  api: {
    bodyParser: false,
  },
};

export default handler;
Answer
Giant pandaOP
Nice, I didn’t realise you had to export the config inside the api route
I was putting it in next config
Works great
that parseForm is among the functions i made once and never tried to understand it again, i just copy paste whenever i need it