DEV Community 👩‍💻👨‍💻

Cover image for Intro to Next.js API and handling Form-Data
Naman Agarwal
Naman Agarwal

Posted on

Intro to Next.js API and handling Form-Data

A walkthrough on how to build a full-stack application using Next.js for both the frontend and the backend 🔥 🔥.

The framework provides the API functionality which allows us to build dynamic API's at scale. The API can be used in production with ease and can replace a full fledged backend service for certain business usecases.

Decision to use Next.js API depends on the scale of the system that we are trying to build and I personally think it's a no-brainer for small businesses and MVP's. Another criteria before choosing Next API is to decide if your team can work effectively on a monolith architecture. Below I have listed down some usecases where Next API's can be used:

  1. Form submission
  2. Gateway/Proxy server
  3. UI Theme Provider

Note: This writeup assumes basic knowledge about building Next.js frontends.

Building API's is similar to building frontends in Next. We put all the API logic inside the api folder within the pages folder. Each folder/file within the api folder is a route, similar to how page routing works. Let's see how it looks so far.

folder structure

Let's start by building an API route /form. There are 2 ways to do so, either make a form.js file within the api folder or create a form folder with an index.js file. As you can sense this is exactly similar to how frontend routing works.

route

This route is accessible on the same port as the frontend app, and the way to access it is: http://hostname:port/api/form

It's time to start coding the logic for /form. As repetitive as it can get while coding anything in Next, start by default exporting a function, which acts as the handler for all request on the /form route. Note, similar to how vanilla Node.js handles requests, any request on /form route irrespective of the HTTP method (GET, POST etc) will be sent to this handler. Let's take a look how the handler looks:

export default async function handler(req, res) {

}
Enter fullscreen mode Exit fullscreen mode

By default (using the default config) following helpers are available:

  • req.query: parses the request query params string as object.
  • req.body: parses the request body json to object.

Default config can be changed as below:

export const config = {
  api: {
    bodyParser: false,
  },
};
Enter fullscreen mode Exit fullscreen mode

The config disables parsing request json body to object.

I'll end my intro to Next API's here. To explore more complex usecases, checkout the Next.js documentation here 😌.

Lastly, I'll leave you with a small example on how to handle form-data in Next API 😮:

Handling Form-Data in Next.js API 🔥

Find the GitHub Repository: here

import formidable from "formidable";
import * as yup from "yup";

let formSchema = yup.object().shape({
  name: yup.string().required(),
  email: yup.string().email().required(),
  image: yup.mixed().required(),
});

async function saveFormData(fields, files) {
  // save to persistent data store
}

async function validateFromData(fields, files) {
  try {
    await formSchema.validate({ ...fields, ...files });
    return true;
  } catch (e) {
    return false;
  }
}

async function handlePostFormReq(req, res) {
  const form = formidable({ multiples: true });

  const formData = new Promise((resolve, reject) => {
    form.parse(req, async (err, fields, files) => {
      if (err) {
        reject("error");
      }
      resolve({ fields, files });
    });
  });

  try {
    const { fields, files } = await formData;
    const isValid = await validateFromData(fields, files);
    if (!isValid) throw Error("invalid form schema");

    try {
      await saveFormData(fields, files);
      res.status(200).send({ status: "submitted" });
      return;
    } catch (e) {
      res.status(500).send({ status: "something went wrong" });
      return;
    }
  } catch (e) {
    res.status(400).send({ status: "invalid submission" });
    return;
  }
}

export default async function handler(req, res) {
  if (req.method == "POST") {
    await handlePostFormReq(req, res);
  } else {
    res.status(404).send("method not found");
  }
}

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

Enter fullscreen mode Exit fullscreen mode

To parse the form-data in request body, I have used Formidable package which is quite easy to work with. I have also provided a saveFormData function which can used to save data on persistent stores like firebase, mongoDB etc.

Next, sending a request to the API from a page:

async function submitForm(data) {
    const f = new FormData();

    f.append("name", data.name);
    f.append("email", data.email);
    f.append("image", data.image.fileList[0]);

    const res = await fetch("/api/form", {
      method: "POST",
      body: f,
    });

    const resBody = await res.json();

    if (res.status == 200) {
      notification.success({
        duration: 3,
        message: "Submission Status",
        description: resBody.status,
        onClose: () => router.reload(window.location.pathname),
      });
    } else {
      notification.error({
        duration: 2,
        message: "Submission Status",
        description: resBody.status,
      });
    }
  }
Enter fullscreen mode Exit fullscreen mode

I'll end here and as I said, the usefulness of Next API depends on the problem you are trying to solve, the scale of the system and collaboration required.

I hope this writeup was useful, have a nice day ✌️.

References

Top comments (2)

Collapse
 
damiano216 profile image
dam216 • Edited on

Using this code I am able to read an image in the backend, but not other...

Here is the frontend code

 const form = new FormData();
  form.append("file", file);


    fetch("/api/apiRouteFileNameHere", {
        method: "POST",
        body: form,
      })
        .then((r) => r.json())
        .then((data) => {
          console.log("data", data);
        })
        .catch((err) => {
          console.log("err", err);
        });

Enter fullscreen mode Exit fullscreen mode

And the Backend code in the NextJS API route

import formidable from "formidable";

async function saveFormData(fields, files) {
  // some images are printed here, some other no. Below are two images as example
  console.log("files", files);
}

async function handlePostFormReq(req, res) {
  const form = new formidable.IncomingForm({ multiple: true });

  const formData = new Promise((resolve, reject) => {
    form.parse(req, async (err, fields, files) => {
      if (err) {
        console.log("err", err);
        reject("error");
      }
      resolve({ fields, files });
    });
  });

  try {
    const { fields, files } = await formData;
    // const isValid = await validateFromData(fields, files);
    // if (!isValid) throw Error("invalid form schema");

    try {
      await saveFormData(fields, files);
      res.status(200).send({ status: "submitted" });
      return;
    } catch (e) {
      res.status(500).send({ status: "something went wrong" });
      return;
    }
  } catch (e) {
    res.status(400).send({ status: "invalid submission" });
    return;
  }
}

export default async function handler(req, res) {
  if (req.method == "POST") {
    await handlePostFormReq(req, res);
  } else {
    res.status(404).send("method not found");
  }
}

export const config = {
  api: {
    bodyParser: false,
  },
};
Enter fullscreen mode Exit fullscreen mode

For example, the API route code can read this image content
Image description

However, when passing this other image to the API route code, nothing happends or gets printed on the terminal.

Image description

I spend a lot of time on this but couldn't find a fix.
What do you think is the mistake?

Thanks!

Collapse
 
divinenaman profile image
Naman Agarwal

Can you check if you have set the "multiple" attribute to true in the input html element and also can you share what exactly is "file" in the first piece of code.

Hey 😍

Want to help the DEV Community feel more like a community?

Head over to the Welcome Thread and greet some new community members!

It only takes a minute of your time, and goes a long way!